Rules rev 4: a creature's blow can be counteracted
The WRAITH card assumes the window exists — "REFLECTIONs used on the
wraith's touch will damage the wraith" — but creature damage applied
instantly, so BLUNT had nothing to catch, as playtesting found. Under
revision 4 every creature blow against a wizard opens the same
counteraction stack a spell does: the wraith's entry touch, the
democratic monster's claw, and commanded troll/skeleton/shadow
attacks. The blow's damage rides the stack; BLUNT halves it (round
up); reflections work by name against creatures — half back for
REFLECTION, the whole blow for FULL REFLECTION — landing on the
creature, not its controller; FULL SHIELD correctly does nothing
("does not stop any physical attack"); the wraith's card theft is a
secondary effect that lands only if damage does. No aim-miss rolls
against invisible or shrunk defenders — the creature is already in
the square. The attack fanfare shows the creature's own card.
Earlier revisions keep the instant touch so every stored game — and
the live one that found this — replays unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
3f3e6d9853
commit
d1eace9530
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { applyCommand, activePlayer, boardView, creatureAt, type GameState, type PlayerId } from "../src/game";
|
||||
import { applyCommand, activePlayer, boardView, creatureAt, type GameState, type PlayerId, createGame } from "../src/game";
|
||||
import { cellKey, SIDES, stepTarget, type Side } from "../src/board";
|
||||
import type { CardInstance } from "../src/cards";
|
||||
import { newExpansionGame as newGame, must, giveCard, toRound2, emptyNeighborCell } from "./helpers";
|
||||
@@ -282,3 +282,62 @@ describe("expansion support cards", () => {
|
||||
expect(state.creatures.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("counteracting a creature's blow (rules rev 4)", () => {
|
||||
function rev4() {
|
||||
return createGame({ playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"], deckRev: 4 });
|
||||
}
|
||||
function wraithOnVictim(state: GameState) {
|
||||
state = toRound2(state);
|
||||
const controller = activePlayer(state);
|
||||
const victim = state.players.find((p) => p.id !== controller.id)!;
|
||||
const spot = emptyNeighborCell(state, controller.position);
|
||||
state.creatures.push({
|
||||
id: "w1", kind: "wraith", controllerId: controller.id, position: { ...controller.position },
|
||||
damage: 0, maxDamage: 4, movesPerTurn: 3, movementUsed: 0,
|
||||
wallPassesPerTurn: 1, wallPassUsed: 0, attackUsed: false, justCreated: false, scorchedThisTurn: [],
|
||||
});
|
||||
victim.position = { ...spot.cell };
|
||||
return { state, controller: controller.id, victim: victim.id, side: spot.side };
|
||||
}
|
||||
|
||||
it("BLUNT halves the wraith's touch; the card theft still lands", () => {
|
||||
let { state } = rev4();
|
||||
const rig = wraithOnVictim(state);
|
||||
state = rig.state;
|
||||
giveCard(state, rig.victim, "blunt", "B", 0);
|
||||
const handBefore = state.players.find((p) => p.id === rig.victim)!.hand.filter(Boolean).length;
|
||||
state = must(state, rig.controller, { type: "moveCreature", creatureId: "w1", direction: rig.side });
|
||||
expect(state.stack?.creatureId).toBe("w1");
|
||||
state = must(state, rig.victim, { type: "counteract", instanceId: "blunt#B" });
|
||||
state = must(state, rig.controller, { type: "pass" });
|
||||
state = must(state, rig.victim, { type: "pass" });
|
||||
const v = state.players.find((p) => p.id === rig.victim)!;
|
||||
expect(v.life).toBe(14); // 2 halved up to 1
|
||||
expect(v.hand.length).toBe(handBefore - 2); // blunt spent + a card stolen
|
||||
});
|
||||
|
||||
it("FULL REFLECTION turns the touch back on the wraith, theft and all", () => {
|
||||
let { state } = rev4();
|
||||
const rig = wraithOnVictim(state);
|
||||
state = rig.state;
|
||||
giveCard(state, rig.victim, "full-reflection", "FR", 0);
|
||||
state = must(state, rig.controller, { type: "moveCreature", creatureId: "w1", direction: rig.side });
|
||||
state = must(state, rig.victim, { type: "counteract", instanceId: "full-reflection#FR" });
|
||||
state = must(state, rig.controller, { type: "pass" });
|
||||
state = must(state, rig.victim, { type: "pass" });
|
||||
const v = state.players.find((p) => p.id === rig.victim)!;
|
||||
expect(v.life).toBe(15);
|
||||
const wraith = state.creatures.find((c) => c.id === "w1")!;
|
||||
expect(wraith.damage).toBe(2);
|
||||
});
|
||||
|
||||
it("legacy games (rev < 4) keep the instant touch", () => {
|
||||
let { state } = createGame({ playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"] });
|
||||
const rig = wraithOnVictim(state);
|
||||
state = rig.state;
|
||||
state = must(state, rig.controller, { type: "moveCreature", creatureId: "w1", direction: rig.side });
|
||||
expect(state.stack).toBeNull();
|
||||
expect(state.players.find((p) => p.id === rig.victim)!.life).toBe(13);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user