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) {