Rev 27: an ILLUSIONARY ATTACK is priced as the spell it fakes, so BLUNT and the rest meet it (UPJE)

The believed illusion was dealt after the counter trail had already
spoken over a zero, so a BLUNT trailed for nothing and the full points
landed. Now the fake carries its spell's points through the pipeline and
the belief roll decides whether the weighed blow lands. Games dealt under
rev 26 and before replay as they were.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jm2auWk6RP71CjaAb4FMoG
This commit is contained in:
Eric Wagoner
2026-09-24 20:40:47 -04:00
co-authored by Claude Fable 5.1
parent 991a6a0443
commit c3f63d4eb7
2 changed files with 80 additions and 1 deletions
@@ -939,3 +939,55 @@ describe("the goat's ram is a physical blow like any other (rev 26)", () => {
expect(state.players.find((p) => p.id === mark)!.life).toBe(15 - distance);
});
});
describe("an illusion is priced as the spell it fakes (rev 27)", () => {
/** A faked FIREBALL met by the given counteraction, then both wizards pass. */
function fakedFireball(counter: string | null, deckRev?: number) {
let { state } = createGame({ playerIds: ["liar", "mark"], seed: 42, sets: ["basic", "expansion1"], ...(deckRev ? { deckRev } : {}) });
state = toRound2(state);
const { attacker, defender } = faceOff(state);
const ill = giveCard(state, attacker, "illusionary-attack");
if (counter) giveCard(state, defender, counter, "C", 0);
state = must(state, attacker, {
type: "cast", instanceId: ill.instanceId, target: { kind: "player", playerId: defender }, params: { cardId: "fireball" },
});
let done = applyCommand(state, defender, { type: "pass" });
if (counter) {
state = must(state, defender, { type: "counteract", instanceId: `${counter}#C` });
// The attacker's pass settles a total stop at once; otherwise the
// defender's pass closes the window.
done = applyCommand(state, attacker, { type: "pass" });
if (done.ok && done.state.stack) done = applyCommand(done.state, defender, { type: "pass" });
}
if (!done.ok) throw new Error(done.error);
const belief = done.events.find((e) => e.type === "illusionBelieved");
const believed = belief?.type === "illusionBelieved" ? belief.believed : null;
return { events: done.events, believed, life: done.state.players.find((p) => p.id === defender)!.life };
}
it("BLUNT halves a believed fake fireball to three, and disbelief costs nothing", () => {
const { events, believed, life } = fakedFireball("blunt");
expect(believed).not.toBeNull();
expect(events).toContainEqual(expect.objectContaining({ type: "attackResolved", incoming: 5 }));
expect(life).toBe(believed ? 12 : 15);
});
it("FULL SHIELD stops the fake outright: nothing to believe in, no die rolled", () => {
const { events, believed, life } = fakedFireball("full-shield");
expect(believed).toBeNull();
expect(events).toContainEqual(expect.objectContaining({ type: "attackResolved", fullyStopped: true }));
expect(life).toBe(15);
});
it("uncountered, a believed fake fireball still does its five", () => {
const { believed, life } = fakedFireball(null);
expect(believed).not.toBeNull();
expect(life).toBe(believed ? 10 : 15);
});
it("older games dealt a believed illusion's full points past a BLUNT", () => {
const { believed, life } = fakedFireball("blunt", 26);
expect(believed).not.toBeNull();
expect(life).toBe(believed ? 10 : 15);
});
});