Warp sight honors "as though they were adjacent" — diagonals included

Playtesting caught the gap with a sticky wand: a target visible
through the opening but one square off the corridor axis was refused.
The colinear-only model was stricter than the rulebook, whose actual
instruction is to treat the connected edges as adjacent boards with a
straight center-to-center line — and a straight line through a
one-cell opening may run diagonally, exactly as through a doorway.

hasWarpLineOfSight now abuts the far side virtually at the mouth
(rotating when the pairing turns a corner, as aisle warps do), finds
where the center-to-center line crosses the rim, requires that
crossing to fall strictly within the open mouth (corner-grazing stays
blocked — the strict reading), and checks the two legs against real
walls on each side of the seam. hasLineOfSight's core became a shared
segmentClear to serve both legs. The colinear corridor cases remain
as the special case they always were; a new test pins that off-axis
squares are visible through mouths.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Eric Wagoner
2026-08-16 15:20:46 -04:00
co-authored by Claude Fable 5
parent 53edf9734e
commit 8af8962330
3 changed files with 198 additions and 28 deletions
+27
View File
@@ -236,3 +236,30 @@ describe("setup diagram pairings (rulebook Set-Up Diagram)", () => {
expect(bent(r.state.board.warps).length).toBe(0); // "only opposite board edges connect"
});
});
describe("sight through warps treats the boards as adjacent", () => {
const twoSector: SectorPlacement[] = [
{ boardId: "board-a", origin: { x: 0, y: 0 }, rotation: 0 },
{ boardId: "board-b", origin: { x: 0, y: 5 }, rotation: 0 },
];
it("diagonal lines pass through the one-cell mouth like any doorway", () => {
const board = assembleBoard(twoSector);
// From SOME warp mouth, at least one square off the straight corridor
// axis must be visible through the opening — the adjacency promise.
let offAxis = 0;
for (const w of board.warps) {
const mouth = w.from.cell;
const axisVertical = w.from.side === "N" || w.from.side === "S";
for (const key of Object.keys(board.cells)) {
const [x, y] = key.split(",").map(Number) as [number, number];
const onAxis = axisVertical ? x === mouth.x : y === mouth.y;
if (onAxis) continue;
if (!hasLineOfSight(board, mouth, { x, y }) && sightBetween(board, mouth, { x, y })) {
offAxis++;
}
}
}
expect(offAxis).toBeGreaterThan(0);
});
});