Disease makes its caster the carrier (rules rev 7)
The card says it plainly — "You're the carrier! Disease caused by
spell does not affect you, only others" — but the engine cast it AT an
adjacent victim, making them the plague rat. At rev 7 it is a
self-cast on fear's pattern: no target, no counteraction window
("REFLECTIONs have no effect against this"), duration equals the
number, and sharing a square bites in both directions — the carrier
walking in, or anyone walking onto the carrier. Older ledgers hold
targeted disease casts with full stack exchanges, so the legacy attack
path survives for rev ≤6 games and all 33 ledgers replay clean. The
client offers the plain Cast button for it in rev-7 games.
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
db3b14fd51
commit
b99c22f14a
@@ -1296,3 +1296,58 @@ describe("a forced drop lands like any drop", () => {
|
||||
if (drop?.type === "treasureDropped") expect(drop.onHomeOf).toBe("def");
|
||||
});
|
||||
});
|
||||
|
||||
describe("disease makes the caster the carrier (rev 7)", () => {
|
||||
it("self-casts with no target; sharing a square bites both directions", () => {
|
||||
let { state } = createGame({ playerIds: ["carrier", "mark"], seed: 42, sets: ["basic", "expansion1"] });
|
||||
state = toRound2(state);
|
||||
while (activePlayer(state).id !== "carrier") {
|
||||
state = must(state, activePlayer(state).id, { type: "endTurn", draw: 0 });
|
||||
}
|
||||
const carrier = state.players.find((p) => p.id === "carrier")!;
|
||||
const mark = state.players.find((p) => p.id === "mark")!;
|
||||
const dz = giveCard(state, "carrier", "disease", "D", 0);
|
||||
giveCard(state, "carrier", "number-3", "N", 1);
|
||||
let r = applyCommand(state, "carrier", {
|
||||
type: "cast", instanceId: dz.instanceId, numberInstanceIds: ["number-3#N"],
|
||||
});
|
||||
if (!r.ok) throw new Error(r.error);
|
||||
state = r.state;
|
||||
expect(state.stack).toBeNull();
|
||||
expect(state.sustained.some((s) => s.cardId === "disease" && s.targetId === "carrier")).toBe(true);
|
||||
|
||||
// The carrier walks INTO the mark's square: the mark takes 3.
|
||||
const view = boardView(state);
|
||||
const carrierNow = state.players.find((p) => p.id === "carrier")!;
|
||||
const markNow = state.players.find((p) => p.id === "mark")!;
|
||||
for (const side of SIDES) {
|
||||
const t = stepTarget(view, carrierNow.position, side);
|
||||
if (t.kind !== "step") continue;
|
||||
markNow.position = { ...t.to };
|
||||
const before = markNow.life;
|
||||
r = applyCommand(state, "carrier", { type: "move", direction: side });
|
||||
if (!r.ok) throw new Error(r.error);
|
||||
state = r.state;
|
||||
expect(state.players.find((p) => p.id === "mark")!.life).toBe(before - 3);
|
||||
break;
|
||||
}
|
||||
state = must(state, "carrier", { type: "endTurn", draw: 0 });
|
||||
|
||||
// The mark walks INTO the carrier's square: the mark takes 3 again.
|
||||
const m2 = state.players.find((p) => p.id === "mark")!;
|
||||
const c2 = state.players.find((p) => p.id === "carrier")!;
|
||||
const view2 = boardView(state);
|
||||
for (const side of SIDES) {
|
||||
const t = stepTarget(view2, c2.position, side);
|
||||
if (t.kind !== "step") continue;
|
||||
m2.position = { ...t.to };
|
||||
const back = side === "N" ? "S" : side === "S" ? "N" : side === "E" ? "W" : "E";
|
||||
const before = m2.life;
|
||||
r = applyCommand(state, "mark", { type: "move", direction: back as "N" });
|
||||
if (!r.ok) throw new Error(r.error);
|
||||
expect(r.state.players.find((p) => p.id === "mark")!.life).toBe(before - 3);
|
||||
return;
|
||||
}
|
||||
throw new Error("no adjacent step for the return walk");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user