From e8c25ddc19f15c5a53e380921db6a30441304c6a Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Wed, 19 Aug 2026 22:22:45 -0400 Subject: [PATCH] Stone Dead counts only the stones in play (rules rev 34) Same reading as fireball's rev 33: 'carrying' means displayed. Counting hidden hand stones both inflated the damage and broadcast the secret count in the total. The automatons' estimate (displayed as the visible floor) is now simply the truth. All production ledgers verified clean before deploy. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_0138A8CjeQRpvzKxuMfz1Bqc --- packages/engine/src/game.ts | 10 ++++++++-- packages/engine/test/casting.test.ts | 23 +++++++++++++++++++++++ packages/server/src/rooms.ts | 2 +- packages/web/src/local.svelte.ts | 2 +- 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/packages/engine/src/game.ts b/packages/engine/src/game.ts index dcabb59..17a8efc 100644 --- a/packages/engine/src/game.ts +++ b/packages/engine/src/game.ts @@ -5402,9 +5402,15 @@ function resolveStack(state: GameState, events: GameEvent[]): void { if (attackId === "fireball" && sustainedOn(state, defender.id, "sticky-web").length > 0) { base += 2; } - // STONE DEAD: number x the stones the defender carries. + // STONE DEAD: number x the stones the defender carries — and "carrying" + // means displayed, as with fireball's burning (rules rev 34): a hidden + // stone is a secret card, and counting it would broadcast the secret in + // the damage total. Earlier games counted the whole hand and replay so. if (attackId === "stone-dead") { - base = (stack.numberValue ?? 1) * defender.hand.filter((c) => isMagicStone(c.cardId)).length; + const inPlay = (state.config.deckRev ?? 1) >= 34 + ? defender.hand.filter((c) => isMagicStone(c.cardId) && defender.displayed.includes(c.instanceId)) + : defender.hand.filter((c) => isMagicStone(c.cardId)); + base = (stack.numberValue ?? 1) * inPlay.length; } base *= stack.amplifyFactor; base += stack.powerAttackPoints; diff --git a/packages/engine/test/casting.test.ts b/packages/engine/test/casting.test.ts index af520df..3c0a0c2 100644 --- a/packages/engine/test/casting.test.ts +++ b/packages/engine/test/casting.test.ts @@ -1083,3 +1083,26 @@ describe("fireball burns only the stones in play (rules rev 33)", () => { expect(after.hand.some((c) => c.instanceId === "speedstone#T")).toBe(true); }); }); + +describe("stone dead counts only the stones in play (rules rev 34)", () => { + it("hidden stones neither add damage nor betray their count", () => { + let { state } = createGame({ playerIds: ["a", "b"], seed: 42, sets: ["basic", "expansion1"], deckRev: 34 }); + state = toRound2(state); + const { attacker, defender } = faceOff(state); + const sd = giveCard(state, attacker, "stone-dead"); + giveCard(state, attacker, "number-3", "N", 1); + const d = state.players.find((p) => p.id === defender)!; + d.hand[0] = { instanceId: "powerstone#T", cardId: "powerstone" }; + d.hand[1] = { instanceId: "speedstone#T", cardId: "speedstone" }; + d.hand[2] = { instanceId: "bloodstone#T", cardId: "bloodstone" }; + d.displayed = ["powerstone#T"]; // one on the table, two secret + state = must(state, attacker, { + type: "cast", instanceId: sd.instanceId, numberInstanceIds: ["number-3#N"], + target: { kind: "player", playerId: defender }, + }); + state = must(state, defender, { type: "pass" }); + // 3 x 1 displayed = 3 damage... minus the displayed bloodstone? It is + // hidden, so no soak either: 15 - 3 = 12. + expect(state.players.find((p) => p.id === defender)!.life).toBe(12); + }); +}); diff --git a/packages/server/src/rooms.ts b/packages/server/src/rooms.ts index 4770524..5d0f86c 100644 --- a/packages/server/src/rooms.ts +++ b/packages/server/src/rooms.ts @@ -54,7 +54,7 @@ export interface Room { const rooms = new Map(); /** Rules revision new games are dealt under (stored games keep their own). */ -const RULES_REV = 33; +const RULES_REV = 34; const ROOM_CODE_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; diff --git a/packages/web/src/local.svelte.ts b/packages/web/src/local.svelte.ts index f350bb9..f71224d 100644 --- a/packages/web/src/local.svelte.ts +++ b/packages/web/src/local.svelte.ts @@ -136,7 +136,7 @@ class LocalGame { seed, sets: expansion ? ["basic", "expansion1"] : ["basic"], ...(colors ? { colors } : {}), - deckRev: 33, + deckRev: 34, }; const { state, events } = createGame(config); for (const e of events) {