fix: restore Schematica registry reflection compatibility

This commit is contained in:
Algent committed 2026-08-27 14:02:47 +03:00
1 parent 8d0ba7c76e
commit 49ed6fd751
7 files changed
+101 -21

No files matched your search

+16 -6
View File
@@ -27,10 +27,10 @@ The binary and source-level consumer audits are complete. Phase 0 still lacks br
pre-optimization profile. The low-coupling Phase 3 queue is complete; before entering measured hot-path or generated
tile work, the immediate milestone is the audit-derived compatibility gate below.
That audit found one existing regression on this branch: Schematica 1.12.6 reflects the original private
That audit found and this branch has now repaired one existing regression: Schematica 1.12.6 reflects the original private
`MultiPartRegistry$` field `codechicken$multipart$MultiPartRegistry$$typeMap` and casts it to a Scala mutable map. The
Java registry port moved the state to a private Java `HashMap` on `MultiPartRegistry`, so Schematica's integration
currently disables itself. Restoring and testing that compatibility view is the next task.
companion again exposes that exact private field as a live view of the canonical Java `HashMap`, and a test performs
Schematica's reflective lookup. The remaining source-only member guards are next.
Phase 1 has not properly started: GTNHLib is present but was added at `api` scope to satisfy a test compile rather
than because a migration change needed it, and no Forge mod dependency was declared. Revisit that entry only when a
@@ -297,7 +297,7 @@ Exit condition: leaf code is Java and no longer generates avoidable Scala closur
Do this before another source conversion:
- [ ] Restore Schematica's reflective `MultiPartRegistry$` type-map view with its exact field name and Scala mutable-map
- [x] Restore Schematica's reflective `MultiPartRegistry$` type-map view with its exact field name and Scala mutable-map
shape, backed by the canonical Java registry state; add a regression test that performs Schematica's actual lookup.
- [ ] Add exact member-level guards for GuideNH's `BlockMicroMaterial.block/meta` mixin fields, Et Futurum's mutable
`ButtonPart.metaSideMap/sideMetaMap`, Iguana's `ItemSaw.harvestLevel`, and Galacticraft's name-only
@@ -393,7 +393,7 @@ The work should move from low to high risk while keeping a buildable mixed-langu
Extensive tests are both possible and recommended here. Their purpose is not to prove that the current Scala behavior is ideal; it is to make its observable behavior explicit before translation, so every later difference is intentional.
The repository now has a Java-8-compatible JUnit Jupiter suite and a small Forge integration runner. The current
baseline is 125 plain-JVM tests and 22 Forge server tests. Minecraft 1.7.10 does not provide the modern GameTest
baseline is 126 plain-JVM tests and 22 Forge server tests. Minecraft 1.7.10 does not provide the modern GameTest
framework, so game-dependent coverage continues through the existing narrow Forge harness rather than a second test
framework.
@@ -476,7 +476,7 @@ The old pre-audit calendar ranges are retired. They mixed the Java source port,
work, and optional Scala-runtime removal into estimates that are no longer useful. Track the remaining work by
completed compatibility gates instead:
1. Repair the Schematica registry view and finish the source-only reflection/member guards.
1. Finish the source-only reflection/member guards; the Schematica registry view is repaired.
2. Freeze tile ordering, lifecycle, NBT, packet, and generator behavior with compact representative fixtures.
3. Capture the first focused CPU/allocation baseline and port the complete redstone unit.
4. Convert a representative generated tile-trait group, then the remaining trait groups.
@@ -594,3 +594,13 @@ generated-trait compatibility work. Scala-runtime removal remains a later projec
- Dropped the three static forwarders Scala generated for inherited `TileEntitySpecialRenderer` members, the same category as the twelve `KeyBinding` forwarders dropped from `ControlKeyHandler`.
- Dropped an unused `ThreadSafeISBRH` import. It annotated nothing and appears nowhere else; if the intent was to mark this ISBRH thread-safe for Angelica then the annotation was never applied in the reference either, which is worth checking independently of the migration.
- Recorded that this port adds nothing to the automated suites and everything to the manual checklist. Static and dynamic rendering, the breaking overlay and the render id all need a client.
### 2026-08-27
- Reproduced Schematica 1.12.6's exact `ReflectionHelper` lookup of the private
`MultiPartRegistry$.codechicken$multipart$MultiPartRegistry$$typeMap` field. The test failed against the existing Java
port with `UnableToFindFieldException`, matching Schematica's silent integration-disable path.
- Restored the exact private field name and `scala.collection.mutable.Map` descriptor as Scala's live wrapper over the
canonical Java registry map. The wrapper adds no copied state or synchronization path.
- Proved through the reflected Scala view that Schematica can resolve a factory and that the Java registry immediately
sees the same entry. The focused registry suite and all 126 plain-JVM tests pass.
+3 -3
View File
@@ -351,9 +351,9 @@ effective public API**. A Java `Map` replacement will silently disable Schematic
is retained or Schematica is patched and deployed first. This is a higher-risk constraint than the constant-pool
inventory alone shows.
**Current branch:** this is already broken by the Java `MultiPartRegistry` port. The canonical state is now a private
Java `HashMap` on `MultiPartRegistry`, while `MultiPartRegistry$` has no field with the reflected name. Restoring and
testing a compatibility view is the immediate migration blocker.
**Current branch:** the Java port initially broke this lookup. `MultiPartRegistry$` now retains the exact private field
as a Scala mutable-map wrapper backed by the canonical Java `HashMap`, and a regression test performs Schematica's
lookup and verifies that both views resolve the same factory.
### UtilitiesInExcess — future replacement, already a substantial consumer
+15 -4
View File
@@ -573,16 +573,18 @@ winning, registration still refuses to run once the registry is closed and still
duplicate part id still throws, and `writePartID` and `getModContainer` still throw rather than returning null for an
unknown name.
### Known reflective regression, discovered 2026-08-27
### Resolved reflective regression, discovered and fixed 2026-08-27
The full consumer-source audit found a constraint that the bytecode scan missed. Schematica 1.12.6 obtains the private
field `codechicken$multipart$MultiPartRegistry$$typeMap` from `MultiPartRegistry$` and casts its value to
`scala.collection.mutable.Map`. The Java port moved the canonical map to a private `java.util.HashMap` on
`MultiPartRegistry`, and `MultiPartRegistry$` exposes no compatibility field, so Schematica currently disables its FMP
`MultiPartRegistry`, and `MultiPartRegistry$` exposed no compatibility field, so Schematica disabled its FMP
integration during reflective initialization.
This is not an accepted divergence. Restore an exact reflective compatibility view backed by the canonical Java
registry state and add a test reproducing Schematica's field lookup before continuing the migration.
`MultiPartRegistry$` now restores that exact private field and descriptor using Scala's existing live Java-map wrapper;
the Java map remains the single source of truth. `MultiPartRegistryCharacterizationTest` performs Schematica's actual
`ReflectionHelper` lookup, casts the result to `scala.collection.mutable.Map`, and proves mutations reach the Java
registry. No separate registry or synchronization path was added.
### Preserved Scala-typed descriptors
@@ -627,6 +629,15 @@ overloads build.
run, and the suite resolves a part registered through each.
- Error paths that log remain uncovered headless, as before, because the multipart logger is null until preInit.
### 2026-08-27 compatibility repair validation
- The new Schematica reflection case failed first with `UnableToFindFieldException`, then passed with the compatibility
field present.
- `javap -private -s` confirms the field is private/final with descriptor `Lscala/collection/mutable/Map;`.
- `MultiPartRegistryCharacterizationTest`: 6 tests, 0 failures, 0 errors.
- Complete plain-JVM suite: 126 tests, 0 failures, 0 errors.
- Clean `spotlessApply checkstyleTest build`: passing.
## 2026-08-14 — TileMultipart Java port
The central tile. Twelve jars reference it and about twenty-five of its members. `TileMultipartClient` was split into
+6 -6
View File
@@ -57,7 +57,7 @@ awk -F'"' '/<testsuite /{t+=$4;f+=$8;e+=$10} END{print "tests="t" failures="f" e
grep -o 'tests="[0-9]*" skipped="[0-9]*" failures="[0-9]*" errors="[0-9]*"' run/server/junit-out/TEST-*.xml
```
Current baseline: **125 plain-JVM tests, 22 Forge server tests, all passing.**
Current baseline: **126 plain-JVM tests, 22 Forge server tests, all passing at their last completed runs.**
### ABI diff against the reference
@@ -192,12 +192,12 @@ Both `package.scala` objects are gone, removed rather than ported. `MultipartRen
**The low-risk queue is empty.** Pick the next piece deliberately rather than off the top of a list.
**Immediate blocker: repair the already-ported registry for Schematica.** Schematica 1.12.6 reflects
`MultiPartRegistry$.codechicken$multipart$MultiPartRegistry$$typeMap` and casts it to a Scala mutable map. The current
Java port has only a private Java `HashMap` on `MultiPartRegistry`, so that integration is broken. Restore an exact
compatibility view backed by the canonical Java state and freeze Schematica's lookup in a test before any new port.
**The Schematica registry blocker is repaired.** `MultiPartRegistry$` again has the exact private field
`codechicken$multipart$MultiPartRegistry$$typeMap` with a Scala mutable-map descriptor. It is a live wrapper over the
canonical Java map, and `MultiPartRegistryCharacterizationTest` reproduces Schematica's reflective lookup and proves
both views reach the same factory.
Then close the small audit-derived test gap: exact reflection/mixin fields, tile list/order/move lifecycle, compact
Next close the remaining audit-derived test gap: exact reflection/mixin fields, tile list/order/move lifecycle, compact
mixed NBT/packet fixtures, and representative generated-trait/pass-through fixtures. The checklist is in the
"Immediate compatibility gate" section of `JAVA_MIGRATION.md`.
@@ -14,6 +14,7 @@ import codechicken.multipart.MultiPartRegistry.IPartConverter;
import codechicken.multipart.MultiPartRegistry.IPartFactory;
import codechicken.multipart.MultiPartRegistry.IPartFactory2;
import cpw.mods.fml.common.ModContainer;
import scala.collection.JavaConversions;
/**
* Scala companion singleton. Retained because compiled Scala consumers read MODULE$ and call these instance methods.
@@ -22,6 +23,11 @@ public final class MultiPartRegistry$ {
public static final MultiPartRegistry$ MODULE$ = new MultiPartRegistry$();
/** Retained with its original name and Scala type because Schematica reflects it directly. */
@SuppressWarnings("unused")
private final scala.collection.mutable.Map<String, IPartFactory2> codechicken$multipart$MultiPartRegistry$$typeMap = JavaConversions
.mapAsScalaMap(MultiPartRegistry.typeMapBacking());
private MultiPartRegistry$() {}
@Deprecated
@@ -87,6 +87,10 @@ public final class MultiPartRegistry {
private MultiPartRegistry() {}
static Map<String, IPartFactory2> typeMapBacking() {
return typeMap;
}
/**
* Register a part factory with an array of types it is capable of instantiating. Must be called before postInit.
*
@@ -6,6 +6,7 @@ import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -13,17 +14,22 @@ import java.util.NoSuchElementException;
import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.world.World;
import org.junit.jupiter.api.Test;
import codechicken.lib.data.MCDataInput;
import codechicken.lib.vec.BlockCoord;
import codechicken.multipart.MultiPartRegistry.IPartConverter;
import codechicken.multipart.MultiPartRegistry.IPartFactory2;
import cpw.mods.fml.relauncher.ReflectionHelper;
import scala.Option;
/**
* Part registration needs FML's active mod container, so it cannot run headless; that half of the registry is
* characterized in the Forge server suite instead. Converter registration and dispatch touch neither FML nor the
* logger, so they are covered here.
* characterized in the Forge server suite instead. Converter dispatch and the Schematica reflection contract touch
* neither FML nor the logger, so they are covered here.
*/
class MultiPartRegistryCharacterizationTest {
@@ -37,6 +43,30 @@ class MultiPartRegistryCharacterizationTest {
assertThrows(NoSuchElementException.class, () -> MultiPartRegistry.getModContainer("test:nosuchpart"));
}
@Test
@SuppressWarnings("unchecked")
void schematicaCanReadTheLiveScalaTypeMap() throws IllegalAccessException {
Field moduleField = ReflectionHelper.findField(MultiPartRegistry$.class, "MODULE$");
Field typeMapField = ReflectionHelper
.findField(MultiPartRegistry$.class, "codechicken$multipart$MultiPartRegistry$$typeMap");
Object module = moduleField.get(MultiPartRegistry$.class);
scala.collection.mutable.Map<String, Object> reflectedTypeMap = (scala.collection.mutable.Map<String, Object>) typeMapField
.get(module);
String type = "test:schematica_reflection";
TestPart part = new TestPart();
TestFactory factory = new TestFactory(part);
reflectedTypeMap.put(type, factory);
try {
Option<Object> reflectedFactory = reflectedTypeMap.get(type);
assertFalse(reflectedFactory.isEmpty());
assertSame(factory, reflectedFactory.get());
assertSame(part, MultiPartRegistry.loadPart(type, new NBTTagCompound()));
} finally {
reflectedTypeMap.remove(type);
}
}
@Test
void convertersAreDispatchedPerBlockAndFirstNonNullWins() {
Block block = new Block(Material.rock) {};
@@ -110,4 +140,23 @@ class MultiPartRegistryCharacterizationTest {
return "test:converted";
}
}
private static final class TestFactory implements IPartFactory2 {
private final TMultiPart part;
private TestFactory(TMultiPart part) {
this.part = part;
}
@Override
public TMultiPart createPart(String name, NBTTagCompound nbt) {
return part;
}
@Override
public TMultiPart createPart(String name, MCDataInput packet) {
return part;
}
}
}