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,
|
||||
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),
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -54,7 +54,7 @@ export interface Room {
|
||||
const rooms = new Map<string, Room>();
|
||||
|
||||
/** 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";
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user