The great simplification: every vintage gate collapses, the count restarts at 1
All 50 rooms were retired (backed up on the droplet and in Spaces), so no ledger needs an old branch to replay: forty revisions of deckRev gates fold into one canonical ruleset — the newest behavior everywhere. The ward's arming, the idiot's steering, the lazy illusion roll, the legacy redirection swap, and the flat wave all leave with their gates; RULES_REV restarts at 1 for whenever the rules fork again. Old-vintage test pins go with them. The opening screen now quietly drops seats whose rooms the server no longer knows, instead of listing them "unreachable" forever. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
e8df6efcde
commit
8a50ff7f36
@@ -106,7 +106,7 @@ describe("expansion combat cards", () => {
|
||||
expect(state.players.find((p) => p.id === attacker)!.life).toBe(10);
|
||||
});
|
||||
|
||||
it("ward springs when a trapped treasure is grabbed", () => {
|
||||
it("ward springs in the moment: the grab hangs on the owner's choice", () => {
|
||||
let { state } = newGame();
|
||||
const me = activePlayer(state);
|
||||
const enemy = state.players.find((p) => p.id !== me.id)!;
|
||||
@@ -114,6 +114,8 @@ describe("expansion combat cards", () => {
|
||||
const treasure = state.treasures.find((t) => t.owner === enemy.id && t.position)!;
|
||||
me.position = { ...treasure.position! };
|
||||
state = must(state, me.id, { type: "pickUpTreasure" });
|
||||
expect(state.wardPending).toEqual({ ownerId: enemy.id, takerId: me.id });
|
||||
state = must(state, enemy.id, { type: "wardChoice", play: true });
|
||||
expect(state.players.find((p) => p.id === me.id)!.life).toBe(12);
|
||||
expect(state.players.find((p) => p.id === enemy.id)!.hand.some((c) => c.cardId === "ward")).toBe(false);
|
||||
});
|
||||
@@ -164,7 +166,7 @@ describe("expansion combat cards", () => {
|
||||
|
||||
it("idiot at rev 23 forbids item handling and punches but allows counteractions", () => {
|
||||
let { state } = createGame({
|
||||
playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"], deckRev: 23,
|
||||
playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"],
|
||||
});
|
||||
state = toRound2(state);
|
||||
const { attacker, defender } = faceOff(state);
|
||||
@@ -198,9 +200,9 @@ describe("expansion combat cards", () => {
|
||||
expect(applyCommand(state, defender, { type: "counteract", instanceId: "absorb-spell#AB" }).ok).toBe(true);
|
||||
});
|
||||
|
||||
it("idiot at rev 37 no longer steers the march — the step goes where asked", () => {
|
||||
it("idiot never steers the march — the step goes where asked", () => {
|
||||
let { state } = createGame({
|
||||
playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"], deckRev: 37,
|
||||
playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"],
|
||||
});
|
||||
state = toRound2(state);
|
||||
const { attacker, defender } = faceOff(state);
|
||||
@@ -225,10 +227,10 @@ describe("expansion combat cards", () => {
|
||||
expect(cellKey(state.players.find((p) => p.id === defender)!.position)).toBe(cellKey(away.to));
|
||||
});
|
||||
|
||||
it("idiot at rev 37 permits DROP OBJECT that frees the victim's own treasure", () => {
|
||||
for (const deckRev of [36, 37]) {
|
||||
it("idiot permits DROP OBJECT that frees the victim's own treasure", () => {
|
||||
{
|
||||
let { state } = createGame({
|
||||
playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"], deckRev,
|
||||
playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"],
|
||||
});
|
||||
state = toRound2(state);
|
||||
const { attacker, defender } = faceOff(state);
|
||||
@@ -252,13 +254,13 @@ describe("expansion combat cards", () => {
|
||||
type: "cast", instanceId: dr.instanceId,
|
||||
target: { kind: "player", playerId: attacker }, params: { cardId: "treasure" },
|
||||
});
|
||||
expect(r.ok).toBe(deckRev >= 37);
|
||||
expect(r.ok).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it("idiot at rev 23 has no effect on a victim carrying their own treasure", () => {
|
||||
let { state } = createGame({
|
||||
playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"], deckRev: 23,
|
||||
playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"],
|
||||
});
|
||||
state = toRound2(state);
|
||||
const { attacker, defender } = faceOff(state);
|
||||
@@ -394,9 +396,9 @@ describe("ambushes (async interrupts)", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("swap meet trades carried items (rules rev 26)", () => {
|
||||
function rig(deckRev: number) {
|
||||
let { state } = createGame({ playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"], deckRev });
|
||||
describe("swap meet trades carried items", () => {
|
||||
function rig() {
|
||||
let { state } = createGame({ playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"] });
|
||||
state = toRound2(state);
|
||||
const { attacker, defender } = faceOff(state);
|
||||
const sm = giveCard(state, attacker, "swap-meet");
|
||||
@@ -412,21 +414,15 @@ describe("swap meet trades carried items (rules rev 26)", () => {
|
||||
}
|
||||
|
||||
it("a dagger trades for a wand — both are carried items", () => {
|
||||
const { state, attacker, defender } = rig(26);
|
||||
const { state, attacker, defender } = rig();
|
||||
expect(state.players.find((p) => p.id === attacker)!.hand.some((c) => c.cardId === "blaster-wand")).toBe(true);
|
||||
expect(state.players.find((p) => p.id === defender)!.hand.some((c) => c.cardId === "dagger")).toBe(true);
|
||||
});
|
||||
|
||||
it("older revisions matched only object-typed cards and replay so", () => {
|
||||
const { state, attacker, defender } = rig(25);
|
||||
expect(state.players.find((p) => p.id === attacker)!.hand.some((c) => c.cardId === "dagger")).toBe(true);
|
||||
expect(state.players.find((p) => p.id === defender)!.hand.some((c) => c.cardId === "blaster-wand")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("swap meet trades treasures too", () => {
|
||||
function treasureRig(theirsToken: string, mineToken: string) {
|
||||
let { state } = createGame({ playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"], deckRev: 26 });
|
||||
let { state } = createGame({ playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"] });
|
||||
state = toRound2(state);
|
||||
const { attacker, defender } = faceOff(state);
|
||||
const a = state.players.find((p) => p.id === attacker)!;
|
||||
@@ -459,7 +455,7 @@ describe("swap meet trades treasures too", () => {
|
||||
it("the trade refuses to overload full arms", () => {
|
||||
// The attacker also carries a treasure: claiming theirs with a dagger
|
||||
// would mean two in hand — the swap quietly cannot happen.
|
||||
let { state } = createGame({ playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"], deckRev: 26 });
|
||||
let { state } = createGame({ playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"] });
|
||||
state = toRound2(state);
|
||||
const { attacker, defender } = faceOff(state);
|
||||
const a = state.players.find((p) => p.id === attacker)!;
|
||||
@@ -481,7 +477,7 @@ describe("swap meet trades treasures too", () => {
|
||||
});
|
||||
|
||||
it("treasure for treasure trades both armfuls", () => {
|
||||
let { state } = createGame({ playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"], deckRev: 26 });
|
||||
let { state } = createGame({ playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"] });
|
||||
state = toRound2(state);
|
||||
const { attacker, defender } = faceOff(state);
|
||||
const a = state.players.find((p) => p.id === attacker)!;
|
||||
@@ -506,7 +502,7 @@ describe("swap meet trades treasures too", () => {
|
||||
|
||||
describe("full reflection hands the swap meet choice to the reflector (rev 27)", () => {
|
||||
function reflectedSwapRig(reflectorChoice?: string) {
|
||||
let { state } = createGame({ playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"], deckRev: 27 });
|
||||
let { state } = createGame({ playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"] });
|
||||
state = toRound2(state);
|
||||
const { attacker, defender } = faceOff(state);
|
||||
const a = state.players.find((p) => p.id === attacker)!;
|
||||
@@ -553,7 +549,7 @@ describe("full reflection hands the swap meet choice to the reflector (rev 27)",
|
||||
|
||||
describe("go away routs around walls (rules rev 36)", () => {
|
||||
it("a victim against a wall bends the line instead of standing still", () => {
|
||||
let { state } = createGame({ playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"], deckRev: 36 });
|
||||
let { state } = createGame({ playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"] });
|
||||
state = toRound2(state);
|
||||
const { attacker, defender } = faceOff(state);
|
||||
const a = state.players.find((p) => p.id === attacker)!;
|
||||
|
||||
Reference in New Issue
Block a user