diff --git a/packages/engine/src/game.ts b/packages/engine/src/game.ts index 5d082c3..dcabb59 100644 --- a/packages/engine/src/game.ts +++ b/packages/engine/src/game.ts @@ -840,9 +840,18 @@ const CARD_EFFECTS: Record baseDamage: () => 5, onResolved: (ctx) => { if (ctx.damageDealt <= 0) return; - const stones = ctx.defender.hand.filter((c) => isMagicStone(c.cardId)); + // "Destroys all magical stones an opponent is CARRYING": a stone + // still hidden in the hand is a secret card, not a carried stone — + // only displayed stones burn (rules rev 33; earlier games burned + // the hand's hidden stones too and replay so). + const rev33 = (ctx.state.config.deckRev ?? 1) >= 33; + const stones = ctx.defender.hand.filter((c) => + isMagicStone(c.cardId) && + (!rev33 || ctx.defender.displayed.includes(c.instanceId))); if (stones.length === 0) return; - ctx.defender.hand = ctx.defender.hand.filter((c) => !isMagicStone(c.cardId)); + ctx.defender.hand = ctx.defender.hand.filter( + (c) => !stones.some((s) => s.instanceId === c.instanceId), + ); ctx.defender.displayed = ctx.defender.displayed.filter( (id) => !stones.some((s) => s.instanceId === id), ); diff --git a/packages/engine/test/casting.test.ts b/packages/engine/test/casting.test.ts index bf4ebbf..af520df 100644 --- a/packages/engine/test/casting.test.ts +++ b/packages/engine/test/casting.test.ts @@ -1065,3 +1065,21 @@ describe("a reflected lightning blast ends the caster's turn (rules rev 28)", () expect(state.players.find((p) => p.id === attacker)!.hand.length).toBeGreaterThan(before); }); }); + +describe("fireball burns only the stones in play (rules rev 33)", () => { + it("a displayed stone dies; a hidden one stays secret and safe", () => { + let { state } = createGame({ playerIds: ["a", "b"], seed: 42, sets: ["basic"], deckRev: 33 }); + state = toRound2(state); + const { attacker, defender } = faceOff(state); + const fb = giveCard(state, attacker, "fireball"); + 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.displayed = ["powerstone#T"]; // only the powerstone is on the table + state = must(state, attacker, { type: "cast", instanceId: fb.instanceId, target: { kind: "player", playerId: defender } }); + state = must(state, defender, { type: "pass" }); + const after = state.players.find((p) => p.id === defender)!; + expect(after.hand.some((c) => c.instanceId === "powerstone#T")).toBe(false); + expect(after.hand.some((c) => c.instanceId === "speedstone#T")).toBe(true); + }); +}); diff --git a/packages/server/src/rooms.ts b/packages/server/src/rooms.ts index 7b6e978..4770524 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 = 32; +const RULES_REV = 33; const ROOM_CODE_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; diff --git a/packages/web/src/local.svelte.ts b/packages/web/src/local.svelte.ts index c02a272..f350bb9 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: 32, + deckRev: 33, }; const { state, events } = createGame(config); for (const e of events) {