Preserve the exact generated client interfaces, render-cache behavior, and ordered display ticks.
Teach Java mixin generation to linearize a registered parent and exclude transient runtime caches from copying.
Keep TIInventoryTile as AE2's public rebuild API and JInventoryTile as the registered mixin input, preserving both generated interfaces and flattened sided-slot behavior.
Preserve the exact generated interface and ordered fluid behavior.
Isolate FluidStack.amount access outside the transformed class. The transformer treats every field read as trait-owned state.
Freeze ordered tank state and fluid distribution behavior.
Pin capability short-circuiting and the exact generated interface. All checks run against the untouched Scala trait.
The fourth generated tile written directly in Java, and the first with
both mutable state and inherited member access. The eight characterization
tests pass unchanged, and the runtime interface keeps the exact thirteen
methods. INeighborTileChange, the surface shipping jars link against, is
byte-identical between the reference and ported dev jars, and no consumer
references the removed $class or closure classes.
Two transformer constraints turned up, both now in the handoff gotchas.
registerJavaTrait rejects a trait with a non-empty InnerClasses attribute
outright, so the anonymous AbstractFunction1 for the operate callback
could not stay in the trait; it is a named class in the access shim.
The shim's parameters are Object rather than the trait type. Typing them
as the trait let javac elide the cast to TileMultipart, since the
untransformed trait extends it, and the verifier then rejected the
getfield once Forge rewrote the trait to an interface.
TRedstoneTileAccess only escapes this because IRedstoneTile is unrelated
to TileMultipart and forces a real checkcast.
Seven behavioral tests and a generated-interface freeze, both against the
untouched Scala trait. Nothing in the frozen consumer baseline references
TTileChangeTile or a $class helper for it, so there is no binary fixture
to take: the load-bearing surface is INeighborTileChange, which is already
Java and unchanged.
The behavior pinned is the flag lifecycle and the coordinate filter.
bindPart only ever ors the flag in, copyFrom takes it from another change
tile but leaves it alone for a plain source, clearParts resets it, and
partRemoved recomputes from the remaining list rather than from the part
handed to it. onNeighborTileChange dispatches only for an axial offset of
one or two blocks, and marks the two-block case weak.
The runtime interface is the exact thirteen methods: six overrides, the
field getter and setter, and five super accessors.
rayTraceAll was the one method the read-path cleanup restructured, moving
from an indexed loop over a copied list to an iterator with a manual
counter, and nothing covered it. The index it writes into ExtendedMOP.data
is what reduceMOP hands back to every click, activate, harvest and pick
block, so an off-by-one there would misroute interactions silently.
Six tests pin the index production: a part that misses still consumes its
index, sorting by distance happens after the indices are assigned, the
part's own data is nested underneath rather than replaced, subHit keeps
its default because data is assigned directly, and an index taken from a
hit still selects the part that produced it. harvestPart's indexing and
collisionRayTrace's nearest-or-null are covered alongside.
All six also pass against the pre-cleanup implementation at 370b353^,
which is the evidence that the restructure preserved behavior.
Kept in the TickScheduler$ shape: the companion holds the implementation
because Forge registers that instance itself, and the plain class holds static
forwarders. Both halves are load-bearing for different reasons -- guidenh
resolves renderWorldBlock as a static first and falls back to MODULE$ -- and
neither has a bytecode consumer, only reflective names.
The emitted class list is unchanged. All five rendering callbacks keep their
signatures, MODULE$ keeps its type and flags, and both classes keep class-level
@SideOnly(CLIENT). Only three Scala forwarders for inherited
TileEntitySpecialRenderer members and the companion clinit's ACC_PUBLIC differ.
The render id is still claimed in the singleton's initialiser, matching the
reference's first-access timing. The breaking-overlay branch moved to a private
helper that checks the erased Tuple2 and the boxed index before unboxing, which
is what the reference's pattern match compiled to.
125 plain-JVM tests and 22 Forge server tests pass, none changed by the port.
This class needs a client, so it adds nothing to either suite and everything to
the manual checklist.
Everything this class does needs a client, so nothing here can run it. What is
pinned is the contract two other parties depend on and the ABI diff cannot see.
guidenh reflects on both MultipartRenderer and MultipartRenderer$ by name,
calling renderWorldBlock as a static first and falling back to the companion's
MODULE$, so both the static's exact signature and the singleton field are
pinned. Forge needs that singleton to be both a TileEntitySpecialRenderer and
an ISimpleBlockRenderingHandler or it rejects the registration. Both classes
carry class-level @SideOnly(CLIENT) and are stripped on a dedicated server.
Every lookup uses initialize = false: initialising the companion runs its
constructor, which claims a render id from Forge's RenderingRegistry.
125 plain-JVM tests, 22 Forge server tests.
A Scala package object compiles to a class literally named 'package', and that
is a Java keyword, so no Java source can declare or even name it. The
characterization had to reach both through Class.forName, which is the argument
in one line. There is no Java form to port to.
Each held exactly one member, a logger alias for its proxy, so the indirection
is inlined at its eight call sites: ConfigContent (7) and MultipartGenerator
(1). The functional characterization asserted beforehand that each alias
returned the very instance its proxy holds, so the substitution is an identity;
both classes are descriptor-identical afterwards.
Exactly four classes leave the jar and nothing else changes. No consumer
referenced them, and no Java consumer could have.
120 plain-JVM tests and 22 Forge server tests pass. PackageObjectCharacterization
went with its subject; ProxyLoggerFunctionalTest keeps the assertion worth
having, that both proxies publish a logger during preInit.
Each holds one member, a logger alias for its proxy's logger. Everything goes
through reflection because 'package' is a Java keyword and cannot appear as a
type name in Java source, which is also why these two classes have no Java
form at all.
The plain-JVM half pins the shape and that the alias reads the proxy's var
rather than holding anything, so it reports null before preInit. The Forge
server half pins the part that matters for what comes next: after preInit each
alias returns the very instance its proxy holds, adding nothing.
123 plain-JVM tests, 23 Forge server tests.
isControlDown(EntityPlayer) and isClientPressing() keep their descriptors.
map() became a java.util.Map and isClientPressing_$eq became setClientPressing.
Dropped playerControlValue and ControlKeyValue. They existed only to give Scala
player.isControlDown sugar through an implicit conversion, and the reference
already shipped the explicit static 'for Java users'. MicroblockPlacement now
calls that static directly.
ControlKeyHandler was a Scala object extending KeyBinding, so the instance both
the key registry and the event bus needed was ControlKeyHandler$.MODULE$. It is
now an ordinary KeyBinding subclass with an INSTANCE constant, which also drops
the twelve static forwarders Scala generated for inherited KeyBinding members.
The misspelling in ControlKeyModifer is the published type name and is kept;
only the file name, which said ControlKeyModifier.scala, is corrected.
120 plain-JVM tests and 21 Forge server tests pass, none of them changed by the
port.
Plain-JVM coverage is structural: the Java-facing isControlDown(EntityPlayer)
static that the reference scaladoc calls out as existing for Java callers, and
the typo in the class name, which is the published name.
Forge server coverage for the behavior. isControlDown branches on whether the
player's world is remote, so it needs a real player; a Forge FakePlayer on a
dedicated server world takes the server branch and reads the map the client
keeps in sync through packet 1. Unknown players default to not holding, the map
reflects the last recorded value, and clearing resets it.
The client branch reads isClientPressing instead and no harness has a client.
120 plain-JVM tests, 21 Forge server tests.
IScheduledPacketPart is descriptor-identical and schedulePacket is unchanged.
TScheduledPacketPart.read stays abstract: TMultiPart declares read, so a
default would be shadowed and a part would silently read a description instead
of a mask. The dispatch that lived in the $class helper is now the interface
static readMask, with the one-line forwarder documented on read.
writeScheduled and readScheduled became defaults. TMultiPart declares neither,
and empty bodies are what mixing the Scala trait in already gave a Scala
implementor -- only Java implementors ever had to write them out.
sendScheduled is public because the reference's private[multipart] reaches the
handler package and Java has no equivalent scope. An unsupported mask width now
throws IllegalArgumentException rather than scala.MatchError; both are
RuntimeException and the map is still left uncleared on that path, as before.
Dropped PacketScheduler$, TScheduledPacketPart$class and two closure classes,
none referenced in bytecode or by reflective name.
118 plain-JVM tests and 18 Forge server tests pass.
Plain-JVM coverage is structural: the shapes of IScheduledPacketPart and
TScheduledPacketPart, and the fact that read must stay abstract. TMultiPart
declares read, so a superclass method would beat any default the interface
tried to supply, and a part that does not declare it resolves to TMultiPart's,
which reads a description rather than a mask.
Forge server coverage for the behavior, which needs a part bound to a tile in a
non-remote world and a real write stream: masks OR-ing into one write, the map
clearing after a send, a part that lost its tile being skipped, all four
documented mask widths being accepted, and an undocumented one being rejected
rather than silently writing nothing.
117 plain-JVM tests, 18 Forge server tests.
The first port free to change shape rather than preserve it: TileCache has zero
downstream references in bytecode and does not appear in guidenh's reflective
names. map() became a java.util.Map, add/remove became void, and apply returns
a nullable FlaggedTile instead of a scala.Option. No internal caller used any
of the discarded return values.
Dropped TileCache$, TileCache$FlaggedTile$ and FlaggedTile's case-class
machinery. It is only ever a map value, never compared, hashed, printed or
destructured.
findTile is unchanged, including the branch the reference obscures: its
trailing 'case _ => null' is unreachable because Some(FlaggedTile(t, rem))
matches whatever the flag holds, so a tile flagged removed is returned like any
other and only the warning is suppressed. That is pinned by a functional test
written before the port.
114 plain-JVM tests and 13 Forge server tests pass; the only test change is the
two-line adapter that stopped unwrapping a scala.Option.
Plain-JVM coverage for the map half: add stores the tile unflagged, remove
replaces the entry rather than dropping it, entries are keyed by coordinate
value rather than identity, and clear empties everything.
Forge server coverage for findTile, which needs a world. It pins the branch
that is easy to misread: the removed flag only suppresses the warning, and a
tile flagged removed is still returned, because the Some pattern matches
regardless of the flag and the reference's trailing null case is unreachable.
The empty-cache path throws with a message starting 'DC: Client multipart @'.
114 plain-JVM tests, 13 Forge server tests.
The constant-pool scan sees the 20 names guidenh reflects on but not the
members it looks up on them. Read from the checked-out source at 6137525.
Adds three constraints the ABI diff cannot see: MultipartGenerator$.MODULE$ is
the only route to generateCompositeTile because it is private[multipart] and
gets no static forwarder; MicroblockGenerator$.create is matched by exact
parameter types with MicroblockClass's FQN string-compared; and
TileMultipart.partList_$eq is reflectively load-bearing despite having no
Java-facing equivalent.
Verified the last of those survived the TileMultipart port already on this
branch, along with loadParts, notifyTileChange and markRender.
MultipartHelper, MultipartHelper$ and MultipartHelper$IPartTileConverter are
all public-member- and descriptor-identical to the reference, and the full
emitted class list is unchanged -- this port neither adds nor removes a class.
MultipartHelper gains a private constructor and its companion's class
initializer loses ACC_PUBLIC, neither of which is nameable by a caller.
MultipartHelper$ was kept deliberately. It is not among the 17 MODULE$
singletons read from bytecode and no jar links against it, but guidenh names it
as a reflective string, and removing a companion breaks that invisibly.
createTileFromParts now builds one JavaConversions wrapper for both calls
instead of one per call site, and convertMulti returns Collections.emptyList or
singletonList instead of a Scala Seq wrapper. The declared type was always
java.lang.Iterable.
107 plain-JVM tests and 10 Forge server tests pass unchanged.
Plain-JVM coverage for the half that needs no world: the id guard on
createTileFromNBT and the whole of IPartTileConverter. MultipartSaveLoad
cannot class-initialize headless, which makes it a usable probe -- returning
null rather than raising a LinkageError is what proves the guard short-circuits
before the loadingWorld assignment.
Forge server coverage for the rest, none of which is reachable headless:
createTileFromParts runs the ASM generator, the NBT round trip needs the
save/load hooks, registerTileConverter appends to a Scala MutableList, and
sendDescPacket needs a loaded chunk.
Also pins the two static descriptors other jars link against and the
MultipartHelper/MultipartHelper$ names guidenh reflects on, which the ABI diff
cannot see.
107 plain-JVM tests, 10 Forge server tests.
TSlottedPart, IRandomDisplayTick, INeighborTileChange, TRandomUpdateTick,
ISidedHollowConnect and IMicroMaterialRender.
All six are member- and descriptor-identical to the reference. The seven
implementors and mixin tiles were diffed too and are unchanged member for
member. Five were already pure abstract interfaces in bytecode, so their
conversion has no bytecode consequence; exactly one class leaves the jar,
TRandomUpdateTick$class.
onWorldJoin cannot become a default: TMultiPart declares it, and a superclass
method beats an interface default, so the auto-registration would silently
never run. It stays abstract and implementors call TickScheduler.loadRandomTick
themselves, as RedstoneTorchPart already did.
No bridge was written for TRandomUpdateTick$class. No jar in the pack
references TRandomUpdateTick in any form, and the frozen baseline holds exactly
the eight helpers the inventory lists.
99 plain-JVM tests and 5 Forge server tests pass.