Safes finally open to keys — and the clockwork stops clawing at the lid

'All LOCK-type cards will work on it' was never wired: openSafes was
reset every turn and filled by nothing. A PICK LOCK or MASTER KEY
aimed at a safe's square (underfoot or beside) now opens it until
turn's end, with its own chronicle line; the dead never-emitted
safeOpened variant and its null humanize go. The automaton — H4EN's
Automaton II spent six turns grabbing at a locked lid saying 'I meant
to do that' — now cracks the box first (key or DISPEL CREATION, which
it held the whole time) and drops unopenable safes from its goals
rather than marching to one forever.

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-09-01 09:22:56 -04:00
co-authored by Claude Fable 5
parent 12ece6065a
commit eabc10f18c
6 changed files with 119 additions and 3 deletions
+36
View File
@@ -1008,6 +1008,42 @@ describe("interception, escape, and hazard sense", () => {
expect(applyCommand(state, "bot", cmd!).ok).toBe(true);
});
it("cracks a safe over the prize instead of grabbing at the lid forever", () => {
let { state } = createGame({ playerIds: ["foe", "bot"], seed: 42, sets: ["basic", "expansion1"] });
state = toBot(state);
const bot = state.players.find((p) => p.id === "bot")!;
const chest = state.treasures.find((t) => t.owner === "foe" && t.position)!;
state.squareContents[cellKey(chest.position!)] = { kind: "safe", damage: 0, createdBy: "foe" };
bot.position = { ...chest.position! };
bot.hand = [{ cardId: "dispel-creation", instanceId: "DC" }];
const cmd = automatonCommand(viewFor(state, "bot"), "hunter", "archmage");
expect(cmd).toMatchObject({ type: "cast", instanceId: "DC", target: { kind: "cell" } });
const r = applyCommand(state, "bot", cmd!);
expect(r.ok).toBe(true);
// The box gone, the very next thought is the grab.
const next = automatonCommand(viewFor(r.state, "bot"), "hunter", "archmage");
expect(next).toMatchObject({ type: "pickUpTreasure" });
});
it("never proposes the doomed grab on a safe it cannot open", () => {
let { state } = createGame({ playerIds: ["foe", "bot"], seed: 42, sets: ["basic", "expansion1"] });
state = toBot(state);
const bot = state.players.find((p) => p.id === "bot")!;
const chest = state.treasures.find((t) => t.owner === "foe" && t.position)!;
state.squareContents[cellKey(chest.position!)] = { kind: "safe", damage: 0, createdBy: "foe" };
bot.position = { ...chest.position! };
bot.hand = [{ cardId: "number-3", instanceId: "N3" }];
for (let i = 0; i < 8; i++) {
const cmd = automatonCommand(viewFor(state, "bot"), "hunter", "archmage");
if (!cmd) break;
expect(cmd.type).not.toBe("pickUpTreasure");
const r = applyCommand(state, "bot", cmd);
expect(r.ok).toBe(true);
state = r.state;
if (cmd.type === "endTurn") break;
}
});
it("a pressed carrier of any temperament turns to mist", () => {
let { state } = createGame({ playerIds: ["foe", "bot"], seed: 42, sets: ["basic", "expansion1"] });
state = toBot(state);