@@ -485,6 +485,170 @@
});
}
// ==================== MULTI-BLOCK WIRING (block entity integration) ====================
/** rescanGroup() assigns controller and extension roles in a 2x1. */
@GameTest(template = EMPTY_STRUCTURE, timeoutTicks = 40)
public void rescanAssignsControllerAndExtension(GameTestHelper h) {
BlockPos left = new BlockPos(1, 1, 1);
BlockPos right = new BlockPos(2, 1, 1);
h.setBlock(left, terminalState(Direction.NORTH));
h.runAfterDelay(3, () -> {
h.setBlock(right, terminalState(Direction.NORTH));
h.runAfterDelay(5, () -> {
TerminalBlockEntity beL = assertTerminalEntity(h, left);
TerminalBlockEntity beR = assertTerminalEntity(h, right);
// Trigger rescan on left (simulates onPlace)
beL.rescanGroup();
// One should be controller, the other extension
boolean leftIsCtrl = !beL.isExtension();
boolean rightIsCtrl = !beR.isExtension();
if (leftIsCtrl == rightIsCtrl) {
throw new AssertionError("One must be controller, other extension. leftExt="
+ beL.isExtension() + " rightExt=" + beR.isExtension());
}
// Both should have a screen group
if (beL.getScreenGroup() == null) throw new AssertionError("Left has no group");
if (beR.getScreenGroup() == null) throw new AssertionError("Right has no group");
h.succeed();
});
});
}
/** Extension block's getController() returns the controller entity. */
@GameTest(template = EMPTY_STRUCTURE, timeoutTicks = 40)
public void extensionDelegatesToController(GameTestHelper h) {
BlockPos a = new BlockPos(1, 1, 1);
BlockPos b = new BlockPos(2, 1, 1);
h.setBlock(a, terminalState(Direction.NORTH));
h.runAfterDelay(3, () -> {
h.setBlock(b, terminalState(Direction.NORTH));
h.runAfterDelay(5, () -> {
TerminalBlockEntity beA = assertTerminalEntity(h, a);
beA.rescanGroup();
TerminalBlockEntity beB = assertTerminalEntity(h, b);
// Find which is the extension
TerminalBlockEntity extension = beA.isExtension() ? beA : beB;
TerminalBlockEntity controller = beA.isExtension() ? beB : beA;
// Extension's getController() should return the controller
TerminalBlockEntity resolved = extension.getController();
if (resolved != controller) {
throw new AssertionError("Extension.getController() returned wrong entity");
}
h.succeed();
});
});
}
/** Controller cols/rows update to combined dimensions after rescan. */
@GameTest(template = EMPTY_STRUCTURE, timeoutTicks = 40)
public void controllerDimensionsMatchGroup(GameTestHelper h) {
// 2x1 group: should be 2*COLS_PER_BLOCK wide, 1*ROWS_PER_BLOCK tall
BlockPos a = new BlockPos(1, 1, 1);
BlockPos b = new BlockPos(2, 1, 1);
h.setBlock(a, terminalState(Direction.NORTH));
h.setBlock(b, terminalState(Direction.NORTH));
h.runAfterDelay(5, () -> {
TerminalBlockEntity beA = assertTerminalEntity(h, a);
TerminalBlockEntity beB = assertTerminalEntity(h, b);
beA.rescanGroup();
TerminalBlockEntity controller = beA.isExtension() ? beB : beA;
// COLS_PER_BLOCK=40, ROWS_PER_BLOCK=12 → 2*40=80, 1*12=12
if (controller.getCols() != 80) {
throw new AssertionError("Expected 80 cols, got " + controller.getCols());
}
if (controller.getRows() != 12) {
throw new AssertionError("Expected 12 rows, got " + controller.getRows());
}
h.succeed();
});
}
/** 2x2 group has 80 cols and 24 rows. */
@GameTest(template = EMPTY_STRUCTURE, timeoutTicks = 40)
public void group2x2Dimensions(GameTestHelper h) {
for (int x = 1; x <= 2; x++)
for (int y = 1; y <= 2; y++)
h.setBlock(new BlockPos(x, y, 1), terminalState(Direction.NORTH));
h.runAfterDelay(5, () -> {
// Rescan from each block to ensure all are in the group
for (int x = 1; x <= 2; x++)
for (int y = 1; y <= 2; y++)
assertTerminalEntity(h, new BlockPos(x, y, 1)).rescanGroup();
// Find any controller
TerminalBlockEntity be = assertTerminalEntity(h, new BlockPos(1, 1, 1));
TerminalBlockEntity ctrl = be.isExtension() ? be.getController() : be;
// 2*40=80 cols, 2*12=24 rows
if (ctrl.getCols() != 80) throw new AssertionError("Cols=" + ctrl.getCols() + ", expected 80");
if (ctrl.getRows() != 24) throw new AssertionError("Rows=" + ctrl.getRows() + ", expected 24");
h.succeed();
});
}
/** 3x2 group has 120 cols and 24 rows. */
@GameTest(template = EMPTY_STRUCTURE, timeoutTicks = 40)
public void group3x2Dimensions(GameTestHelper h) {
for (int x = 1; x <= 3; x++)
for (int y = 1; y <= 2; y++)
h.setBlock(new BlockPos(x, y, 1), terminalState(Direction.NORTH));
h.runAfterDelay(5, () -> {
// Rescan from each block
for (int x = 1; x <= 3; x++)
for (int y = 1; y <= 2; y++)
assertTerminalEntity(h, new BlockPos(x, y, 1)).rescanGroup();
TerminalBlockEntity be = assertTerminalEntity(h, new BlockPos(1, 1, 1));
TerminalBlockEntity ctrl = be.isExtension() ? be.getController() : be;
// 3*40=120 cols, 2*12=24 rows
if (ctrl.getCols() != 120) throw new AssertionError("Cols=" + ctrl.getCols() + ", expected 120");
if (ctrl.getRows() != 24) throw new AssertionError("Rows=" + ctrl.getRows() + ", expected 24");
h.succeed();
});
}
/** Breaking middle of 3x1 leaves two singles with default 80x24. */
@GameTest(template = EMPTY_STRUCTURE, timeoutTicks = 60)
public void breakMiddleResetsToDefaults(GameTestHelper h) {
BlockPos a = new BlockPos(1, 1, 1);
BlockPos b = new BlockPos(2, 1, 1);
BlockPos c = new BlockPos(3, 1, 1);
h.setBlock(a, terminalState(Direction.NORTH));
h.setBlock(b, terminalState(Direction.NORTH));
h.setBlock(c, terminalState(Direction.NORTH));
h.runAfterDelay(5, () -> {
// Form group
assertTerminalEntity(h, a).rescanGroup();
h.runAfterDelay(3, () -> {
// Break middle
h.destroyBlock(b);
h.runAfterDelay(5, () -> {
// Remaining blocks should be standalone with default dims
TerminalBlockEntity beA = assertTerminalEntity(h, a);
TerminalBlockEntity beC = assertTerminalEntity(h, c);
// After neighbor notification, they should have rescanned
if (beA.isExtension()) throw new AssertionError("A should not be extension after break");
if (beC.isExtension()) throw new AssertionError("C should not be extension after break");
if (beA.getCols() != 80) throw new AssertionError("A cols=" + beA.getCols() + " expected 80");
if (beC.getCols() != 80) throw new AssertionError("C cols=" + beC.getCols() + " expected 80");
h.succeed();
});
});
});
}
// ==================== HELPERS ====================
private static BlockState terminalState(Direction facing) {