A warp mouth is a wallable line — and the tunnel seals whole

Create Wall refused the board-edge openings: no far cell, so "walls
must be created between two spaces". But bricking over a lettered
opening is a classic play, and the far space is the tunnel itself.
A warp mouth now counts as wallable — with the wall raised at BOTH
mouths, since a tunnel has one passage: no walking in from the far
board, no warp sight through a sealed pair, and tearing down either
end reopens both. The client grows edge hitboxes at the perimeter
mouths so the line is clickable at all.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Eric Wagoner
2026-08-17 17:22:49 -04:00
co-authored by Claude Fable 5
parent bed7d849eb
commit 766ec0a726
4 changed files with 87 additions and 2 deletions
+40 -1
View File
@@ -9,7 +9,7 @@ import {
type SectorPlacement,
} from "../src/board";
import { buildDeck } from "../src/cards";
import { activePlayer, applyCommand, createGame } from "../src/game";
import { activePlayer, applyCommand, boardView, createGame, gameLos } from "../src/game";
import { createRng } from "../src/rng";
import { setupBoard } from "../src/setups";
import { giveCard } from "./helpers";
@@ -240,3 +240,42 @@ describe("the aisle warp treats its corner as adjacent — sight included", () =
expect(offAxis).toBeGreaterThan(0);
});
});
describe("bricking over a warp mouth", () => {
it("create wall seals a lettered opening: no passage, no sight", () => {
let { state } = createGame({ playerIds: ["a", "b"], seed: 42, sets: ["basic"], deckRev: 18 });
// round 1 passes
for (let i = 0; i < 2; i++) {
const r = applyCommand(state, activePlayer(state).id, { type: "endTurn", draw: 0 });
if (!r.ok) throw new Error(r.error);
state = r.state;
}
const w = state.board.warps[0]!;
const caster = activePlayer(state);
caster.position = { ...w.from.cell };
caster.hand[0] = { instanceId: "cw#1", cardId: "create-wall" };
const r = applyCommand(state, caster.id, {
type: "cast", instanceId: "cw#1",
target: { kind: "edge", cell: w.from.cell, side: w.from.side },
});
if (!r.ok) throw new Error(r.error);
state = r.state;
// Passage: stepping into the warp is now a wall.
const step = stepTarget(boardView(state), w.from.cell, w.from.side);
expect(step.kind).toBe("blocked");
// Sight: the far mouth no longer carries warp sight.
expect(gameLos(state, w.from.cell, w.to.cell)).toBe(false);
// One tunnel: the FAR mouth is walled too, and unwalling it reopens both.
const farStep = stepTarget(boardView(state), w.to.cell, w.to.side);
expect(farStep.kind).toBe("blocked");
const caster2 = activePlayer(state);
caster2.position = { ...w.to.cell };
caster2.hand[0] = { instanceId: "dw#1", cardId: "destroy-wall" };
const r2 = applyCommand(state, caster2.id, {
type: "cast", instanceId: "dw#1",
target: { kind: "edge", cell: w.to.cell, side: w.to.side },
});
if (!r2.ok) throw new Error(r2.error);
expect(stepTarget(boardView(r2.state), w.from.cell, w.from.side).kind).toBe("warp");
});
});