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.
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.
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 carry no implementation, so their
characterization is their shape: every member abstract and public, no
superinterface, and the exact member names.
Two of them extend TMultiPart in Scala source but emit a bare interface, and
IMicroMaterialRender is satisfied entirely by TMultiPart's Scala-style
world/x/y/z accessors, so the names are load-bearing.
TRandomUpdateTick's onWorldJoin auto-registration lives in its $class helper.
A part that does not declare onWorldJoin resolves it to TMultiPart's no-op;
that is pinned so the port cannot quietly turn it into an interface default,
which the superclass would shadow.
Gate step before converting the central tile. Covers the surface that does
not need a world: the part list is replaced rather than mutated so a
previously published Seq never changes under a caller, jPartList reflects
the current parts, operate skips parts whose tile has been cleared,
getLightValue is the maximum and zero when empty while
getExplosionResistance is the maximum but throws when empty, occlusionTest
needs agreement in both directions, canReplacePart excludes the outgoing
part but still rejects a part already present, and solidity falls back to
partMap being null on a bare tile.
One assumption was wrong and the test caught it: TMultiPart.doesTick
defaults to true, so a default part starts the tile ticking and a part has
to opt out to leave it idle. The case now records the real behavior.
Anything touching worldObj, the registry or packets stays out of this
layer because it cannot run headless; the Forge server suite covers that
side.
90 plain-JVM tests, all passing against the untouched Scala.
Splits the characterization across both layers, because part registration
needs FML's active mod container and throws without one, so it cannot run
headless at all.
Plain JVM covers what is reachable: converters are dispatched per block,
a converter returning null does not stop the scan, converters registered
for another block are never consulted, a converter claiming several blocks
is registered for each, and an unknown mod container throws rather than
returning null.
The Forge server suite covers registration itself, and deliberately
exercises both overloads downstream depends on. Content registers through
the deprecated IPartFactory array form that seven jars use, and each
microblock class registers through the IPartFactory2 varargs form that
ProjRed uses, so resolving mc_torch and mcr_face proves both paths. Part
ids also round trip through the shared IDWriter carrier.
78 plain-JVM tests and 5 server tests, all passing against the untouched
Scala.
Freezes the single trait default before the port: conductsRedstone returns
false, TEdgePart extends TSlottedPart, and the $class static holds the
default rather than dispatching, so an overriding part gets its own value
from a direct call but false from the bridge.
The member is abstract on the reference interface, so a Java class cannot
inherit it today; the test part mirrors the forwarder Scala emits instead,
which keeps the file compiling before and after the port.
Adds a frozen Scala 2.11.5 consumer compiled against the reference dev jar
at f2f1870. OpenComputers is the only consumer, and it links against
$init$ rather than conductsRedstone.
61 plain-JVM tests, all passing against the untouched Scala.
Freezes the placement logic before the port. getHitDepth projects the hit
vector onto the axis for that side and adds 1 on even sides, giving the
six frozen values, and it copies the vector rather than mutating the
caller's. onItemUse tries the clicked block first only when the depth is
below 1, then always tries the neighbour on that side, reusing and
mutating a single BlockCoord across both attempts.
Placement short-circuits as soon as newPart returns null, before any
world, player or stack access, so all of that is reachable headless with
null arguments. Real placement needs a world and stays on the functional
and manual layers.
Adds a frozen Scala 2.11.5 consumer compiled against the reference dev jar
at 7843f13, whose forwarders call all three statics. ProjRed is the only
consumer of this bridge.
57 plain-JVM tests, all passing against the untouched Scala.
Freezes what is reachable without a client: getBreakingIcon falls back to
getBrokenIcon for the same side and ignores its subPart argument, destroy
effects request sides 0 through 5 in order, and the scaleDensity overload
decides whether the part's own bounds are consulted at all. The particle
calls need a world and an EffectRenderer, so actual appearance stays on
the manual checklist.
Adds a frozen Scala 2.11.5 consumer compiled against the reference dev jar
at 57130a6. It exercises both bridges: getBreakingIcon proves
JIconHitEffects$class delegates to the fixture's getBrokenIcon, and
addDestroyEffects proves TIconHitEffects$class resolves by collecting all
six icons before failing on the null tile.
49 plain-JVM tests, all passing against the untouched Scala.
Freezes the trait defaults before the port: solid returns true for every
side including out-of-range values, and redstoneConductionMap returns 0.
Also freezes that TFacePart extends TSlottedPart, and that the $class
statics hold the defaults rather than dispatching, so an overriding part
gets its own values from a direct call but the trait values from the
bridge.
Both members are abstract on the reference interface, so a Java class
cannot inherit them today; the test part mirrors the forwarders Scala
emits instead, which keeps the file compiling before and after the port.
Adds a frozen Scala 2.11.5 consumer compiled against the reference dev jar
at a1bf3c8, whose forwarders call all three statics. OpenComputers,
ProjRed and ProjectBlue depend on them.
43 plain-JVM tests, all passing against the untouched Scala.
Extends the occlusion characterization to the parts of the area the port
will touch: the NormalOcclusionTest$ singleton and its MODULE$ field, the
TNormalOcclusion$class bridge, the super accessor the bridge calls back
through, NormallyOccludedPart's single-box constructor that AE2 links
against, and its null getType.
The generated codechicken$multipart$TNormalOcclusion$$super$occlusionTest
accessor is ACC_SYNTHETIC on the reference interface, so javac neither
sees nor allows implementing it. It is reached by reflection here, and the
port has to expose it as an ordinary method for any new implementor to be
able to satisfy it.
Adds a frozen Scala 2.11.5 consumer compiled against the reference dev jar
at cd6420f. It mixes in the trait without overriding occlusionTest, so
invoking it covers the whole bridge round trip: forwarder, $class,
singleton, and the callback through the accessor. ForgeRelocationFMP,
OpenComputers and ProjRed all depend on that path.
38 plain-JVM tests, all passing against the untouched Scala.
Freezes the observable behavior of TCuboidPart, JCuboidPart and the
TCuboidPart$class bridge before the Java port, per the test-first gate in
JAVA_MIGRATION.md. All cases pass against the untouched Scala.
Records two asymmetries worth preserving: getSubParts wraps the bounds in
an IndexedCuboid6, whose copy constructor makes it a copy carrying a boxed
Integer 0 as data, while getCollisionBoxes aliases the bounds instance
itself. Cuboid6 has no equals, so geometry is compared component-wise.
Also adds a frozen Scala consumer compiled with 2.11.5 against the
reference dev jar at 246daff. Its forwarders call all four $class statics,
so it detects the linkage break that a freshly recompiled test would miss.
ForgeRelocationFMP, OpenComputers, ProjRed and ProjectBlue all depend on
those descriptors.
One case exists specifically to guard the port: TMultiPart supplies empty
getSubParts and getCollisionBoxes, and on the JVM a superclass method beats
an interface default, so a Java interface cannot reproduce Scala trait
linearization on its own.
The downstream ABI inventory scanned 240 mod jars in GTNH daily
2026-08-14+678 and found no consumer of either bridge. Both were written
speculatively, before the inventory existed.
IDWriter had no downstream reference at all, so its four Scala function
accessors are removed and the class now selects a carrier width directly.
That also removes the Integer boxing and Function1/Function2 allocation
per read and write that the first port had preserved. Calling write or
read before setMax now throws IllegalStateException rather than
NullPointerException; no caller reaches that state.
JPartialOcclusion$class had no downstream reference either. The two
consumers that touch JPartialOcclusion only call getPartialOcclusionBoxes,
and the two jars that implement the interface emit no call to the helper.
The interface and both of its method descriptors are unchanged. The
ReferenceScalaPartialOcclusion fixture is removed with the helper it
existed to verify.
Validated with 27 plain-JVM tests, a clean checkstyleTest build with
Spotless re-enabled, and the Java 8 Forge dedicated-server suite.