test: characterize tile change trait

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.
This commit is contained in:
Algent committed 2026-08-28 18:35:22 +03:00
1 parent bde3445488
commit 003e8f94ca
2 files changed
+221

No files matched your search

@@ -39,6 +39,7 @@ import codechicken.multipart.TMultiPart;
import codechicken.multipart.TileMultipart;
import codechicken.multipart.asm.MultipartMixinFactory;
import codechicken.multipart.scalatraits.TSlottedTile;
import codechicken.multipart.scalatraits.TTileChangeTile;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.Loader;
import scala.collection.JavaConversions;
@@ -214,6 +215,52 @@ class ForgeEnvironmentSmokeTest {
assertSame(replacement, trait.getMethod("v_partMap").invoke(first));
}
@Test
void generatesAndCachesTileChangeTileClass() throws Exception {
int traitId = MultipartMixinFactory.getId(TTileChangeTile.class.getName().replace('.', '/'));
assertFalse(traitId < 0);
BitSet traits = new BitSet();
traits.set(traitId);
Object first = MultipartMixinFactory.construct(traits, Nil$.MODULE$);
Object second = MultipartMixinFactory.construct(traits, Nil$.MODULE$);
Class<?> trait = Class.forName("codechicken.multipart.scalatraits.TTileChangeTile");
assertTrue(first instanceof TileMultipart);
assertTrue(first instanceof TTileChangeTile);
assertTrue(trait.isInterface());
assertNotSame(first, second);
assertSame(first.getClass(), second.getClass());
assertEquals(0, trait.getDeclaredFields().length);
Set<String> signatures = new TreeSet<>();
for (java.lang.reflect.Method method : trait.getDeclaredMethods()) {
signatures.add(method.getName() + org.objectweb.asm.Type.getMethodDescriptor(method));
}
String prefix = "codechicken$multipart$scalatraits$TTileChangeTile$$super$";
assertEquals(
new TreeSet<>(
Arrays.asList(
"bindPart(Lcodechicken/multipart/TMultiPart;)V",
"clearParts()V",
"copyFrom(Lcodechicken/multipart/TileMultipart;)V",
"getWeakChanges()Z",
"onNeighborTileChange(III)V",
"partRemoved(Lcodechicken/multipart/TMultiPart;I)V",
"weakTileChanges()Z",
"weakTileChanges_$eq(Z)V",
prefix + "bindPart(Lcodechicken/multipart/TMultiPart;)V",
prefix + "clearParts()V",
prefix + "copyFrom(Lcodechicken/multipart/TileMultipart;)V",
prefix + "onNeighborTileChange(III)V",
prefix + "partRemoved(Lcodechicken/multipart/TMultiPart;I)V")),
signatures);
assertEquals(boolean.class, first.getClass().getDeclaredField("weakTileChanges").getType());
trait.getMethod("weakTileChanges_$eq", boolean.class).invoke(first, true);
assertEquals(true, trait.getMethod("weakTileChanges").invoke(first));
assertEquals(false, trait.getMethod("weakTileChanges").invoke(second), "State is per tile");
}
@Test
void generatedJavaTraitOverridesPartialOcclusion() throws Exception {
PartialPart existing = new PartialPart(voxel(0));
@@ -0,0 +1,174 @@
package codechicken.multipart.test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.List;
import org.junit.jupiter.api.Test;
import codechicken.multipart.INeighborTileChange;
import codechicken.multipart.TMultiPart;
import codechicken.multipart.TileMultipart;
import codechicken.multipart.asm.MultipartMixinFactory;
import codechicken.multipart.scalatraits.TTileChangeTile;
import scala.collection.immutable.Nil$;
/** Consumer-visible state and behavior of the generated neighbor-tile-change trait. */
class TTileChangeTileFunctionalTest {
@Test
void theFlagStartsFalseAndIsPublishedThroughGetWeakChanges() {
TileMultipart tile = newTileChangeTile();
assertFalse(weakTileChanges(tile));
assertFalse(tile.getWeakChanges());
}
@Test
void bindPartOnlyEverSetsTheFlag() {
TileMultipart tile = newTileChangeTile();
tile.bindPart(new PlainPart());
assertFalse(tile.getWeakChanges(), "A part that is not an INeighborTileChange contributes nothing");
tile.bindPart(new ChangePart("quiet", false));
assertFalse(tile.getWeakChanges());
tile.bindPart(new ChangePart("weak", true));
assertTrue(tile.getWeakChanges());
// The flag is or-ed in, so a later quiet part cannot clear it.
tile.bindPart(new ChangePart("quiet2", false));
assertTrue(tile.getWeakChanges());
}
@Test
void copyFromTakesTheFlagOnlyFromAnotherChangeTile() {
TileMultipart source = newTileChangeTile();
source.bindPart(new ChangePart("weak", true));
TileMultipart target = newTileChangeTile();
target.copyFrom(source);
assertTrue(target.getWeakChanges());
// A plain source leaves the existing value alone rather than clearing it.
target.copyFrom(new TileMultipart());
assertTrue(target.getWeakChanges());
}
@Test
void clearPartsResetsTheFlagAndTheSuperclassList() {
TileMultipart tile = newTileChangeTile();
tile.addPart_do(new ChangePart("weak", true));
assertTrue(tile.getWeakChanges());
tile.clearParts();
assertFalse(tile.getWeakChanges());
assertTrue(tile.jPartList().isEmpty());
}
@Test
void partRemovedRecomputesTheFlagFromTheRemainingParts() {
TileMultipart tile = newTileChangeTile();
ChangePart weak = new ChangePart("weak", true);
ChangePart quiet = new ChangePart("quiet", false);
tile.addPart_do(weak);
tile.addPart_do(quiet);
assertTrue(tile.getWeakChanges());
// partRemoved recomputes from the list, so the part must already be gone from it.
tile.partList_$eq(seq(quiet));
tile.partRemoved(weak, 0);
assertFalse(tile.getWeakChanges());
tile.partList_$eq(seq(quiet, weak));
tile.partRemoved(quiet, 0);
assertTrue(tile.getWeakChanges(), "A remaining weak part keeps the flag set");
}
@Test
void neighborChangesDispatchOnlyWithinTwoBlocksAlongOneAxis() {
TileMultipart tile = newTileChangeTile();
ChangePart part = new ChangePart("weak", true);
tile.addPart_do(part);
// The tile sits at the origin, so the neighbor coordinates are the offset.
tile.onNeighborTileChange(1, 0, 0);
tile.onNeighborTileChange(0, -2, 0);
assertEquals(Arrays.asList("5:false", "0:true"), part.calls);
part.calls.clear();
tile.onNeighborTileChange(0, 0, 0); // no distance
tile.onNeighborTileChange(3, 0, 0); // too far
tile.onNeighborTileChange(1, 1, 0); // not axial, so no side
assertTrue(part.calls.isEmpty());
}
@Test
void onlyPartsWantingNeighborChangesAreCalled() {
TileMultipart tile = newTileChangeTile();
ChangePart listening = new ChangePart("weak", true);
tile.addPart_do(new PlainPart());
tile.addPart_do(listening);
tile.onNeighborTileChange(0, 1, 0);
assertEquals(Arrays.asList("1:false"), listening.calls);
}
private static TileMultipart newTileChangeTile() {
int traitId = MultipartMixinFactory.getId(TTileChangeTile.class.getName().replace('.', '/'));
BitSet traits = new BitSet();
traits.set(traitId);
return (TileMultipart) MultipartMixinFactory.construct(traits, Nil$.MODULE$);
}
private static boolean weakTileChanges(TileMultipart tile) {
return ((TTileChangeTile) tile).weakTileChanges();
}
private static scala.collection.Seq<TMultiPart> seq(TMultiPart... parts) {
return scala.collection.JavaConversions.asScalaBuffer(new ArrayList<>(Arrays.asList(parts))).toList();
}
private static class PlainPart extends TMultiPart {
@Override
public String getType() {
return "tilechange_test:plain";
}
}
private static final class ChangePart extends PlainPart implements INeighborTileChange {
private final String type;
private final boolean weak;
private final List<String> calls = new ArrayList<>();
private ChangePart(String type, boolean weak) {
this.type = type;
this.weak = weak;
}
@Override
public String getType() {
return "tilechange_test:" + type;
}
@Override
public boolean weakTileChanges() {
return weak;
}
@Override
public void onNeighborTileChanged(int side, boolean weak) {
calls.add(side + ":" + weak);
}
}
}