The reflected-attack window (rules rev 25)
A FULL REFLECTION's returned spell is a fresh attack on its own caster — who, per the owner's ruling, gets a defender's counteraction window against it (absorb the blast, shield it, or FULL REFLECTION again: the ping-pong war ends when the cards run out). The reflection now settles into a new stack with the roles swapped and the damage pre-priced (numbers, amplifies, and surviving counters already applied); the blow returns to its sender unerringly, with no fresh hit rolls. onResolved effects run with the swapped roles, so a reflected IDIOT now afflicts its own caster. Earlier revisions land the blow instantly and stored games replay unchanged. Automatons price the incoming reflected damage off the stack; the attack modal announces 'Your own spell comes screaming back!' with the inbound total. 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
dcaf358a93
commit
06faea0c79
@@ -962,3 +962,64 @@ describe("empathy mirrors resolution-hook blows (rules rev 16)", () => {
|
||||
expect(15 - aAfter.life).toBe(15 - dAfter.life);
|
||||
});
|
||||
});
|
||||
|
||||
describe("the reflected-attack window (rules rev 25)", () => {
|
||||
function reflectRig() {
|
||||
let { state } = createGame({ playerIds: ["a", "b"], seed: 42, sets: ["basic"], deckRev: 25 });
|
||||
state = toRound2(state);
|
||||
const { attacker, defender } = faceOff(state);
|
||||
const sd = giveCard(state, attacker, "sudden-death");
|
||||
giveCard(state, defender, "full-reflection", "FR", 0);
|
||||
state = must(state, attacker, {
|
||||
type: "cast", instanceId: sd.instanceId, target: { kind: "player", playerId: defender },
|
||||
});
|
||||
state = must(state, defender, { type: "counteract", instanceId: "full-reflection#FR" });
|
||||
// The attacker's pass meets the total stop and settles the reflection —
|
||||
// which opens the returned spell's window, waiting on the caster.
|
||||
state = must(state, attacker, { type: "pass" });
|
||||
return { state, attacker, defender };
|
||||
}
|
||||
|
||||
it("the returned spell opens a window: absorb soaks the reflected blast", () => {
|
||||
let { state, attacker, defender } = reflectRig();
|
||||
// The reflection settled into a NEW stack: the caster now defends.
|
||||
expect(state.stack).not.toBeNull();
|
||||
expect(state.stack!.defenderId).toBe(attacker);
|
||||
expect(state.stack!.reflectedBase).toEqual({ damage: 10, duration: 0 });
|
||||
giveCard(state, attacker, "absorb", "AB", 0);
|
||||
state = must(state, attacker, { type: "counteract", instanceId: "absorb#AB" });
|
||||
state = must(state, defender, { type: "pass" });
|
||||
state = must(state, attacker, { type: "pass" });
|
||||
// 10 reflected minus absorb's 3.
|
||||
expect(state.players.find((p) => p.id === attacker)!.life).toBe(8);
|
||||
expect(state.players.find((p) => p.id === defender)!.life).toBe(15);
|
||||
});
|
||||
|
||||
it("a second full reflection turns it around again — the ping-pong war", () => {
|
||||
let { state, attacker, defender } = reflectRig();
|
||||
giveCard(state, attacker, "full-reflection", "FR2", 0);
|
||||
state = must(state, attacker, { type: "counteract", instanceId: "full-reflection#FR2" });
|
||||
state = must(state, defender, { type: "pass" });
|
||||
// Turned around again: now the original defender must answer it.
|
||||
expect(state.stack).not.toBeNull();
|
||||
expect(state.stack!.defenderId).toBe(defender);
|
||||
state = must(state, defender, { type: "pass" });
|
||||
expect(state.players.find((p) => p.id === defender)!.life).toBe(5);
|
||||
expect(state.players.find((p) => p.id === attacker)!.life).toBe(15);
|
||||
});
|
||||
|
||||
it("rev 24 lands the reflected blow instantly, as stored games replay", () => {
|
||||
let { state } = createGame({ playerIds: ["a", "b"], seed: 42, sets: ["basic"], deckRev: 24 });
|
||||
state = toRound2(state);
|
||||
const { attacker, defender } = faceOff(state);
|
||||
const sd = giveCard(state, attacker, "sudden-death");
|
||||
giveCard(state, defender, "full-reflection", "FR", 0);
|
||||
state = must(state, attacker, {
|
||||
type: "cast", instanceId: sd.instanceId, target: { kind: "player", playerId: defender },
|
||||
});
|
||||
state = must(state, defender, { type: "counteract", instanceId: "full-reflection#FR" });
|
||||
state = must(state, attacker, { type: "pass" });
|
||||
expect(state.stack).toBeNull();
|
||||
expect(state.players.find((p) => p.id === attacker)!.life).toBe(5);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user