# Java migration divergences Current intentional differences from the `1.7.12` reference. This is a compatibility ledger, not a port diary. An unchanged implementation needs no entry. Track remaining work in [migration status](README.md), client gaps in [manual release checks](MANUAL_CHECKS.md), and use the [performance protocol](MAINTAINERS.md#performance-validation) for measurements. Superseded decisions and per-port narratives remain in git history. “No audited consumer” means the frozen GTNH binary inventory and consumer-source audit, not every possible third-party mod. Removed members can still break unaudited consumers. Check `docs/migration/COMPATIBILITY.md`, which holds both the binary inventory and the consumer-source audit, before extending this list. ## Runtime and error behavior The multipart block intentionally retains the pre-PR #54 unlocalized name `tile.multipart`. PR #54 (`2d050bd`) renamed it to `tile.codechicken/multipart`; `cb3bef7` reverted that part to preserve the established name. The block registry ID remains `ForgeMultipart:block`. This is an intentional rollback of the later upstream rename, not a divergence from the `1.7.12` reference. | Area | Difference and consequence | | --- | --- | | Raw microblock recipe material lookup | Skips `MissingMicroMaterial`, whose recovery item is stone, so sawing stone selects the real stone material. Existing missing-material microblocks retain their recovery behavior. This fixes a pre-existing Scala bug, also fixed upstream in PR #61. | | `BlockMicroMaterial.icont()` after a texture stitch | `loadIcons` still builds icons eagerly, then marks them stale; the first `icont()` read after the stitch re-reads the block's icons once. Icons that another mod finishes in a later `TextureStitchEvent.Post` handler (Chisel's CTMLib `-v4` submaps) are picked up instead of rendering as missing or previous-stitch textures. Icons assigned through `icont_$eq` are kept. Upstream PR #63 targets the same bug by lowering the stitch handler to `EventPriority.LOW`; this port does not need that ordering. | | Client part additions | Reuses an existing multipart tile or creates one without converting the stale client block. Conversion and its outgoing packets remain server-side, avoiding duplicate parts and client disconnects. Matches upstream PR #62. | | `BlockMicroMaterial.createAndRegister(Block, Seq, String)` | Registers every supplied metadata value instead of only zero, including dark-oak logs and leaves and their legacy-name remaps. This fixes a pre-existing Scala bug; material IDs are rebuilt through the existing registry mapping. | | Chunk scheduled-tick restoration | Reactivates chunks with restored scheduled ticks so their callbacks resume at the saved deadlines. The Scala implementation restored entries without activating their chunk. NBT layout and duplicate-scheduling behavior are unchanged. | | World scheduler clock loading | Retains successfully loaded `schedTime` instead of overwriting it with total world time. Missing or unreadable files still fall back to world time. This fixes a pre-existing Scala bug and preserves the clock used by saved absolute tick deadlines; the file format is unchanged. | | Converted tile loading | Copies the source tile coordinates to a replacement produced by `MultipartSaveLoad.loadTiles` before assigning its world and validating it. The Scala implementation left converted replacements at their default coordinates. | | Placement-modifier player lifecycle | Removes cached control-key state when a player logs out and transfers it from the old player object to its replacement on clone. The Scala implementation retained entries until server shutdown. | | `IDWriter` before `setMax` | `read`/`write` throw `IllegalStateException` instead of `NullPointerException`. Uninitialized use is unsupported. | | `PacketScheduler` invalid `maskWidth` | Throws `IllegalArgumentException` instead of `scala.MatchError`. Valid widths remain 1, 2, 4 and 8. | | `NormalOcclusionTest.apply(Traversable, Traversable)` | Materializes both inputs into Java lists before testing instead of nested Scala `forall`. Finite, side-effect-free collections give the same result; eager traversal can change side effects, failure timing and termination for unusual inputs. | | `MultipartRenderer` dynamic-part guard | Uses short-circuit `\|\|` instead of `\|`; the second getter is no longer evaluated when the first operand succeeds. Ordinary getters give the same render decision. | | `ControlKeyModifer.map()` | The exposed Java map has no Scala default-value wrapper: an absent key returns null from `get`, not false from Scala `apply`. Use `isControlDown` for the unchanged absent-means-false behavior. | | Public companion constructors, including `ASMImplicits.ExtBitSet$` / `ExtClass$` | Explicit construction creates an independent instance instead of replacing `MODULE$`. Java initializes the retained final module field in its static initializer; use `MODULE$` for singleton identity. | | `registerJavaTrait` abstract inputs | Abstract Java classes are accepted instead of throwing `IllegalArgumentException`. Declared abstract methods become generated-interface contracts without helper bodies; a later mixin must implement them before those members can be invoked. A no-argument abstract-mixin constructor may call an argument-taking superclass constructor; registration discards that direct call and its argument evaluation because the composite invokes the real base constructor first. This enables Java microblock mixins without changing existing concrete registrations. | | `registerJavaTrait` members annotated `@SideOnly` | The old Java path ignored the annotation. It now omits opposite-side fields and methods from generated metadata and output, matching Forge and the Scala-trait path; visible and invisible annotation tables are both accepted. If the no-argument constructor is absent, including after Forge has already stripped it, an empty `$init$` preserves the generated initializer contract without running its body. Current-side members are unchanged. No audited Java registration relied on the old retention. | Concrete collection implementations also change without changing the declared `java.lang.Iterable`/`List` contracts: | Return path | Reference → Java | | --- | --- | | `JCuboidPart.getSubParts` / `getCollisionBoxes` | Scala `Seq` adapter → immutable `Collections.singletonList` | | `MultipartHelper.IPartTileConverter.convertMulti` default | Scala `Seq` adapter → `Collections.emptyList` / `singletonList` | | `Microblock.getDrops` | Java view of Scala `ListBuffer` → mutable `ArrayList` | Code depending on concrete collection classes or their exact mutation failure behavior must adapt. ## Removed or changed binary surface These removals were accepted because neither audit identified a consumer of the removed surface. This does not authorize removing other members of the same type. | Area | Removed or changed surface | | --- | --- | | `IDWriter` | Removed `write()`, `write_$eq(Function2)`, `read()` and `read_$eq(Function1)`. Use the Java methods listed below. | | Both registries | `MicroMaterialRegistry.readIDMap` and `MultiPartRegistry$.readIDMap` return `java.util.List` instead of `scala.collection.Seq`; the return descriptor changes. | | Registry compiler accessors | Removed `MicroMaterialRegistry`'s public mangled `$$idMap()`, `$$typeMap()` and `$$nameMap()` accessors, and the five `codechicken$multipart$MultiPartRegistry$$...()` accessors. Schematica's separate private `MultiPartRegistry$.$$typeMap` **field is retained** as a live Scala-map view; it is not one of the removed methods. | | `NormallyOccludedPart` | Removed the inferred `getType(): scala.runtime.Null$` overload; the `String` method remains. | | `MissingMicroMaterial` | Removed static `canRenderInPass(int)` and `isSolid()` forwarders. The companion no longer declares these methods but inherits the interface defaults. | | `TileMultipart` | Removed compiler accessor `protected$worldObj(TileMultipart)`. | | `BlockMultipart` | Removed `hasTileEntity$default$1()`; source callers must supply metadata explicitly. | | `TileCache` | `map()` now returns Java `Map`; `add`/`remove` return `void` instead of Scala `Option`; `apply(BlockCoord)` returns a nullable `FlaggedTile` instead of `Option`. | | `TileCache.FlaggedTile` | Dropped Scala case-class `copy`, copy-default getters, `canEqual`, value `equals`/`hashCode`/`toString`, product methods, and `scala.Product`/`scala.Serializable`. It now has ordinary object identity semantics; `t()` and `removed()` remain. | | `ControlKeyModifer` | Replaced `isClientPressing_$eq(boolean)` with `setClientPressing(boolean)`; `map()` returns Java `Map` instead of Scala mutable `Map`; removed `playerControlValue(EntityPlayer)` and its implicit wrapper type. | | `ControlKeyHandler` | Former static facade is now a `KeyBinding` subclass with `INSTANCE`. `wasPressed`/`tick` are instance methods, and the twelve inherited-KeyBinding static forwarders disappear. Initializing this client-only class now initializes its binding. | | `MultipartRenderer` | Removed inherited-TESR static forwarders `func_147498_b`, `func_147496_a` and `func_147497_a`. | Removed named helpers and companions (besides private compiler artifacts covered below): - `JPartialOcclusion$class`, including `allowCompleteOcclusion` and `$init$`. - `MicroMaterialRegistry$IMicroMaterial$class` and its four statics. - `TRandomUpdateTick$class`, including `onWorldJoin` and `$init$`. - `TScheduledPacketPart$class` and `PacketScheduler$`. - `IconHitEffects$`. - `TileCache$` and `TileCache$FlaggedTile$`. - `ControlKeyModifer$`, `ControlKeyModifer$ControlKeyValue` and `ControlKeyHandler$`. - `codechicken.multipart.package` / `package$` and `codechicken.microblock.package` / `package$`. Their logger aliases are replaced by the corresponding proxy's `logger()`. Generated-tile helpers removed only from the **raw artifact** are listed separately below; Forge recreates them. Other retained `$class` bridges and `MODULE$` companions are not covered by this removal decision. ## Additions and default-method changes | Area | Additive Java entry points | | --- | --- | | `IDWriter` | `write(MCDataOutput, int)` and `read(MCDataInput)` | | `JCuboidPart` | Static `subParts`, `collisionBoxes` and `renderBreaking` implementations for explicit forwarding | | `JItemMultiPart` | Static `hitDepth` and `onItemUse(part, ...)` implementations | | `MicroMaterialRegistry` | Static `setupIDMap`, `calcMaxCuttingStrength` and `loadIcons` forwarders | | `PacketScheduler` / `TScheduledPacketPart` | Static `sendScheduled()` on the facade and `readMask(part, packet)` on the interface | | `TileMultipart` | `hasDynamicParts` (false by default), `renderStatic`, `renderDynamic` and `randomDisplayTick` hooks for dispatching generated overrides through a stable superclass | | `MultipartMixinFactory` | Four additional static forwarders: `onCompiled`, `autoCompleteJavaTrait` and the two mangled `ASMMixinFactory` parent helpers. Scala emitted them when the base became Java with its original public JVM access; the Java facade retains them. Existing facade entries are unchanged. | | `MultipartEventHandler` / `MultipartEventHandler$` | Public `playerLoggedOut(PlayerLoggedOutEvent)` and `playerCloned(PlayerEvent.Clone)` event handlers for placement-modifier state cleanup and transfer | | `MultipartHelper` | `createPreviewTileFromNBT(World, NBTTagCompound)` builds an uninstalled client composite directly from saved multipart NBT and rejects unsupported parts as a whole | Previously abstract JVM interface methods now have Java defaults, allowing inheritance without Scala forwarders: | Interface | Default methods | | --- | --- | | `JPartialOcclusion` | `allowCompleteOcclusion` | | `TFacePart` | `solid`, `redstoneConductionMap` | | `JIconHitEffects` | `getBreakingIcon` | | `TItemMultiPart` | `getHitDepth` | | `TEdgePart` | `conductsRedstone` | | `Saw` | `getMaxCuttingStrength` | | `MicroMaterialRegistry.IMicroMaterial` | `loadIcons`, `canRenderInPass`, `isSolid` | | `TScheduledPacketPart` | `writeScheduled`, `readScheduled` | | `PlacementGrid` | `render`, `drawLines`, `glTransformFace` | | `MultiPartRegistry.IPartFactory2` | `createPartForClientPreview`; defaults to unsupported, while legacy Boolean/function adapters and built-in microblock factories construct their client variant | `ItemSaw` consequently no longer declares `getMaxCuttingStrength`; ordinary calls still resolve to the default, but declared-member reflection differs. The same distinction applies to the two `MissingMicroMaterial$` methods above. The `TNormalOcclusion` super accessor `codechicken$multipart$TNormalOcclusion$$super$occlusionTest(TMultiPart)` additionally loses `ACC_SYNTHETIC` and becomes a default returning true, so javac can see and implement it. `NormallyOccludedPart` inherits it rather than declaring its own. Existing Scala binaries retain their explicit super-chain overrides. ## Recompiling Scala consumers Binary descriptors alone do not preserve Scala source semantics after `ScalaSignature` metadata is removed. ### Trait composition A Java interface cannot supply a Scala trait's class supertype or its linearization. A superclass method wins over an interface default, so recompiling `extends TMultiPart with TCuboidPart` silently selects the empty superclass methods. Explicit forwarding is required where the implementation remains abstract: | Trait | Source migration | | --- | --- | | `TCuboidPart` | Extend `JCuboidPart`, or forward subparts, collision boxes and breaking rendering to its statics. | | `TNormalOcclusion` | Explicitly combine `NormalOcclusionTest.apply(this, other)` with the intended super chain. Audit existing `super.occlusionTest` calls too: they no longer route through the old trait. | | `TIconHitEffects` | Explicitly forward `addHitEffects` and `addDestroyEffects` to `IconHitEffects`. | | `TItemMultiPart` | Explicitly forward `onItemUse` to `JItemMultiPart`; the inherited Item method otherwise wins. | | `TRandomUpdateTick` | Explicitly implement `onWorldJoin` with `TickScheduler.loadRandomTick(this)`. | | `TScheduledPacketPart` | Explicitly forward `read` to `TScheduledPacketPart.readMask(this, packet)`. | | `ScratchBitSet` | Implement both methods and storage accessors, and initialize storage explicitly; the retained `$class` helper supplies the original implementations. | Previously compiled calls to **retained** `$class` bridges still link. This does not apply to the removed helpers above. ### Source syntax and generic signatures - An object used as a value now needs `X$.MODULE$` where that companion is retained (event handlers, proxies, recipes, packet handlers, materials and generators). Static calls alone do not supply a singleton value. - Object-application sugar becomes explicit `.apply(...)`, e.g. `NormalOcclusionTest.apply(...)` and `MicroblockPlacement$.MODULE$.apply(...)`. - `ASMImplicits` no longer provides Scala implicit/value-class syntax. Use `nodeName(clazz.getName)` and `ExtBitSet$.MODULE$.copy$extension(bits)` explicitly, or construct its retained Java wrappers. Existing boxed and `$extension` binary entry points remain; implicit conversion requires Scala metadata Java cannot emit. - Scala property assignment needs the explicit Java-authored `_$eq(...)` method, e.g. `renderID`, `loadingWorld`, `angelicaCompat` and microblock `shape`. - `ByteCodeReader.advance(length)(value)` becomes `advance(length, value)` when recompiling Scala. Argument evaluation remains eager, and previously compiled calls retain the same binary descriptor. - `ASMMixinFactory` constructor parameters and `construct` arguments need an explicit Scala `Seq`, not Scala varargs syntax. Overrides of `onCompiled` and `autoCompleteJavaTrait` must be public: Java preserves their original public JVM access without Scala's protected-source metadata. - `tile.partList(i)` becomes `tile.partList.apply(i)`. Static bounds getters on Face, Corner, Edge and Post factories need `aBounds()(index)` instead of `aBounds(index)`; Java no-argument methods may need explicit parentheses. - `TileMultipart.getOrConvertTile2` exposes `Tuple2` instead of a Scala-typed Boolean second element; source callers need a cast. Scala 2.11 callbacks to `operate(Function1<..., BoxedUnit>)` need an explicit `AbstractFunction1` instead of the previous Unit-returning lambda adaptation. - The Face, Corner, Edge, Post and Hollow factory **companions** widen `baseTrait` to `Class` from their specific trait type. Their static facades retain the specific generic type, and the raw JVM return descriptor remains `Class`. - `MultiPartRegistry.registerParts(IPartFactory, String[])` and the `IPartFactory2` array overload gain Java varargs flags. `BlockMultipart.addCollisionBoxesToList` uses raw `List` rather than `List` to override vanilla. Neither changes its raw JVM descriptor. ## Generated tile traits: raw artifact versus Forge runtime These ports replace Scala interfaces/helpers with concrete Java mixin inputs. Forge rewrites the inputs back into interfaces and generates implementation helpers. Existing binaries retain the runtime interface calls, but code recompiled against the **untransformed dev jar** may emit `invokevirtual` or `getfield` against a type that becomes an interface, causing verification/linkage failures. A transformed compile stub or explicit source guidance is required before claiming source-compatible downstream rebuilds. | Raw artifact change | Affected types | | --- | --- | | Interface + `$class` → concrete `TileMultipart` subclass | `TSlottedTile`, `TRedstoneTile`, `TTileChangeTile`, `TFluidHandlerTile`, `TileMultipartClient` | | Interface + `$class` → concrete child mixin input | `TRandomDisplayTickTile` (extends the registered `TileMultipartClient` input) | | Removed raw `TIInventoryTile$class`; moved implementation into `JInventoryTile` input | `TIInventoryTile` itself remains a Java interface. Source consumers should target it, not the transformed `JInventoryTile` class. | Inherited/foreign-field and callback workarounds add package-private access shims for redstone, tile-change, fluid, inventory and both client traits; they are not new consumer APIs. Transformer constraints and shim recipes are documented once in the handoff. The client-trait port also deliberately extends the Java-trait transformer: - Registered Java traits may extend an already registered Java trait; the parent runtime interface participates in hierarchy and linearization. - Explicit field accessors are recognized rather than duplicated. - Transient input fields are excluded from automatic `copyFrom`. Client render caches use this opt-out; their generated runtime fields remain ordinary private fields. ## Shared classfile differences These are accepted once for the migration, not repeated for every otherwise equivalent port: - Java-authored types lose Scala signature/marker attributes and use Java source-file/debug metadata. Static facades and helper classes gain private constructors where Scala emitted none. Java class initializers lack Scala's `ACC_PUBLIC`, and singleton publication follows Java initialization rather than Scala's constructor assignment. - Unreferenced Scala `$$anonfun$`/anonymous implementation classes and private compiler accessors disappear or are replaced by Java helpers/lambdas. Their names are not supported API. This does **not** include named compatibility bridges or reflective fields: audit those individually. Retained Scala traits still emit their required helpers. - The `Object[]` renderer bridges on `ItemMicroPartRenderer$` and `ItemSawRenderer$` are ordinary Java methods rather than synthetic Scala methods; signatures and dispatch remain available. - The private scratch `ThreadLocal` fields on `MicroblockGenerator$` and `MultipartGenerator$` lose `ACC_FINAL` so their public Scala-trait setters can still replace storage. - The private `ItemSaw.harvestLevel` field loses `ACC_FINAL` so `setHarvestLevel(int)` can update the same storage as the retained getter and old Iguana reflection. Its name, type and visibility remain unchanged. - TickScheduler's private Scala scheduler types become package-private static nested Java classes, retaining their binary names. ## Updating this ledger Add only a new effective runtime, binary, source or classfile difference, with its consequence and reason for acceptance. Amend an existing row when a later change supersedes it. Do not add passing test counts, unchanged behavior, build observations, performance claims or a “no divergence” section for each new port.