Fireball burns only the stones in play (rules rev 33)
'Destroys all magical stones an opponent is CARRYING' — the owner's ruling: a stone hidden in the hand is a secret card, not a carried stone. Only displayed stones burn; the old all-hand burning also leaked hidden information by announcing cards nobody knew existed. Earlier games replay their broader fire. All 42 production ledgers verified before deploy. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0138A8CjeQRpvzKxuMfz1Bqc
This commit is contained in:
co-authored by
Claude Fable 5
parent
1cf5d0a68d
commit
b67f2a4c8a
@@ -840,9 +840,18 @@ const CARD_EFFECTS: Record<string, AttackEffect | NeutralEffect | CounterEffect>
|
|||||||
baseDamage: () => 5,
|
baseDamage: () => 5,
|
||||||
onResolved: (ctx) => {
|
onResolved: (ctx) => {
|
||||||
if (ctx.damageDealt <= 0) return;
|
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;
|
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(
|
ctx.defender.displayed = ctx.defender.displayed.filter(
|
||||||
(id) => !stones.some((s) => s.instanceId === id),
|
(id) => !stones.some((s) => s.instanceId === id),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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);
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export interface Room {
|
|||||||
const rooms = new Map<string, Room>();
|
const rooms = new Map<string, Room>();
|
||||||
|
|
||||||
/** Rules revision new games are dealt under (stored games keep their own). */
|
/** 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";
|
const ROOM_CODE_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
||||||
|
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ class LocalGame {
|
|||||||
seed,
|
seed,
|
||||||
sets: expansion ? ["basic", "expansion1"] : ["basic"],
|
sets: expansion ? ["basic", "expansion1"] : ["basic"],
|
||||||
...(colors ? { colors } : {}),
|
...(colors ? { colors } : {}),
|
||||||
deckRev: 32,
|
deckRev: 33,
|
||||||
};
|
};
|
||||||
const { state, events } = createGame(config);
|
const { state, events } = createGame(config);
|
||||||
for (const e of events) {
|
for (const e of events) {
|
||||||
|
|||||||
Reference in New Issue
Block a user