Zero-damage attacks stop lying about being stopped
DROP OBJECT resolved correctly — the treasure fell — but the chronicle announced "The attack is completely stopped," because the attackResolved event branded any attack that dealt zero damage as stopped. Utility attacks deal zero by design; only an attack that was TRYING to wound counts as stopped when nothing lands. Tests pin both sides: uncountered DROP OBJECT reports unstopped and drops the treasure; FULL SHIELD reports stopped and the treasure stays carried. Not deployed — a live game is in progress. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
7e3e00bf97
commit
b210cffa76
@@ -487,3 +487,50 @@ describe("rules revision 3", () => {
|
||||
expect(state.players.find((p) => p.id === thief.id)!.life).toBe(12);
|
||||
});
|
||||
});
|
||||
|
||||
describe("zero-damage utility attacks", () => {
|
||||
it("an uncountered DROP OBJECT is not reported as stopped — and the treasure falls", () => {
|
||||
let { state } = newGame();
|
||||
state = toRound2(state);
|
||||
const { attacker, defender } = faceOff(state);
|
||||
const d = state.players.find((p) => p.id === defender)!;
|
||||
const treasure = state.treasures.find((t) => t.owner !== defender)!;
|
||||
treasure.carriedBy = defender;
|
||||
treasure.position = null;
|
||||
d.carriedTreasureId = treasure.id;
|
||||
const dob = giveCard(state, attacker, "drop-object");
|
||||
state = must(state, attacker, {
|
||||
type: "cast", instanceId: dob.instanceId, params: { cardId: "treasure" },
|
||||
target: { kind: "player", playerId: defender },
|
||||
});
|
||||
const result = applyCommand(state, defender, { type: "pass" });
|
||||
if (!result.ok) throw new Error(result.error);
|
||||
const resolved = result.events.find((e) => e.type === "attackResolved");
|
||||
expect(resolved && "fullyStopped" in resolved && resolved.fullyStopped).toBe(false);
|
||||
expect(result.state.players.find((p) => p.id === defender)!.carriedTreasureId).toBeNull();
|
||||
});
|
||||
|
||||
it("FULL SHIELD genuinely stops it — the treasure stays carried", () => {
|
||||
let { state } = newGame();
|
||||
state = toRound2(state);
|
||||
const { attacker, defender } = faceOff(state);
|
||||
const d = state.players.find((p) => p.id === defender)!;
|
||||
const treasure = state.treasures.find((t) => t.owner !== defender)!;
|
||||
treasure.carriedBy = defender;
|
||||
treasure.position = null;
|
||||
d.carriedTreasureId = treasure.id;
|
||||
const dob = giveCard(state, attacker, "drop-object");
|
||||
giveCard(state, defender, "full-shield", "FS", 0);
|
||||
state = must(state, attacker, {
|
||||
type: "cast", instanceId: dob.instanceId, params: { cardId: "treasure" },
|
||||
target: { kind: "player", playerId: defender },
|
||||
});
|
||||
state = must(state, defender, { type: "counteract", instanceId: "full-shield#FS" });
|
||||
state = must(state, attacker, { type: "pass" });
|
||||
const result = applyCommand(state, defender, { type: "pass" });
|
||||
if (!result.ok) throw new Error(result.error);
|
||||
const resolved = result.events.find((e) => e.type === "attackResolved");
|
||||
expect(resolved && "fullyStopped" in resolved && resolved.fullyStopped).toBe(true);
|
||||
expect(result.state.players.find((p) => p.id === defender)!.carriedTreasureId).toBe(treasure.id);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user