Rev 21: LIFESAVER saves, and FORCE FIELD stands for the turn
An audit of every card in the deck, prompted by the Alter Ego, found two more whose cast worked but whose promise was never kept. LIFESAVER — "immune to the effects of losing both of your treasures" — was cast and remembered and never asked where a wizard is eliminated for exactly that; twenty-three recorded casts never mattered. FORCE FIELD stopped the spell and vanished, though the card keeps it standing until the opponent's turn ends, barring them from entering its caster's square or casting on or past them. Both are honored from rev 21, the field on every side where the card says one. Older games replay as they were played. Tests pin both readings. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jm2auWk6RP71CjaAb4FMoG
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
10d5c5815d
commit
85690f0e23
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { applyCommand, activePlayer, boardView, createGame, gameLos, sustainedOn, walkingDistance, type GameState } from "../src/game";
|
||||
import { cellKey, edgeKey, stepTarget, type Cell } from "../src/board";
|
||||
import { cellKey, edgeKey, opposite, SIDES, stepTarget, type Cell, type Side } from "../src/board";
|
||||
import type { CardInstance } from "../src/cards";
|
||||
import { newExpansionGame as newGame, must, drain, giveCard, toRound2, faceOff, castAt } from "./helpers";
|
||||
|
||||
@@ -843,3 +843,55 @@ describe("butt-head's charge reaches as far as its legs (K4T3)", () => {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe("cards that keep their whole promise (rev 21)", () => {
|
||||
it("LIFESAVER's holder survives losing both treasures", () => {
|
||||
for (const [deckRev, alive] of [[21, true], [20, false]] as const) {
|
||||
let { state } = createGame({ playerIds: ["alice", "bob", "carol"], seed: 42, sets: ["basic", "expansion1"], deckRev });
|
||||
const me = activePlayer(state);
|
||||
const others = state.players.filter((p) => p.id !== me.id);
|
||||
const card = giveCard(state, me.id, "lifesaver");
|
||||
state = must(state, me.id, { type: "cast", instanceId: card.instanceId });
|
||||
// Both of the holder's treasures rest on two different enemy homes: nobody wins, the holder is done for.
|
||||
const mine = state.treasures.filter((t) => t.owner === me.id);
|
||||
mine[0]!.position = { ...others[0]!.home };
|
||||
mine[1]!.position = { ...others[1]!.home };
|
||||
state = must(state, me.id, { type: "endTurn", draw: 0 });
|
||||
expect(state.players.find((p) => p.id === me.id)!.alive).toBe(alive);
|
||||
}
|
||||
});
|
||||
|
||||
it("FORCE FIELD stands until the opponent's turn ends: no entering, no casting on its caster", () => {
|
||||
for (const deckRev of [21, 20]) {
|
||||
let { state } = createGame({ playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"], deckRev });
|
||||
state = toRound2(state);
|
||||
const { attacker, defender } = faceOff(state);
|
||||
const a = state.players.find((p) => p.id === attacker)!;
|
||||
const d = state.players.find((p) => p.id === defender)!;
|
||||
// Stand the attacker beside the defender, an open edge between.
|
||||
const view = boardView(state);
|
||||
const side = SIDES.find((s: Side) => { const t = stepTarget(view, d.position, s); return t.kind === "step" && !state.squareContents[cellKey(t.to)]; })!;
|
||||
const beside = stepTarget(view, d.position, side);
|
||||
if (beside.kind === "blocked") throw new Error("no open side");
|
||||
a.position = { ...beside.to };
|
||||
const fb = giveCard(state, attacker, "fireball");
|
||||
d.hand[0] = { instanceId: "force-field#T", cardId: "force-field" };
|
||||
state = must(state, attacker, { type: "cast", instanceId: fb.instanceId, target: { kind: "player", playerId: defender } });
|
||||
state = must(state, defender, { type: "counteract", instanceId: "force-field#T" });
|
||||
state = must(state, attacker, { type: "pass" });
|
||||
if (state.stack) state = must(state, defender, { type: "pass" });
|
||||
expect(state.players.find((p) => p.id === defender)!.life).toBe(15);
|
||||
const step = applyCommand(state, attacker, { type: "move", direction: opposite(side) });
|
||||
if (deckRev >= 21) {
|
||||
expect(step.ok).toBe(false);
|
||||
if (!step.ok) expect(step.error).toMatch(/force field/);
|
||||
expect(state.sustained.some((f) => f.cardId === "force-field")).toBe(true);
|
||||
const after = must(state, attacker, { type: "endTurn", draw: 0 });
|
||||
expect(after.sustained.some((f) => f.cardId === "force-field")).toBe(false);
|
||||
} else {
|
||||
expect(step.ok).toBe(true);
|
||||
expect(state.sustained.some((f) => f.cardId === "force-field")).toBe(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user