ref:c55db3488c3f770d56515ca4d41b37cb43387983

Fix multi-block on client: lazy group detection in clientTick

Root cause: Block.onPlace() only fires on the server side in MC 1.20.1. The client-side block entities never knew about the group because rescanGroup() was only called from onPlace. Fix: clientTick now does lazy group detection — every 10 ticks for the first 3 seconds, each block rescans for neighboring terminal blocks. This discovers groups on the client where rendering happens. Also resets the scan flag when rescanGroup() is called explicitly (e.g., from server-side onPlace neighbor propagation), so the client re-evaluates when the world state changes. 35 GameTests + 52 Rust + visual test (2/2), all passing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SHA: c55db3488c3f770d56515ca4d41b37cb43387983
Author: Cole Christensen <cole.christensen@macmillan.com>
Date: 2026-03-20 13:59
Parents: 734948a
2 files changed +36 -4
Type
common/src/main/java/io/fangorn/alacrittymc/block/TerminalBlock.java +15 −4
@@ -97,21 +97,32 @@
@Override
public void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean movedByPiston) {
super.onPlace(state, level, pos, oldState, movedByPiston);
// Scan for multi-block groups when a terminal block is placed.
// Rescan THIS block AND all adjacent terminal blocks so the entire
// group forms correctly regardless of placement order.
System.out.println("[AlacrittyMC] onPlace at " + pos + " side=" + (level.isClientSide() ? "CLIENT" : "SERVER"));
BlockEntity be = level.getBlockEntity(pos);
if (be instanceof TerminalBlockEntity terminalBE) {
terminalBE.rescanGroup();
System.out.println("[AlacrittyMC] self rescan: group=" + (terminalBE.getScreenGroup() != null
? terminalBE.getScreenGroup().getGridCols() + "x" + terminalBE.getScreenGroup().getGridRows()
: "none") + " ext=" + terminalBE.isExtension() + " cols=" + terminalBE.getCols());
} else {
System.out.println("[AlacrittyMC] NO block entity at " + pos);
}
// Also rescan all 4 horizontal neighbors + up/down
int neighborCount = 0;
for (Direction dir : new Direction[]{Direction.NORTH, Direction.SOUTH, Direction.EAST, Direction.WEST, Direction.UP, Direction.DOWN}) {
BlockPos neighbor = pos.relative(dir);
BlockEntity neighborBE = level.getBlockEntity(neighbor);
if (neighborBE instanceof TerminalBlockEntity neighborTBE) {
neighborTBE.rescanGroup();
neighborCount++;
System.out.println("[AlacrittyMC] neighbor " + dir + " at " + neighbor
+ ": group=" + (neighborTBE.getScreenGroup() != null
? neighborTBE.getScreenGroup().getGridCols() + "x" + neighborTBE.getScreenGroup().getGridRows()
: "none") + " ext=" + neighborTBE.isExtension() + " cols=" + neighborTBE.getCols());
}
}
System.out.println("[AlacrittyMC] " + neighborCount + " terminal neighbors found");
}
@Override
common/src/main/java/io/fangorn/alacrittymc/block/TerminalBlockEntity.java +21 −0
@@ -83,5 +83,8 @@
*/
public void rescanGroup() {
if (level == null) return;
// Reset lazy scan flag so the client re-evaluates
clientGroupScanned = false;
clientTickCount = 0;
ScreenGroup group = ScreenGroup.scan(level, getBlockPos());
@@ -233,7 +236,25 @@
}
}
// Client tick counter for lazy group detection
private int clientTickCount = 0;
private boolean clientGroupScanned = false;
public static void clientTick(Level level, BlockPos pos, BlockState state, TerminalBlockEntity be) {
be.clientTickCount++;
// Lazy client-side group detection: onPlace only fires on the server,
// so the client needs to discover groups itself. Rescan periodically
// until a stable group is found, then stop scanning.
if (!be.clientGroupScanned && be.clientTickCount % 10 == 0) {
be.rescanGroup();
// Stop scanning once we've had a few chances to find neighbors
if (be.clientTickCount > 60) {
be.clientGroupScanned = true;
}
}
// Only the controller runs the terminal
if (be.terminal == null || !be.terminalStarted) return;
boolean alive = be.terminal.pollPty();
if (!alive) {