diff --git a/.playwright-mcp/console-2026-08-16T19-04-55-807Z.log b/.playwright-mcp/console-2026-08-16T19-04-55-807Z.log index 806163c..a8dcc41 100644 --- a/.playwright-mcp/console-2026-08-16T19-04-55-807Z.log +++ b/.playwright-mcp/console-2026-08-16T19-04-55-807Z.log @@ -238,3 +238,60 @@ [ 941634ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 [ 947338ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 [ 950320ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 954153ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 957484ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 961665ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 965207ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 968046ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 971084ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 973824ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 980166ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 984179ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 989191ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 995232ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 997946ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1001234ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1006913ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1011516ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1014303ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1018041ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1021328ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1026098ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1030726ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1035086ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1041304ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1044467ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1049226ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1052558ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1058154ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1061028ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1065787ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1071637ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1075522ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1080951ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1085930ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1088820ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1092037ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1098390ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1102678ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1105217ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1109419ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1115559ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1121996ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1126779ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1132972ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1137004ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1140326ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1144869ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1150935ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1153701ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1157899ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1164235ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1169866ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1175925ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1182032ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1185270ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1190429ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1194642ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1197213ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 +[ 1202483ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8899/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8899/assets/index-Di4XAJUy.js:1 diff --git a/packages/engine/src/board.ts b/packages/engine/src/board.ts index cf8f5db..2c8b82b 100644 --- a/packages/engine/src/board.ts +++ b/packages/engine/src/board.ts @@ -27,6 +27,13 @@ export interface Warp { from: { cell: Cell; side: Side }; /** ...you arrive at this cell (entering through `side`). */ to: { cell: Cell; side: Side }; + /** + * An aisle (AUTO) warp joins two edges that physically meet at a corner: + * "for all purposes, treat the connected board edges as though they are + * adjacent" — including line of sight. The lettered wraparounds carry + * movement only; no rule grants sight across the map. + */ + aisle?: boolean; } export interface AssembledBoard { @@ -38,6 +45,8 @@ export interface AssembledBoard { /** Cells that are part of the map (all cells inside placed sectors). */ cells: Record; warps: Warp[]; + /** Pre-rev-7 games saw through every warp; their replays keep that sight. */ + sightThroughAllWarps?: boolean; /** Per player-sector info, in placement order. */ homes: Cell[]; treasureSpaces: Cell[][]; @@ -119,7 +128,7 @@ export interface AssemblyOptions { * openings pair straight across the map — correct for the 4p square; the * standard 2p column crosses its side openings and must pass pairs. */ - warpPairs?: [OpeningRef, OpeningRef][]; + warpPairs?: ([OpeningRef, OpeningRef] | [OpeningRef, OpeningRef, "aisle"])[]; } /** @@ -204,11 +213,12 @@ export function assembleBoard( }; if (options.warpPairs) { - for (const [a, b] of options.warpPairs) { + for (const [a, b, kind] of options.warpPairs) { const ca = openingCell(a.sector, a.side); const cb = openingCell(b.sector, b.side); - warps.push({ from: { cell: ca, side: a.side }, to: { cell: cb, side: b.side } }); - warps.push({ from: { cell: cb, side: b.side }, to: { cell: ca, side: a.side } }); + const aisle = kind === "aisle" ? { aisle: true as const } : {}; + warps.push({ from: { cell: ca, side: a.side }, to: { cell: cb, side: b.side }, ...aisle }); + warps.push({ from: { cell: cb, side: b.side }, to: { cell: ca, side: a.side }, ...aisle }); } } else { for (let i = 0; i < placements.length; i++) { @@ -363,6 +373,7 @@ export function hasWarpLineOfSight( }; const EPS = 1e-9; for (const w of board.warps) { + if (!w.aisle && !board.sightThroughAllWarps) continue; const mouthA = w.from.cell; const sideA = w.from.side; const mouthB = w.to.cell; diff --git a/packages/engine/src/game.ts b/packages/engine/src/game.ts index e765258..830ef65 100644 --- a/packages/engine/src/game.ts +++ b/packages/engine/src/game.ts @@ -218,6 +218,8 @@ export interface GameConfig { * BIG MAN pushes occupants ahead, steps over floor hazards for 2 points, * and bars monsters from his square. Rev 6: ANTI-ANTI cannot pin a * teleport escape ("does not work against escape" — the card face). + * Rev 7: sight passes only through aisle-warp corners, not the lettered + * wraparounds ("no rule grants it; DIMENSIONAL WARP explicitly denies it"). */ deckRev?: number; } @@ -290,8 +292,17 @@ export interface GameState { /** The board with dynamic wall changes applied — use for movement and LOS. */ export function boardView(state: GameState): AssembledBoard { - if (Object.keys(state.edgeOverrides).length === 0) return state.board; - return { ...state.board, edges: { ...state.board.edges, ...state.edgeOverrides } }; + // Rev 7 restricts warp sight to the aisle corners; earlier games keep the + // wider sight their commands were validated against. + const legacySight = (state.config.deckRev ?? 1) < 7; + if (Object.keys(state.edgeOverrides).length === 0) { + return legacySight ? { ...state.board, sightThroughAllWarps: true } : state.board; + } + return { + ...state.board, + edges: { ...state.board.edges, ...state.edgeOverrides }, + ...(legacySight ? { sightThroughAllWarps: true } : {}), + }; } export function sustainedOn(state: GameState, playerId: PlayerId, cardId?: string): SustainedEffect[] { diff --git a/packages/engine/src/setups.ts b/packages/engine/src/setups.ts index f8a3b4d..75b579e 100644 --- a/packages/engine/src/setups.ts +++ b/packages/engine/src/setups.ts @@ -68,7 +68,7 @@ export function setupBoard(playerCount: number, rng: RngState): SetupResult { ]; const board = assembleBoard(placements, { warpPairs: [ - [{ sector: 0, side: "W" }, { sector: 1, side: "N" }], // AUTO WARP corner + [{ sector: 0, side: "W" }, { sector: 1, side: "N" }, "aisle"], // AUTO WARP corner [{ sector: 0, side: "N" }, { sector: 2, side: "S" }], [{ sector: 1, side: "W" }, { sector: 2, side: "E" }], [{ sector: 0, side: "E" }, { sector: 1, side: "S" }], @@ -106,10 +106,10 @@ export function setupBoard(playerCount: number, rng: RngState): SetupResult { warpPairs: [ [{ sector: 0, side: "N" }, { sector: 4, side: "S" }], // tip to tip [{ sector: 1, side: "W" }, { sector: 3, side: "E" }], - [{ sector: 0, side: "W" }, { sector: 1, side: "N" }], // aisle-warp corners - [{ sector: 0, side: "E" }, { sector: 3, side: "N" }], - [{ sector: 4, side: "W" }, { sector: 1, side: "S" }], - [{ sector: 4, side: "E" }, { sector: 3, side: "S" }], + [{ sector: 0, side: "W" }, { sector: 1, side: "N" }, "aisle"], // aisle-warp corners + [{ sector: 0, side: "E" }, { sector: 3, side: "N" }, "aisle"], + [{ sector: 4, side: "W" }, { sector: 1, side: "S" }, "aisle"], + [{ sector: 4, side: "E" }, { sector: 3, side: "S" }, "aisle"], ], }); return { board, rng: rngN }; diff --git a/packages/engine/test/board.test.ts b/packages/engine/test/board.test.ts index 2a711b7..c7cf5e7 100644 --- a/packages/engine/test/board.test.ts +++ b/packages/engine/test/board.test.ts @@ -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); }); }); diff --git a/packages/server/src/rooms.ts b/packages/server/src/rooms.ts index 6abaafc..a8590bd 100644 --- a/packages/server/src/rooms.ts +++ b/packages/server/src/rooms.ts @@ -46,7 +46,7 @@ export interface Room { const rooms = new Map(); /** Rules revision new games are dealt under (stored games keep their own). */ -const RULES_REV = 6; +const RULES_REV = 7; const ROOM_CODE_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; diff --git a/packages/web/src/local.svelte.ts b/packages/web/src/local.svelte.ts index 423889b..d7f90ff 100644 --- a/packages/web/src/local.svelte.ts +++ b/packages/web/src/local.svelte.ts @@ -101,7 +101,7 @@ class LocalGame { seed, sets: expansion ? ["basic", "expansion1"] : ["basic"], ...(colors ? { colors } : {}), - deckRev: 6, + deckRev: 7, }; const { state, events } = createGame(config); this.config = config;