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
+20 -1
View File
@@ -1206,6 +1206,25 @@
</div>
{/if}
{#if view?.wardPending}
{#if view.wardPending.ownerId === view.you}
<div class="scrim attack-scrim" role="alertdialog" aria-label="your treasure is taken">
<div class="attack-notice">
<div class="attack-title">{view.wardPending.takerId} grabs your treasure!</div>
<div class="attack-power">
"When a player picks up one of your treasures, you may play at
that time (out of turn) this card on him." Three points, and the
Ward is spent.
</div>
<div class="attack-actions">
<button class="stamp primary" onclick={() => dispatch({ type: "wardChoice", play: true })}>Spring the Ward!</button>
<button class="hint-cancel" onclick={() => dispatch({ type: "wardChoice", play: false })}>let them go</button>
</div>
</div>
</div>
{/if}
{/if}
{#if prefsOpen}
<div class="scrim attack-scrim" role="button" tabindex="-1"
onclick={() => (prefsOpen = false)} onkeydown={() => {}}>
@@ -2043,7 +2062,7 @@
onclick={() => dispatch({ type: "pickUpObject", instanceId: obj.instanceId })}>
Pick up {cardDef(obj.cardId).name}</button>
{/each}
{#if holdingWard}
{#if holdingWard && view.deckRev < 31}
<button class="stamp" onclick={() => dispatch({ type: "armWard", armed: !view.yourWardArmed })}>
{view.yourWardArmed ? "Stand down the ward" : "Set the ward"}</button>
{/if}
+1 -1
View File
@@ -136,7 +136,7 @@ class LocalGame {
seed,
sets: expansion ? ["basic", "expansion1"] : ["basic"],
...(colors ? { colors } : {}),
deckRev: 30,
deckRev: 31,
};
const { state, events } = createGame(config);
for (const e of events) {
+1
View File
@@ -61,6 +61,7 @@ export function humanize(e: GameEvent): string | null {
case "wallDestroyed": return e.wasDoor ? `A door is blasted to rubble!` : `A wall crumbles!`;
case "warpOpened": return `The outer wall breaches clean through — a new warp opens across the maze!`;
case "extraTurnGranted": return `${e.player} speeds up — extra turn banked.`;
case "wardWindow": return `The grab hangs in the air — ${e.owner} clutches something…`;
case "trapSprung": return e.cardId === "gift-from-below"
? `${e.player} draws GIFT FROM BELOW — it bites for 3, then deals again!`
: `${e.player} walked into an old TRAP! Lose a turn.`;