The tear waits for counteractions (rules rev 6)
STRENGTH's card says the grab "would be an attack," but the engine resolved it instantly while punches opened counteraction windows — the defender never got their say. At rev 6 the tear rides the stack like any physical attack: the clutch roll waits out the counters, and only an unstopped grab still in arm's reach of a still-laden defender gets its wrestle. A TELEPORT escape now genuinely saves the chest (FULL SHIELD does not — the card stops spell attacks only, and the grab is physical). The automatons counter tears at their carried gold by not being there when the hand closes. Narrowing, so rev-gated: games pinned at rev 5 keep their instant tears and replay unchanged — all 29 ledgers verify. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015RCWSTnb1KYTPyL4GmhGnF
This commit is contained in:
co-authored by
Claude Fable 5
parent
cbd4d80dea
commit
e388691858
@@ -1196,3 +1196,72 @@ describe("the bent sight-trace shows AROUND THE CORNER's legs", () => {
|
||||
expect(traced?.bend?.mid).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("the tear is an attack (rev 6)", () => {
|
||||
function tearRig(deckRev?: number) {
|
||||
let { state } = createGame({
|
||||
playerIds: ["att", "def"], seed: 42, sets: ["basic", "expansion1"],
|
||||
...(deckRev != null ? { deckRev } : {}),
|
||||
});
|
||||
state = toRound2(state);
|
||||
while (activePlayer(state).id !== "att") {
|
||||
state = must(state, activePlayer(state).id, { type: "endTurn", draw: 0 });
|
||||
}
|
||||
const att = state.players.find((p) => p.id === "att")!;
|
||||
const def = state.players.find((p) => p.id === "def")!;
|
||||
def.position = { ...att.position };
|
||||
const gold = state.treasures.find((t) => t.owner === "att" && t.position)!;
|
||||
gold.position = null;
|
||||
gold.carriedBy = "def";
|
||||
def.carriedTreasureId = gold.id;
|
||||
state.sustained.push({
|
||||
id: "fx-t", cardId: "strength", casterId: "att", targetId: "att",
|
||||
remainingTurns: 3, data: {},
|
||||
});
|
||||
return { state, att, def, gold };
|
||||
}
|
||||
|
||||
it("opens a counteraction window; a TELEPORT escape keeps the chest", () => {
|
||||
let { state, gold } = tearRig();
|
||||
const r = applyCommand(state, "att", { type: "tearTreasure", targetId: "def" });
|
||||
if (!r.ok) throw new Error(r.error);
|
||||
state = r.state;
|
||||
expect(state.stack?.tearTreasure).toBe(true);
|
||||
expect(state.stack?.waitingOn).toBe("def");
|
||||
const def = state.players.find((p) => p.id === "def")!;
|
||||
const view = boardView(state);
|
||||
const away = SIDES.map((s) => stepTarget(view, def.position, s))
|
||||
.find((t) => t.kind === "step")!;
|
||||
giveCard(state, "def", "teleport", "TP", 0);
|
||||
state = must(state, "def", { type: "counteract", instanceId: "teleport#TP", params: { cell: (away as { to: Cell }).to } });
|
||||
state = must(state, "att", { type: "pass" });
|
||||
if (state.stack) state = must(state, state.stack.waitingOn, { type: "pass" });
|
||||
expect(state.stack).toBeNull();
|
||||
expect(state.players.find((p) => p.id === "def")!.carriedTreasureId).toBe(gold.id);
|
||||
});
|
||||
|
||||
it("an uncountered tear wrestles after the window closes", () => {
|
||||
let { state, gold } = tearRig();
|
||||
const r = applyCommand(state, "att", { type: "tearTreasure", targetId: "def" });
|
||||
if (!r.ok) throw new Error(r.error);
|
||||
state = r.state;
|
||||
const res = applyCommand(state, "def", { type: "pass" });
|
||||
if (!res.ok) throw new Error(res.error);
|
||||
state = res.state;
|
||||
const rolled = res.events.find((e) => e.type === "dieRolled");
|
||||
expect(rolled).toBeTruthy();
|
||||
const kept = rolled!.type === "dieRolled" && rolled!.roll === 1;
|
||||
const att = state.players.find((p) => p.id === "att")!;
|
||||
const def = state.players.find((p) => p.id === "def")!;
|
||||
if (kept) expect(def.carriedTreasureId).toBe(gold.id);
|
||||
else expect(att.carriedTreasureId).toBe(gold.id);
|
||||
});
|
||||
|
||||
it("rev 5 games keep the instant tear, no stack", () => {
|
||||
let { state } = tearRig(5);
|
||||
const r = applyCommand(state, "att", { type: "tearTreasure", targetId: "def" });
|
||||
if (!r.ok) throw new Error(r.error);
|
||||
expect(r.state.stack).toBeNull();
|
||||
expect(r.events.some((e) => e.type === "treasureTorn" || e.type === "tearResisted")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user