ref:4bb0fdff794927e40939eb9a13f806eddcd347c5

Fix multi-block resize bug: extension placement now resizes controller

Bug: When block B was placed next to already-running block A, the rescanGroup() on B detected A as controller but never told A to resize its terminal to the combined dimensions. A kept running at 80x24 instead of resizing to the group size. Fix: The propagation loop now checks if a member is the controller with a running terminal and resizes it to match the new group. Added test: extensionResizesRunningController verifies that placing an extension next to a running controller updates its cols/rows. 35 GameTests + 52 Rust tests, all passing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SHA: 4bb0fdff794927e40939eb9a13f806eddcd347c5
Author: Cole Christensen <cole.christensen@macmillan.com>
Date: 2026-03-20 08:11
Parents: b65660a
2 files changed +59 -2
Type
common/src/main/java/io/fangorn/alacrittymc/block/TerminalBlockEntity.java +24 −2
@@ -118,13 +118,35 @@
this.controllerPos = ctrlPos;
}
// Propagate group to all members
// Propagate group to all members and resize the controller if needed
int newCols = group.totalCols(COLS_PER_BLOCK);
int newRows = group.totalRows(ROWS_PER_BLOCK);
for (BlockPos memberPos : group.getMembers()) {
if (memberPos.equals(getBlockPos())) continue;
BlockEntity be = level.getBlockEntity(memberPos);
if (be instanceof TerminalBlockEntity memberBE) {
memberBE.screenGroup = group;
boolean isCtrl = ctrlPos.equals(memberPos);
memberBE.controllerPos = isCtrl ? null : ctrlPos;
memberBE.controllerPos = ctrlPos.equals(memberPos) ? null : ctrlPos;
// If this member is the controller and has a running terminal,
// resize it to match the new group dimensions
if (isCtrl && memberBE.terminalStarted && memberBE.terminal != null) {
if (memberBE.cols != newCols || memberBE.rows != newRows) {
memberBE.cols = newCols;
memberBE.rows = newRows;
memberBE.terminal.resize(newCols, newRows);
int[] dims = memberBE.terminal.getDimensions();
if (dims != null && dims.length >= 2) {
memberBE.pixelWidth = dims[0];
memberBE.pixelHeight = dims[1];
memberBE.pixelBuffer = ByteBuffer.allocateDirect(dims[0] * dims[1] * 4);
}
}
} else if (isCtrl) {
memberBE.cols = newCols;
memberBE.rows = newRows;
}
}
}
} else {
fabric/src/main/java/io/fangorn/alacrittymc/fabric/test/TerminalGameTest.java +35 −0
@@ -487,6 +487,41 @@
// ==================== 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. */
@GameTest(template = EMPTY_STRUCTURE, timeoutTicks = 40)
public void rescanAssignsControllerAndExtension(GameTestHelper h) {