Strength gets its grip: tear a treasure from a wizard's arms

The card's second power was never built. New tearTreasure command: with
STRENGTH on you, in the same square as a carrier, the tear spends your
attack; the victim keeps the treasure only on a D4 roll of 1. Per the
FAQ it is not a pickup (your turn's actions continue), and full or
weakened arms tear it loose onto the floor instead. A torn treasure's
warded owner gets their window, blind grabs flail like blind punches,
and the automatons wrestle too. A new command breaks no stored ledger,
so live games gain the grip at once.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Eric Wagoner
2026-08-21 13:44:50 -04:00
co-authored by Claude Fable 5
parent 4a91d56d24
commit 76207c6264
6 changed files with 211 additions and 0 deletions
+12
View File
@@ -1021,6 +1021,13 @@
);
const carryingTreasure = $derived(me?.carriedTreasureId != null);
const treasureHere = $derived(treasuresHere.length > 0);
/** Co-located carriers STRENGTH lets you wrestle (the tear is an attack). */
const tearTargets = $derived(
view && me && view.sustained.some((e) => e.cardId === "strength" && e.targetId === view.you)
? view.players.filter((p) => p.alive && p.id !== view.you && p.carriedTreasureId &&
cellKey(p.position) === cellKey(me!.position))
: [],
);
/** Squares a selected L.O.S./ADJACENT card can reach; null = no dimming. */
// While an LOS attack sits on the stack, draw the line it traveled — the
// answer to "how can he even see me?" when sight ran through a warp mouth.
@@ -2120,6 +2127,11 @@
onclick={() => dispatch({ type: "pickUpObject", instanceId: obj.instanceId })}>
Pick up {cardDef(obj.cardId).name}</button>
{/each}
{#each tearTargets as t (t.id)}
<button class="stamp" disabled={view.turn.attackUsed || view.turn.round === 1}
onclick={() => dispatch({ type: "tearTreasure", targetId: t.id })}>
Tear the treasure from {t.id}'s arms</button>
{/each}
<button class="stamp" class:primary={punchWallMode} disabled={view.turn.attackUsed && !punchWallMode}
onclick={() => (punchWallMode = !punchWallMode)}>
{punchWallMode ? "Click the wall to punch — or cancel" : "Punch a wall…"}</button>
+2
View File
@@ -61,6 +61,8 @@ function describe(view: GameView, cmd: Command): string {
return `march the monster ${DIRECTION[cmd.direction]}`;
case "creatureAttack":
return `set the monster on ${cmd.targetId}`;
case "tearTreasure":
return `wrest the treasure from ${cmd.targetId}'s arms`;
case "setAmbush":
return `lay an ambush with ${nameOf(view, cmd.instanceId)}`;
case "discard":
+4
View File
@@ -62,6 +62,10 @@ export function humanize(e: GameEvent): string | null {
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 "treasureTorn": return e.toFloor
? `${e.attacker} TEARS the treasure from ${e.defender}'s arms — it tumbles to the floor!`
: `${e.attacker} TEARS the treasure from ${e.defender}'s arms!`;
case "tearResisted": return `${e.defender} clutches the treasure with white knuckles — ${e.attacker} comes away empty!`;
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.`;