Three rulings from the night games (rules rev 2)

Teleport wraps the maze's rim: its four counted squares now step
through warp mouths like any doorway, so a wizard can blink off one
side of the board and onto the other.

A wizard at a door's threshold may pull it open and peek — lock
removed, door unlocked this turn, or PICK LOCK / MASTER KEY in hand —
without stepping through. The door still hangs shut to everyone down
the hallway; only a HELD door is propped open for every eye. Gated
behind rules rev 2 (now sourced from the engine's CURRENT_RULES_REV)
so stored games replay with their doors dark; the client's sight
mirror learns the same peek.

Fear no longer nails a cornered wizard to the floor: a step closer to
the dread is allowed when it walks the shortest way out of it, so
escape can round a corner. Standing outside and stepping in is still
refused.

All 15 production ledgers verified before deploy; 269 engine tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015RCWSTnb1KYTPyL4GmhGnF
This commit is contained in:
Eric Wagoner
2026-08-25 14:28:12 -04:00
co-authored by Claude Fable 5
parent 2932d1e500
commit 154e00141c
6 changed files with 241 additions and 45 deletions
@@ -445,7 +445,7 @@ describe("a held door is an open doorway to the eye", () => {
expect(state.players.find((p) => p.id === pursuer)!.life).toBeLessThan(lifeBefore);
});
it("an unheld unlocked door still blocks sight", () => {
it("an unlocked door is an open doorway to the wizard at its threshold", () => {
let { state, cell, side, holder, pursuer } = sightRig();
const pick = giveCard(state, holder, "pick-lock");
state = must(state, holder, {
@@ -453,9 +453,56 @@ describe("a held door is an open doorway to the eye", () => {
target: { kind: "edge", cell, side },
});
const fb = giveCard(state, holder, "fireball", "FB", 1);
const refused = applyCommand(state, holder, {
const cast = applyCommand(state, holder, {
type: "cast", instanceId: fb.instanceId, target: { kind: "player", playerId: pursuer },
});
expect(cast.ok).toBe(true);
});
it("a lock REMOVED lets any wizard beside the door peek through", () => {
let { state, cell, side, holder, pursuer } = sightRig();
const rm = giveCard(state, holder, "remove-lock");
state = must(state, holder, {
type: "cast", instanceId: rm.instanceId,
target: { kind: "edge", cell, side },
});
const fb = giveCard(state, holder, "fireball", "FB", 1);
const cast = applyCommand(state, holder, {
type: "cast", instanceId: fb.instanceId, target: { kind: "player", playerId: pursuer },
});
expect(cast.ok).toBe(true);
});
it("PICK LOCK in hand is enough to ease a locked door open a crack", () => {
let { state, cell, side, holder, pursuer } = sightRig();
giveCard(state, holder, "pick-lock");
const fb = giveCard(state, holder, "fireball", "FB", 1);
const cast = applyCommand(state, holder, {
type: "cast", instanceId: fb.instanceId, target: { kind: "player", playerId: pursuer },
});
expect(cast.ok).toBe(true);
});
it("the wizard down the hallway sees no further than the shut door", () => {
let { state, cell, side, holder, pursuer } = sightRig();
// Pull the pursuer one square further down the hall, so the door is
// no longer on their threshold; clear their path to the doorway.
const near = neighbor(cell, side);
const far = neighbor(near, side);
if (!boardView(state).cells[cellKey(far)]) return; // the maze ends here; geometry unavailable
state.players.find((p) => p.id === pursuer)!.position = far;
state.edgeOverrides[edgeKey(near, side)] = "open";
const rm = giveCard(state, holder, "remove-lock");
state = must(state, holder, {
type: "cast", instanceId: rm.instanceId,
target: { kind: "edge", cell, side },
});
state = must(state, holder, { type: "endTurn", draw: 0 });
const fb = giveCard(state, pursuer, "fireball", "FB", 1);
const refused = applyCommand(state, pursuer, {
type: "cast", instanceId: fb.instanceId, target: { kind: "player", playerId: holder },
});
expect(refused.ok).toBe(false);
if (!refused.ok) expect(refused.error).toContain("line of sight");
});
});