Rev 7: sight passes only through the aisle warps

The cards settle what the morning's feature got wrong. DIMENSIONAL
WARP: "There is no L.O.S. through the warp." The AUTO WARP: "for all
purposes, treat the connected board edges as though they are
adjacent" — an explicit grant, for edges that physically meet at a
corner. The lettered wraparounds get no grant at all: their rule is
about walking. So the pattern is that warps carry movement, not
sight, unless the text says otherwise — and only the aisle corners
say otherwise.

Warps now carry an aisle flag (the 3-player AUTO WARP and the
5-player corner arcs); hasWarpLineOfSight honors only those, with
the adjacency geometry — diagonals through the corner mouth —
unchanged from this morning where it rightfully applies. Games begun
before rev 7 keep the wider sight their logged commands were
validated against, via a legacy flag boardView stamps from the
game's rules revision. Tests pin all three states: lettered mouths
see nothing, aisle mouths see the corner fan, legacy games see
everything they used to.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Eric Wagoner
2026-08-16 15:25:00 -04:00
co-authored by Claude Fable 5
parent 8af8962330
commit 493f3bc567
7 changed files with 131 additions and 60 deletions
+39 -47
View File
@@ -138,39 +138,26 @@ describe("player counts 2-6", () => {
});
});
describe("line of sight through wraparound openings", () => {
describe("sight and the wraparound openings", () => {
const twoSector: SectorPlacement[] = [
{ boardId: "board-a", origin: { x: 0, y: 0 }, rotation: 0 },
{ boardId: "board-b", origin: { x: 0, y: 5 }, rotation: 0 },
];
it("sees straight down the corridor, out one mouth and in the other", () => {
it("lettered wraparounds carry movement only — no sight across the map", () => {
const board = assembleBoard(twoSector);
// (2,0) N wraps to (2,9): the mouths see each other through the join.
expect(hasLineOfSight(board, { x: 2, y: 0 }, { x: 2, y: 9 })).toBe(false);
// (2,0) N wraps to (2,9) for walking, but no rule grants sight there,
// and DIMENSIONAL WARP's own face denies it for warps generally.
expect(sightBetween(board, { x: 2, y: 0 }, { x: 2, y: 9 })).toBe(false);
expect(sightBetween(board, { x: 2, y: 9 }, { x: 2, y: 0 })).toBe(false);
});
it("pre-rev-7 games keep the wider sight their commands were validated under", () => {
const board = { ...assembleBoard(twoSector), sightThroughAllWarps: true };
expect(sightBetween(board, { x: 2, y: 0 }, { x: 2, y: 9 })).toBe(true);
expect(sightBetween(board, { x: 2, y: 9 }, { x: 2, y: 0 })).toBe(true);
});
it("demands a straight corridor: off-axis viewers see nothing", () => {
const board = assembleBoard(twoSector);
expect(sightBetween(board, { x: 1, y: 0 }, { x: 2, y: 9 })).toBe(false);
expect(sightBetween(board, { x: 3, y: 1 }, { x: 2, y: 9 })).toBe(false);
});
it("walls inside either corridor still block the warp sight", () => {
const board = assembleBoard(twoSector);
// Layout A: the wall between (2,0) and (2,1)'s row (home column sight is
// cut above row 2 — pinned earlier) blocks a deeper viewer's warp sight.
const deepViewer = { x: 2, y: 2 };
expect(hasLineOfSight(board, deepViewer, { x: 2, y: 0 })).toBe(false);
expect(sightBetween(board, deepViewer, { x: 2, y: 9 })).toBe(false);
});
it("a filled mouth chokes the tunnel", () => {
const board = assembleBoard(twoSector);
const stone = { "2,9": true as const };
expect(sightBetween(board, { x: 2, y: 0 }, { x: 2, y: 8 }, stone)).toBe(false);
// Even then: walls inside the corridor still block, and a filled mouth chokes.
expect(sightBetween(board, { x: 2, y: 2 }, { x: 2, y: 9 })).toBe(false);
expect(sightBetween(board, { x: 2, y: 0 }, { x: 2, y: 8 }, { "2,9": true })).toBe(false);
});
});
@@ -237,29 +224,34 @@ describe("setup diagram pairings (rulebook Set-Up Diagram)", () => {
});
});
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++;
}
describe("the aisle warp treats its corner as adjacent — sight included", () => {
it("sight passes through the AUTO WARP, diagonals and all", () => {
const { board } = setupBoardForPins(3, createRng(7));
const aisle = board.warps.find((w) => w.aisle)!;
expect(aisle).toBeDefined();
// From the aisle mouth, squares beyond the corner are visible — including
// at least one OFF the straight axis (the adjacency promise).
const mouth = aisle.from.cell;
const axisVertical = aisle.from.side === "N" || aisle.from.side === "S";
let seen = 0, offAxis = 0;
for (const key of Object.keys(board.cells)) {
const [x, y] = key.split(",").map(Number) as [number, number];
if (hasLineOfSight(board, mouth, { x, y })) continue;
if (sightBetween(board, mouth, { x, y })) {
seen++;
if (axisVertical ? x !== mouth.x : y !== mouth.y) offAxis++;
}
}
expect(seen).toBeGreaterThan(0);
expect(offAxis).toBeGreaterThan(0);
// A lettered (non-aisle) mouth on the same board sees nothing extra.
const lettered = board.warps.find((w) => !w.aisle)!;
const lm = lettered.from.cell;
let extra = 0;
for (const key of Object.keys(board.cells)) {
const [x, y] = key.split(",").map(Number) as [number, number];
if (!hasLineOfSight(board, lm, { x, y }) && sightBetween(board, lm, { x, y })) extra++;
}
expect(extra).toBe(0);
});
});