Fix frame flood-fill to follow diagonal (stepped) ring connections

Round/octagonal rings built the normal Minecraft way step diagonally
at the corners - two frame blocks touching only edge-to-edge, not
face-to-face. The scanner only checked the 6 face-adjacent neighbours,
so those blocks were treated as disconnected fragments and the whole
ring failed to register (or only a fraction of it did).

Frame discovery now flood-fills using all 26 surrounding offsets
instead of just the 6 faces. The geometric chevron walk needed the
same fix - its in-plane adjacency now checks 8 directions (orthogonal
+ diagonal) instead of 4, so stepped rings still produce a clean
single-loop walk instead of falling back to evenly-spaced chevrons.

Interior/iris flood-fill is intentionally left orthogonal-only: since
it only ever moves face-to-face, a diagonal-only frame corner already
blocks it from slipping past, so enclosure detection didn't need to
change.
This commit is contained in:
Michael Burgess
2026-08-09 11:04:20 -04:00
parent fe3b2b0f14
commit 282c8cfdbb
2 changed files with 30 additions and 4 deletions
@@ -25,6 +25,25 @@ public class GateStructureScanner {
BlockFace.UP, BlockFace.DOWN, BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST
};
// Round/octagonal rings built the usual Minecraft way step diagonally at the corners - two
// frame blocks touching only edge-to-edge (or even just corner-to-corner), not face-to-face.
// The frame search has to follow those too, or a stepped ring gets treated as several
// disconnected fragments instead of one structure.
private static final int[][] ALL_26_OFFSETS = buildAll26Offsets();
private static int[][] buildAll26Offsets() {
List<int[]> offsets = new ArrayList<>();
for (int dx = -1; dx <= 1; dx++) {
for (int dy = -1; dy <= 1; dy++) {
for (int dz = -1; dz <= 1; dz++) {
if (dx == 0 && dy == 0 && dz == 0) continue;
offsets.add(new int[]{dx, dy, dz});
}
}
}
return offsets.toArray(new int[0][]);
}
public static final class ScanResult {
public final GateStructure structure; // null on failure
public final String failureReason; // null on success
@@ -84,8 +103,8 @@ public class GateStructureScanner {
+ "Raise max-frame-blocks or make the frame materials more specific.");
}
for (BlockFace face : NEIGHBORS) {
Block next = cur.getRelative(face);
for (int[] d : ALL_26_OFFSETS) {
Block next = cur.getRelative(d[0], d[1], d[2]);
long k = key(next);
if (frameKeys.contains(k)) continue;
if (isFrameMaterial(next.getType())) {
@@ -224,14 +243,18 @@ public class GateStructureScanner {
return chevrons.isEmpty() ? fallbackEvenlySpaced(frameBlocks) : chevrons;
}
/** Walks the ring in geometric order assuming each frame block has exactly two in-plane neighbours; null if that assumption fails. */
private static final int[][] PLANE_8_OFFSETS = {
{1, 0}, {-1, 0}, {0, 1}, {0, -1}, {1, 1}, {1, -1}, {-1, 1}, {-1, -1}
};
/** Walks the ring in geometric order assuming each frame block has exactly two in-plane neighbours (orthogonal or diagonal, for stepped rings); null if that assumption fails. */
private List<Block> walkRing(List<Block> frameBlocks, Map<Long, Block> plane, boolean horizontalIsX) {
Map<Block, List<Block>> adjacency = new HashMap<>();
for (Block b : frameBlocks) {
int col = horizontalIsX ? b.getX() : b.getZ();
int row = b.getY();
List<Block> neighbors = new ArrayList<>();
for (int[] d : new int[][]{{1, 0}, {-1, 0}, {0, 1}, {0, -1}}) {
for (int[] d : PLANE_8_OFFSETS) {
long k = (((long) (row + d[0])) << 32) ^ ((col + d[1]) & 0xFFFFFFFFL);
Block n = plane.get(k);
if (n != null && n != b) neighbors.add(n);