@@ -485,44 +485,7 @@
});
}
// ==================== MULTI-BLOCK WIRING (block entity integration) ====================
/** Placing extension next to an already-running controller resizes it. */
@GameTest(template = EMPTY_STRUCTURE, timeoutTicks = 40)
public void extensionResizesRunningController(GameTestHelper h) {
BlockPos a = new BlockPos(1, 1, 1);
BlockPos b = new BlockPos(2, 1, 1);
// Place block A first (simulating player placing and starting terminal)
h.setBlock(a, terminalState(Direction.NORTH));
h.runAfterDelay(3, () -> {
TerminalBlockEntity beA = assertTerminalEntity(h, a);
// Verify A starts with default single-block dimensions
if (beA.getCols() != 80) throw new AssertionError("A should start at 80 cols");
if (beA.getRows() != 24) throw new AssertionError("A should start at 24 rows");
// Now place block B next to it
h.setBlock(b, terminalState(Direction.NORTH));
h.runAfterDelay(5, () -> {
// Trigger rescan from the NEW block (what onPlace does)
TerminalBlockEntity beB = assertTerminalEntity(h, b);
beB.rescanGroup();
// The controller's dimensions should have been updated
TerminalBlockEntity ctrl = beA.isExtension() ? beB : beA;
// 2 blocks wide × 40 cols/block = 80 cols, 1 block tall × 12 rows/block = 12 rows
if (ctrl.getCols() != 80) {
throw new AssertionError("Controller should have 80 cols after group, got " + ctrl.getCols());
}
if (ctrl.getRows() != 12) {
throw new AssertionError("Controller should have 12 rows after group, got " + ctrl.getRows());
}
h.succeed();
});
});
}
/** rescanGroup() assigns controller and extension roles in a 2x1. */
// Old multi-block wiring tests replaced by comprehensive suite
@GameTest(template = EMPTY_STRUCTURE, timeoutTicks = 40)
public void rescanAssignsControllerAndExtension(GameTestHelper h) {
BlockPos left = new BlockPos(1, 1, 1);
@@ -556,7 +519,7 @@
/** Extension block's getController() returns the controller entity. */
@GameTest(template = EMPTY_STRUCTURE, timeoutTicks = 40)
public void extensionDelegatesToController(GameTestHelper h) {
public void extensionDelegatesToControllerOld(GameTestHelper h) {
BlockPos a = new BlockPos(1, 1, 1);
BlockPos b = new BlockPos(2, 1, 1);
h.setBlock(a, terminalState(Direction.NORTH));
@@ -681,10 +644,281 @@
h.succeed();
});
});
});
}
// ==================== MULTI-BLOCK WIRING ====================
/** Rescan assigns exactly one controller and N-1 extensions in a 2x1. */
@GameTest(template = EMPTY_STRUCTURE, timeoutTicks = 40)
public void rescanRoles2x1(GameTestHelper h) {
BlockPos a = new BlockPos(1, 1, 1), b = new BlockPos(2, 1, 1);
h.setBlock(a, terminalState(Direction.NORTH));
h.setBlock(b, terminalState(Direction.NORTH));
h.runAfterDelay(5, () -> {
assertTerminalEntity(h, a).rescanGroup();
TerminalBlockEntity beA = assertTerminalEntity(h, a);
TerminalBlockEntity beB = assertTerminalEntity(h, b);
int controllers = (beA.isExtension() ? 0 : 1) + (beB.isExtension() ? 0 : 1);
if (controllers != 1) throw new AssertionError("Expected exactly 1 controller, got " + controllers);
h.succeed();
});
}
/** Extension's getController() returns the actual controller entity. */
@GameTest(template = EMPTY_STRUCTURE, timeoutTicks = 40)
public void extensionDelegatesToController(GameTestHelper h) {
BlockPos a = new BlockPos(1, 1, 1), b = new BlockPos(2, 1, 1);
h.setBlock(a, terminalState(Direction.NORTH));
h.setBlock(b, terminalState(Direction.NORTH));
h.runAfterDelay(5, () -> {
assertTerminalEntity(h, a).rescanGroup();
TerminalBlockEntity beA = assertTerminalEntity(h, a);
TerminalBlockEntity beB = assertTerminalEntity(h, b);
TerminalBlockEntity ext = beA.isExtension() ? beA : beB;
TerminalBlockEntity ctrl = beA.isExtension() ? beB : beA;
if (ext.getController() != ctrl) throw new AssertionError("Extension.getController() wrong");
h.succeed();
});
}
/** 2x1 controller has 80 cols, 12 rows. */
@GameTest(template = EMPTY_STRUCTURE, timeoutTicks = 40)
public void dims2x1(GameTestHelper h) {
BlockPos a = new BlockPos(1, 1, 1), b = new BlockPos(2, 1, 1);
h.setBlock(a, terminalState(Direction.NORTH));
h.setBlock(b, terminalState(Direction.NORTH));
h.runAfterDelay(5, () -> {
assertTerminalEntity(h, a).rescanGroup();
TerminalBlockEntity ctrl = findController(h, a, b);
assertDims(ctrl, 80, 12);
h.succeed();
});
}
/** 2x2 = 80 cols, 24 rows. */
@GameTest(template = EMPTY_STRUCTURE, timeoutTicks = 40)
public void dims2x2(GameTestHelper h) {
placeGrid(h, 1, 1, 2, 2, 1, Direction.NORTH);
h.runAfterDelay(5, () -> {
rescanAll(h, 1, 1, 2, 2, 1);
TerminalBlockEntity ctrl = findAnyController(h, 1, 1, 2, 2, 1);
assertDims(ctrl, 80, 24);
h.succeed();
});
}
/** 3x2 = 120 cols, 24 rows. */
@GameTest(template = EMPTY_STRUCTURE, timeoutTicks = 40)
public void dims3x2(GameTestHelper h) {
placeGrid(h, 1, 1, 3, 2, 1, Direction.NORTH);
h.runAfterDelay(5, () -> {
rescanAll(h, 1, 1, 3, 2, 1);
TerminalBlockEntity ctrl = findAnyController(h, 1, 1, 3, 2, 1);
assertDims(ctrl, 120, 24);
h.succeed();
});
}
/** Rescan kills rogue terminal on extension that started before group formed. */
@GameTest(template = EMPTY_STRUCTURE, timeoutTicks = 40)
public void rescanKillsRogueTerminalOnExtension(GameTestHelper h) {
BlockPos a = new BlockPos(1, 1, 1), b = new BlockPos(2, 1, 1);
h.setBlock(a, terminalState(Direction.NORTH));
h.setBlock(b, terminalState(Direction.NORTH));
h.runAfterDelay(5, () -> {
// Before rescan, neither is an extension, so neither has a "rogue" terminal
// But after rescan, exactly one is the extension and should NOT be running
assertTerminalEntity(h, a).rescanGroup();
TerminalBlockEntity beA = assertTerminalEntity(h, a);
TerminalBlockEntity beB = assertTerminalEntity(h, b);
TerminalBlockEntity ext = beA.isExtension() ? beA : beB;
if (ext.isTerminalRunning()) throw new AssertionError("Extension should not have a running terminal");
h.succeed();
});
}
// ==================== BREAKING TESTS ====================
/** Break controller of 2x1 — no crash, extension becomes standalone. */
@GameTest(template = EMPTY_STRUCTURE, timeoutTicks = 60)
public void breakController2x1(GameTestHelper h) {
BlockPos a = new BlockPos(1, 1, 1), b = new BlockPos(2, 1, 1);
h.setBlock(a, terminalState(Direction.NORTH));
h.setBlock(b, terminalState(Direction.NORTH));
h.runAfterDelay(5, () -> {
assertTerminalEntity(h, a).rescanGroup();
TerminalBlockEntity beA = assertTerminalEntity(h, a);
TerminalBlockEntity beB = assertTerminalEntity(h, b);
BlockPos ctrlPos = beA.isExtension() ? b : a;
BlockPos extPos = beA.isExtension() ? a : b;
// Break the controller
h.destroyBlock(ctrlPos);
h.runAfterDelay(5, () -> {
// Extension should become standalone
TerminalBlockEntity survivor = assertTerminalEntity(h, extPos);
if (survivor.isExtension()) throw new AssertionError("Survivor should be standalone, not extension");
if (survivor.getScreenGroup() != null && survivor.getScreenGroup().getMembers().size() > 1)
throw new AssertionError("Survivor should not be in a group");
h.succeed();
});
});
}
/** Break extension of 2x1 — controller survives with default dims. */
@GameTest(template = EMPTY_STRUCTURE, timeoutTicks = 60)
public void breakExtension2x1(GameTestHelper h) {
BlockPos a = new BlockPos(1, 1, 1), b = new BlockPos(2, 1, 1);
h.setBlock(a, terminalState(Direction.NORTH));
h.setBlock(b, terminalState(Direction.NORTH));
h.runAfterDelay(5, () -> {
assertTerminalEntity(h, a).rescanGroup();
TerminalBlockEntity beA = assertTerminalEntity(h, a);
BlockPos ctrlPos = beA.isExtension() ? b : a;
BlockPos extPos = beA.isExtension() ? a : b;
h.destroyBlock(extPos);
h.runAfterDelay(5, () -> {
TerminalBlockEntity ctrl = assertTerminalEntity(h, ctrlPos);
if (ctrl.isExtension()) throw new AssertionError("Controller should remain standalone");
// Should revert to default single-block dims
assertDims(ctrl, 80, 24);
h.succeed();
});
});
}
/** Break middle of 3x1 — two singletons remain, no crash. */
@GameTest(template = EMPTY_STRUCTURE, timeoutTicks = 60)
public void breakMiddle3x1(GameTestHelper h) {
BlockPos a = new BlockPos(1, 1, 1), b = new BlockPos(2, 1, 1), 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, () -> {
assertTerminalEntity(h, a).rescanGroup();
h.destroyBlock(b);
h.runAfterDelay(5, () -> {
TerminalBlockEntity beA = assertTerminalEntity(h, a);
TerminalBlockEntity beC = assertTerminalEntity(h, c);
if (beA.isExtension()) throw new AssertionError("A should be standalone");
if (beC.isExtension()) throw new AssertionError("C should be standalone");
h.succeed();
});
});
}
/** Break all blocks one by one — no crash at any point. */
@GameTest(template = EMPTY_STRUCTURE, timeoutTicks = 80)
public void breakAllOneByOne(GameTestHelper h) {
BlockPos a = new BlockPos(1, 1, 1), b = new BlockPos(2, 1, 1), 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, () -> {
assertTerminalEntity(h, a).rescanGroup();
h.destroyBlock(a);
h.runAfterDelay(3, () -> {
h.destroyBlock(b);
h.runAfterDelay(3, () -> {
h.destroyBlock(c);
h.runAfterDelay(3, () -> {
h.assertBlockNotPresent(AlacrittyMod.TERMINAL_BLOCK.get(), a);
h.assertBlockNotPresent(AlacrittyMod.TERMINAL_BLOCK.get(), b);
h.assertBlockNotPresent(AlacrittyMod.TERMINAL_BLOCK.get(), c);
h.succeed();
});
});
});
});
}
/** Place, form group, break, place again — no crash or stale state. */
@GameTest(template = EMPTY_STRUCTURE, timeoutTicks = 80)
public void placeBreakReplace(GameTestHelper h) {
BlockPos a = new BlockPos(1, 1, 1), b = new BlockPos(2, 1, 1);
h.setBlock(a, terminalState(Direction.NORTH));
h.setBlock(b, terminalState(Direction.NORTH));
h.runAfterDelay(5, () -> {
assertTerminalEntity(h, a).rescanGroup();
// Break B
h.destroyBlock(b);
h.runAfterDelay(5, () -> {
// Re-place B
h.setBlock(b, terminalState(Direction.NORTH));
h.runAfterDelay(5, () -> {
// Rescan — should form group again
assertTerminalEntity(h, a).rescanGroup();
TerminalBlockEntity beA = assertTerminalEntity(h, a);
TerminalBlockEntity beB = assertTerminalEntity(h, b);
if (beA.getScreenGroup() == null || beB.getScreenGroup() == null) {
throw new AssertionError("Group should reform after re-placing");
}
h.succeed();
});
});
});
}
/** getController() returns self when controller block was destroyed. */
@GameTest(template = EMPTY_STRUCTURE, timeoutTicks = 60)
public void getControllerAfterDestruction(GameTestHelper h) {
BlockPos a = new BlockPos(1, 1, 1), b = new BlockPos(2, 1, 1);
h.setBlock(a, terminalState(Direction.NORTH));
h.setBlock(b, terminalState(Direction.NORTH));
h.runAfterDelay(5, () -> {
assertTerminalEntity(h, a).rescanGroup();
TerminalBlockEntity beA = assertTerminalEntity(h, a);
TerminalBlockEntity beB = assertTerminalEntity(h, b);
BlockPos ctrlPos = beA.isExtension() ? b : a;
BlockPos extPos = beA.isExtension() ? a : b;
h.destroyBlock(ctrlPos);
h.runAfterDelay(5, () -> {
TerminalBlockEntity survivor = assertTerminalEntity(h, extPos);
// getController should return self (not crash)
TerminalBlockEntity ctrl = survivor.getController();
if (ctrl != survivor) throw new AssertionError("Should return self when controller destroyed");
h.succeed();
});
});
}
// ==================== HELPERS ====================
private void placeGrid(GameTestHelper h, int x0, int y0, int w, int height, int z, Direction facing) {
for (int x = x0; x < x0 + w; x++)
for (int y = y0; y < y0 + height; y++)
h.setBlock(new BlockPos(x, y, z), terminalState(facing));
}
private void rescanAll(GameTestHelper h, int x0, int y0, int w, int height, int z) {
for (int x = x0; x < x0 + w; x++)
for (int y = y0; y < y0 + height; y++)
assertTerminalEntity(h, new BlockPos(x, y, z)).rescanGroup();
}
private TerminalBlockEntity findController(GameTestHelper h, BlockPos a, BlockPos b) {
TerminalBlockEntity beA = assertTerminalEntity(h, a);
return beA.isExtension() ? assertTerminalEntity(h, b) : beA;
}
private TerminalBlockEntity findAnyController(GameTestHelper h, int x0, int y0, int w, int height, int z) {
for (int x = x0; x < x0 + w; x++)
for (int y = y0; y < y0 + height; y++) {
TerminalBlockEntity be = assertTerminalEntity(h, new BlockPos(x, y, z));
if (!be.isExtension()) return be;
}
throw new AssertionError("No controller found in grid");
}
private static void assertDims(TerminalBlockEntity ctrl, int expectedCols, int expectedRows) {
if (ctrl.getCols() != expectedCols)
throw new AssertionError("Cols=" + ctrl.getCols() + ", expected " + expectedCols);
if (ctrl.getRows() != expectedRows)
throw new AssertionError("Rows=" + ctrl.getRows() + ", expected " + expectedRows);
}
private static BlockState terminalState(Direction facing) {
return AlacrittyMod.TERMINAL_BLOCK.get().defaultBlockState()