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
+31
View File
@@ -720,4 +720,35 @@ describe("fear holds off monsters and unwilling feet alike", () => {
expect(r.ok).toBe(false);
if (!r.ok) expect(r.error).toContain("dread");
});
it("a wizard caged inside the dread may round a corner to escape", () => {
let { state } = createGame({ playerIds: ["a", "b"], seed: 42, sets: ["basic", "expansion1"] });
const a = state.players.find((p) => p.id === "a")!;
const b = state.players.find((p) => p.id === "b")!;
pushSustained(state, {
id: "fx-fear", cardId: "fear", casterId: "b", targetId: "b",
remainingTurns: 5, data: {},
});
// b radiates dread from (2,5). a stands in a dead-end pocket at
// (2,3): walls on three sides, so the only way out steps SOUTH —
// closer to b — before the corridor east leads clear of the dread.
b.position = { x: 2, y: 5 };
a.position = { x: 2, y: 3 };
for (const side of ["N", "E", "W"] as const) {
state.edgeOverrides[edgeKey({ x: 2, y: 3 }, side)] = "wall";
}
state.edgeOverrides[edgeKey({ x: 2, y: 3 }, "S")] = "open";
state.edgeOverrides[edgeKey({ x: 2, y: 4 }, "E")] = "open";
state.edgeOverrides[edgeKey({ x: 3, y: 4 }, "E")] = "open";
state.edgeOverrides[edgeKey({ x: 4, y: 4 }, "N")] = "open";
while (state.players[state.turn.activeIndex]!.id !== "a") {
const r0 = applyCommand(state, state.players[state.turn.activeIndex]!.id, { type: "endTurn", draw: 0 });
if (!r0.ok) throw new Error(r0.error);
state = r0.state;
}
// The closer step is the only way out: permitted.
const r = applyCommand(state, "a", { type: "move", direction: "S" });
if (!r.ok) throw new Error("escape step refused: " + r.error);
expect(r.ok).toBe(true);
});
});