The Ward is played in the moment (rules rev 31)

'When a player picks up one of your treasures, you may play at that
time (out of turn) this card on him' — the owner read the card and is
right: no arming ahead. When a treasure whose owner holds WARD is
grabbed, the grab hangs (new wardPending phase, outranking all other
input) and the owner alone answers: spring it (3 damage, card spent)
or let them go (card kept, silence). Automatons always spring it on a
thief of their gold. The arming mechanic is refused at rev 31 and its
button hidden; rev 3-30 games keep their armed wards and rev 1-2 keep
the automatic spring, replaying unchanged. The window does reveal that
the owner holds SOMETHING - as it would at any real table when the
room turns to look at them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0138A8CjeQRpvzKxuMfz1Bqc
This commit is contained in:
Eric Wagoner
2026-08-19 20:17:43 -04:00
co-authored by Claude Fable 5
parent b7f93fe454
commit 75d3d44c6c
8 changed files with 137 additions and 12 deletions
+50
View File
@@ -1,4 +1,5 @@
import { describe, expect, it } from "vitest";
import { giveCard } from "./helpers";
import {
applyCommand,
activePlayer,
@@ -262,3 +263,52 @@ describe("elimination by lost treasures drops what the fallen carried (rev 30)",
expect(btAfter.position).toEqual(cAfter.position);
});
});
describe("the Ward is played in the moment (rules rev 31)", () => {
function grabRig() {
let { state } = createGame({ playerIds: ["thief", "owner"], seed: 42, sets: ["basic"], deckRev: 31 });
const thief = state.players.find((p) => p.id === "thief")!;
const owner = state.players.find((p) => p.id === "owner")!;
giveCard(state, "owner", "ward", "W", 0);
const t = state.treasures.find((t) => t.owner === "owner" && t.position)!;
thief.position = { ...t.position! };
while (state.players[state.turn.activeIndex]!.id !== "thief") {
const r = applyCommand(state, state.players[state.turn.activeIndex]!.id, { type: "endTurn", draw: 0 });
if (!r.ok) throw new Error(r.error);
state = r.state;
}
const r = applyCommand(state, "thief", { type: "pickUpTreasure" });
if (!r.ok) throw new Error(r.error);
return { state: r.state, owner, thief };
}
it("the grab hangs on the owner; springing costs the thief 3 and the card", () => {
let { state } = grabRig();
expect(state.wardPending).toEqual({ ownerId: "owner", takerId: "thief" });
// Nobody else may act while it hangs.
expect(applyCommand(state, "thief", { type: "endTurn", draw: 0 }).ok).toBe(false);
const r = applyCommand(state, "owner", { type: "wardChoice", play: true });
if (!r.ok) throw new Error(r.error);
state = r.state;
expect(state.wardPending).toBeNull();
expect(state.players.find((p) => p.id === "thief")!.life).toBe(12);
expect(state.players.find((p) => p.id === "owner")!.hand.some((c) => c.cardId === "ward")).toBe(false);
});
it("declining lets the thief go, Ward still in hand", () => {
let { state } = grabRig();
const r = applyCommand(state, "owner", { type: "wardChoice", play: false });
if (!r.ok) throw new Error(r.error);
state = r.state;
expect(state.players.find((p) => p.id === "thief")!.life).toBe(15);
expect(state.players.find((p) => p.id === "owner")!.hand.some((c) => c.cardId === "ward")).toBe(true);
expect(applyCommand(state, "thief", { type: "endTurn", draw: 0 }).ok).toBe(true);
});
it("arming is refused in this vintage", () => {
let { state } = createGame({ playerIds: ["a", "b"], seed: 42, sets: ["basic"], deckRev: 31 });
giveCard(state, state.players[state.turn.activeIndex]!.id, "ward", "W", 0);
const r = applyCommand(state, state.players[state.turn.activeIndex]!.id, { type: "armWard", armed: true });
expect(r.ok).toBe(false);
});
});