From 5995e366b7d0fb0d247c8f8e79527c4b3b39ce3f Mon Sep 17 00:00:00 2001 From: Algent Date: Fri, 4 Sep 2026 22:53:52 +0200 Subject: [PATCH] fix: preserve material property dispatch --- JAVA_MIGRATION_HANDOFF.md | 8 +- docs/migration/HISTORY.md | 16 ++ .../microblock/BlockMicroMaterial.java | 38 +++-- ...lockMicroMaterialCharacterizationTest.java | 149 ++++++++++++++++++ 4 files changed, 190 insertions(+), 21 deletions(-) diff --git a/JAVA_MIGRATION_HANDOFF.md b/JAVA_MIGRATION_HANDOFF.md index 21a5faf..58ca809 100644 --- a/JAVA_MIGRATION_HANDOFF.md +++ b/JAVA_MIGRATION_HANDOFF.md @@ -16,7 +16,7 @@ migration checkout; `codex/tile-compatibility-fixes` was deleted after its fixes ## Current state and next target -**526 plain-JVM tests and 237 Java 8 Forge tests pass, with zero failures/errors/skips.** Sources total **224 Java +**530 plain-JVM tests and 237 Java 8 Forge tests pass, with zero failures/errors/skips.** Sources total **224 Java files and 9 Scala files / 747 nonblank Scala lines**. The packaged inventory has 443 classes. Review follow-up: restored packet-scheduler callback mutation behavior with the original Scala hash-map traversal, @@ -24,6 +24,12 @@ virtual tile accessor dispatch throughout `TMultiPart`, and null-safe equality f Six JVM and two Forge regression cases cover the fixes. Callable signatures remain unchanged; the packet traversal callback adds one anonymous class. See the latest history entries for the individual fixes and validation. +Accessor review fixes: `TileMultipart`, `Microblock` and `BlockMicroMaterial` now use their virtual state accessors +throughout the restored paths. Eleven new regression cases pass against the original Scala jar and the fixed port; +all 519 archived consumers pass with their recorded version. The supplied consumer checkouts confirm extension/API +use, but no override triggering these three regressions was found. All 443 class/member APIs, 17 ScalaSignature +payloads and 116 generated dumps are retained. Plan adjustments remain deferred for discussion after these fixes. + Latest bounded target: `ScalaSignature.MethodSymbol.info` delegates info-ID evaluation to `ScalaSignatureParser.methodSymbolInfo`. Seven characterization tests were committed first as `9859aa8`. They pin infoId/eval order, repeated uncached reads, exact and null results, the result cast, failures, unused symbol/type members diff --git a/docs/migration/HISTORY.md b/docs/migration/HISTORY.md index 269893c..ba40fae 100644 --- a/docs/migration/HISTORY.md +++ b/docs/migration/HISTORY.md @@ -1708,3 +1708,19 @@ differences belong in the [divergence ledger](../../JAVA_MIGRATION_DIVERGENCES.m - Build/style checks, 526 JVM tests and 237 Java 8 Forge tests pass. ProjectRed extends `Microblock`, and consumers use its shape/material accessors, but no supplied consumer overriding those accessors was found. Evidence is under ignored `run/conversion-review/`; no migration-plan changes were made. + +### 2026-09-04 — BlockMicroMaterial property accessor compatibility + +- Restored virtual block, metadata, icon-transform and registry-key reads across material properties, items, + strength, icon loading and rendering. Preserved the constructor's direct argument use, private reflective fields, + client-only annotations, safe-icon exception boundary and original render/accessor evaluation order. +- Four new cases compiled against the original Scala jar pass there, fail on the unfixed port and pass unchanged + after the fix. Together the three fixes add eleven JVM cases. Build/style checks, 530 JVM tests, 237 Java 8 Forge + tests and all 14 assertions in the original-jar accessor probe pass. All 519 archived JVM consumers also pass + when built with their recorded version; their two embedded-version assertions require that override. +- Compared against the pre-fix dev jar: all 443 class/member APIs, all 17 ScalaSignature payloads and 3,702 unrelated + method bodies are unchanged, allowing only the build-version literal to differ. The fixes change 44 method bodies; + all 116 generated tile dumps are identical. Both jars contain 443 Java 8 classes and five matching mod versions. +- ProjectRed, Chisel and ExtraUtilities extend `BlockMicroMaterial`; no supplied consumer overrides the affected + accessors. These are verified extension-contract regressions, without a demonstrated current-pack gameplay + failure. Evidence is under ignored `run/conversion-review/`; migration-plan changes remain deferred. diff --git a/src/main/scala/codechicken/microblock/BlockMicroMaterial.java b/src/main/scala/codechicken/microblock/BlockMicroMaterial.java index 3ef125d..f2b18d0 100644 --- a/src/main/scala/codechicken/microblock/BlockMicroMaterial.java +++ b/src/main/scala/codechicken/microblock/BlockMicroMaterial.java @@ -10,6 +10,7 @@ import net.minecraft.util.IIcon; import codechicken.lib.render.CCRenderState; import codechicken.lib.render.uv.MultiIconTransformation; +import codechicken.lib.vec.BlockCoord; import codechicken.lib.vec.Cuboid6; import codechicken.lib.vec.Vector3; import codechicken.microblock.MicroMaterialRegistry.IMicroMaterial; @@ -106,7 +107,7 @@ public class BlockMicroMaterial implements IMicroMaterial { @Override @SideOnly(Side.CLIENT) public void loadIcons() { - Block currentBlock = Block.getBlockFromName(blockKey); + Block currentBlock = Block.getBlockFromName(blockKey()); IIcon[] icons = new IIcon[6]; for (int side = 0; side < icons.length; side++) { icons[side] = codechicken$microblock$BlockMicroMaterial$$safeIcon$1(currentBlock, side); @@ -116,7 +117,7 @@ public class BlockMicroMaterial implements IMicroMaterial { public final IIcon codechicken$microblock$BlockMicroMaterial$$safeIcon$1(Block block, int side) { try { - return MicroblockProxy.renderBlocks().getIconSafe(block.getIcon(side, meta)); + return MicroblockProxy.renderBlocks().getIconSafe(block.getIcon(side, meta())); } catch (Exception ignored) { return MicroblockProxy.renderBlocks().getIconSafe(null); } @@ -124,36 +125,33 @@ public class BlockMicroMaterial implements IMicroMaterial { @Override public void renderMicroFace(Vector3 pos, int pass, Cuboid6 bounds) { - MaterialRenderHelper$.MODULE$.start(pos, pass, icont).blockColour(getColour(pass)).lighting() - .blockAndMeta(block, meta).render(); + MaterialRenderHelper$.MODULE$.start(pos, pass, icont()).blockColour(getColour(pass)).lighting() + .blockAndMeta(block(), meta()).render(); } public int getColour(int pass) { if (pass == -1) { - return (block.getBlockColor() << 8) | 0xFF; + return (block().getBlockColor() << 8) | 0xFF; } CCRenderState state = CCRenderState.instance(); - return (block.colorMultiplier( - state.lightMatrix.access, - state.lightMatrix.pos.x, - state.lightMatrix.pos.y, - state.lightMatrix.pos.z) << 8) | 0xFF; + BlockCoord pos = state.lightMatrix.pos; + return (block().colorMultiplier(state.lightMatrix.access, pos.x, pos.y, pos.z) << 8) | 0xFF; } @Override public boolean canRenderInPass(int pass) { - return block.canRenderInPass(pass); + return block().canRenderInPass(pass); } @Override @SideOnly(Side.CLIENT) public IIcon getBreakingIcon(int side) { - return block.getIcon(side, meta); + return block().getIcon(side, meta()); } @Override public ItemStack getItem() { - return new ItemStack(block, 1, meta); + return new ItemStack(block(), 1, meta()); } @Override @@ -165,19 +163,19 @@ public class BlockMicroMaterial implements IMicroMaterial { public float getStrength(EntityPlayer player) { float hardness = 30F; try { - hardness = block.getBlockHardness(null, 0, 0, 0); + hardness = block().getBlockHardness(null, 0, 0, 0); } catch (Exception ignored) {} - return player.getBreakSpeed(block, false, meta % 16, 0, -1, 0) / hardness; + return player.getBreakSpeed(block(), false, meta() % 16, 0, -1, 0) / hardness; } @Override public boolean isTransparent() { - return !block.isOpaqueCube(); + return !block().isOpaqueCube(); } @Override public int getLightValue() { - return block.getLightValue(); + return block().getLightValue(); } public Seq toolClasses() { @@ -186,16 +184,16 @@ public class BlockMicroMaterial implements IMicroMaterial { @Override public int getCutterStrength() { - return block.getHarvestLevel(meta % 16); + return block().getHarvestLevel(meta() % 16); } @Override public Block.SoundType getSound() { - return block.stepSound; + return block().stepSound; } @Override public float explosionResistance(Entity entity) { - return block.getExplosionResistance(entity); + return block().getExplosionResistance(entity); } } diff --git a/src/test/java/codechicken/microblock/BlockMicroMaterialCharacterizationTest.java b/src/test/java/codechicken/microblock/BlockMicroMaterialCharacterizationTest.java index f57871e..8517a9d 100644 --- a/src/test/java/codechicken/microblock/BlockMicroMaterialCharacterizationTest.java +++ b/src/test/java/codechicken/microblock/BlockMicroMaterialCharacterizationTest.java @@ -5,11 +5,13 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; 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 static org.junit.jupiter.api.Assertions.assertTrue; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -20,6 +22,8 @@ import java.util.concurrent.atomic.AtomicInteger; import net.minecraft.block.Block; import net.minecraft.block.material.Material; import net.minecraft.init.Blocks; +import net.minecraft.util.IIcon; +import net.minecraft.world.IBlockAccess; import org.junit.jupiter.api.Test; import org.objectweb.asm.Type; @@ -221,6 +225,151 @@ class BlockMicroMaterialCharacterizationTest { assertTrue(operations.stream().anyMatch(ColourMultiplier.class::isInstance)); } + @Test + void overriddenBlockAndMetadataDriveMaterialProperties() { + Block block = new Block(Material.rock) { + + @Override + public int getHarvestLevel(int metadata) { + return metadata; + } + + @Override + public int colorMultiplier(IBlockAccess world, int x, int y, int z) { + return 0x123456; + } + }; + block.setLightLevel(0.8f); + block.setResistance(12); + List reads = new ArrayList<>(); + BlockMicroMaterial material = new BlockMicroMaterial(null, 0) { + + @Override + public Block block() { + reads.add("block"); + return block; + } + + @Override + public int meta() { + reads.add("meta"); + return 23; + } + }; + assertTrue(reads.isEmpty(), "Construction uses the argument before subclass state is initialized"); + assertEquals(block.getLightValue(), material.getLightValue()); + assertEquals(!block.isOpaqueCube(), material.isTransparent()); + assertEquals(7, material.getCutterStrength()); + assertSame(block.stepSound, material.getSound()); + assertEquals(block.getExplosionResistance(null), material.explosionResistance(null)); + assertEquals(block.canRenderInPass(1), material.canRenderInPass(1)); + assertEquals((block.getBlockColor() << 8) | 0xFF, material.getColour(-1)); + assertEquals(0x123456FF, material.getColour(0)); + reads.clear(); + assertNotNull(material.getItem()); + assertEquals(Arrays.asList("block", "meta"), reads); + reads.clear(); + assertThrows(NullPointerException.class, () -> material.getStrength(null)); + assertEquals(Arrays.asList("block", "block", "meta"), reads); + } + + @Test + void overriddenMetadataReachesBreakingAndSafeIcons() { + IIcon icon = (IIcon) Proxy.newProxyInstance( + IIcon.class.getClassLoader(), + new Class[] { IIcon.class }, + (proxy, method, arguments) -> { throw new AssertionError(method); }); + List reads = new ArrayList<>(); + Block block = new Block(Material.rock) { + + @Override + public IIcon getIcon(int side, int metadata) { + reads.add(side); + reads.add(metadata); + return icon; + } + }; + BlockMicroMaterial material = new BlockMicroMaterial(null, 0) { + + @Override + public Block block() { + return block; + } + + @Override + public int meta() { + return 23; + } + }; + assertSame(icon, material.getBreakingIcon(2)); + assertSame(icon, material.codechicken$microblock$BlockMicroMaterial$$safeIcon$1(block, 4)); + assertEquals(Arrays.asList(2, 23, 4, 23), reads); + } + + @Test + void overriddenBlockKeyIsReadBeforeLoadingIcons() { + IllegalStateException failure = new IllegalStateException("block key unavailable"); + BlockMicroMaterial material = new BlockMicroMaterial(null, 0) { + + @Override + public String blockKey() { + throw failure; + } + }; + assertSame(failure, assertThrows(IllegalStateException.class, material::loadIcons)); + } + + @Test + void overriddenIconsAndBlockPropertiesReachTheRenderPipeline() throws Exception { + MultiIconTransformation icons = new MultiIconTransformation((IIcon) null); + List reads = new ArrayList<>(); + BlockMicroMaterial material = new BlockMicroMaterial(null, 0) { + + @Override + public MultiIconTransformation icont() { + reads.add("icons"); + return icons; + } + + @Override + public int getColour(int pass) { + reads.add("colour"); + return 0x89ABCDEF; + } + + @Override + public Block block() { + reads.add("block"); + return null; + } + + @Override + public int meta() { + reads.add("meta"); + return 23; + } + }; + assertTrue(reads.isEmpty()); + CCRenderState state = CCRenderState.instance(); + state.resetInstance(); + BlockFace face = new BlockFace(); + face.side = 1; + state.model = face; + state.firstVertexIndex = 0; + state.lastVertexIndex = 0; + try { + material.renderMicroFace(new Vector3(), -1, Cuboid6.full); + assertEquals(Arrays.asList("icons", "colour", "block", "meta"), reads); + Field operationsField = CCRenderPipeline.class.getDeclaredField("ops"); + operationsField.setAccessible(true); + assertTrue(((List) operationsField.get(state.pipeline)).contains(icons)); + } finally { + state.resetInstance(); + MaterialRenderHelper.pass_$eq(0); + MaterialRenderHelper.builder_$eq(null); + } + } + private static void assertClass(Class type, Class superclass, boolean isFinal, Set methods) { assertTrue(Modifier.isPublic(type.getModifiers())); assertEquals(isFinal, Modifier.isFinal(type.getModifiers()));