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:
Eric Wagoner
2026-08-21 12:45:35 -04:00
co-authored by Claude Fable 5
parent e8df6efcde
commit 8a50ff7f36
19 changed files with 383 additions and 685 deletions
+30 -43
View File
@@ -162,10 +162,10 @@ describe("expansion terrain", () => {
// Wave side effects vary by geometry; the melt itself is the pinned behavior.
});
it("wall of fire takes a rim warp from rev 39 — both mouths burn the crossing", () => {
for (const deckRev of [38, 39]) {
it("wall of fire takes a rim warp — both mouths burn the crossing", () => {
{
let { state } = createGame({
playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"], deckRev,
playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"],
});
const me = activePlayer(state);
const warp = state.board.warps[0]!;
@@ -175,8 +175,7 @@ describe("expansion terrain", () => {
type: "cast", instanceId: wof.instanceId,
target: { kind: "edge", cell: warp.from.cell, side: warp.from.side },
});
expect(r.ok).toBe(deckRev >= 39);
if (!r.ok) continue;
if (!r.ok) throw new Error(r.error);
state = r.state;
const nearKey = edgeKey(warp.from.cell, warp.from.side);
const farKey = edgeKey(warp.to.cell, warp.to.side);
@@ -191,10 +190,10 @@ describe("expansion terrain", () => {
}
});
it("waterwall takes a rim warp from rev 40 — the collapse washes both rims", () => {
for (const deckRev of [39, 40]) {
it("waterwall takes a rim warp — the collapse washes both rims", () => {
{
let { state } = createGame({
playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"], deckRev,
playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"],
});
const me = activePlayer(state);
const other = state.players.find((p) => p.id !== me.id)!;
@@ -206,8 +205,7 @@ describe("expansion terrain", () => {
type: "cast", instanceId: ww.instanceId,
target: { kind: "edge", cell: warp.from.cell, side: warp.from.side },
});
expect(r.ok).toBe(deckRev >= 40);
if (!r.ok) continue;
if (!r.ok) throw new Error(r.error);
// Both wizards stood in the mouths; the collapse washed both inward.
const meAfter = r.state.players.find((p) => p.id === me.id)!;
const otherAfter = r.state.players.find((p) => p.id === other.id)!;
@@ -216,10 +214,10 @@ describe("expansion terrain", () => {
}
});
it("illusion wall hangs on a rim warp mouth from rev 40", () => {
for (const deckRev of [39, 40]) {
it("illusion wall hangs on a rim warp mouth", () => {
{
const { state } = createGame({
playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"], deckRev,
playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"],
});
const me = activePlayer(state);
const warp = state.board.warps[0]!;
@@ -229,17 +227,17 @@ describe("expansion terrain", () => {
type: "cast", instanceId: il.instanceId,
target: { kind: "edge", cell: warp.from.cell, side: warp.from.side },
});
expect(r.ok).toBe(deckRev >= 40);
expect(r.ok).toBe(true);
if (r.ok) {
expect(r.state.illusionWalls[edgeKey(warp.from.cell, warp.from.side)]).toBeDefined();
}
}
});
it("stone to water breaches the outer rim from rev 40, opening a warp", () => {
for (const deckRev of [39, 40]) {
it("stone to water breaches the outer rim, opening a warp", () => {
{
let { state } = createGame({
playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"], deckRev,
playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"],
});
const me = activePlayer(state);
const board = boardView(state);
@@ -264,20 +262,15 @@ describe("expansion terrain", () => {
type: "cast", instanceId: stw.instanceId,
target: { kind: "edge", cell: rim.cell, side: rim.side },
});
if (deckRev >= 40) {
// The far rim melted with it and the wraparound now runs.
expect(state.board.warps.length).toBe(warpsBefore + 2);
} else {
// Older vintages left a dangling opening and replay so.
expect(state.board.warps.length).toBe(warpsBefore);
}
// The far rim melted with it and the wraparound now runs.
expect(state.board.warps.length).toBe(warpsBefore + 2);
}
});
it("no doors through the rim from rev 40", () => {
for (const deckRev of [39, 40]) {
it("no doors through the rim", () => {
{
const { state } = createGame({
playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"], deckRev,
playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"],
});
const me = activePlayer(state);
const board = boardView(state);
@@ -299,15 +292,14 @@ describe("expansion terrain", () => {
type: "cast", instanceId: cd.instanceId,
target: { kind: "edge", cell: rim.cell, side: rim.side },
});
// Rev 39 hung a dead door there (and replays so); rev 40 refuses.
expect(r.ok).toBe(deckRev < 40);
expect(r.ok).toBe(false);
}
});
it("stone to water melts a door from rev 38 — a small entryway in a stone wall", () => {
for (const deckRev of [37, 38]) {
it("stone to water melts a door — a small entryway in a stone wall", () => {
{
const { state } = createGame({
playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"], deckRev,
playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"],
});
const me = activePlayer(state);
const board = boardView(state);
@@ -329,7 +321,7 @@ describe("expansion terrain", () => {
type: "cast", instanceId: stw.instanceId,
target: { kind: "edge", cell: doorAt.cell, side: doorAt.side },
});
expect(r.ok).toBe(deckRev >= 38);
expect(r.ok).toBe(true);
if (r.ok) {
const key = edgeKey(doorAt.cell, doorAt.side);
expect(boardView(r.state).edges[key] ?? "open").toBe("open");
@@ -389,11 +381,11 @@ describe("eligibility dimming mirrors the engine", () => {
});
});
describe("a wave's force is spent as it travels (rules rev 24)", () => {
describe("a wave's force is spent as it travels", () => {
/** Caster at B, one cell above A; the target wall is A's south edge, so
* the range-2 wave covers A (dist 0) and B (dist 1). */
function rig(deckRev: number, behind: "open" | "wall") {
const { state } = createGame({ playerIds: ["a", "b"], seed: 42, sets: ["basic", "expansion1"], deckRev });
function rig(behind: "open" | "wall") {
const { state } = createGame({ playerIds: ["a", "b"], seed: 42, sets: ["basic", "expansion1"] });
const me = activePlayer(state);
const A = { x: 4, y: 5 }, B = { x: 4, y: 4 }, Bn = { x: 4, y: 3 }, Bnn = { x: 4, y: 2 };
for (const c of [A, B, Bn, Bnn]) expect(boardView(state).cells[cellKey(c)]).toBeTruthy();
@@ -412,20 +404,15 @@ describe("a wave's force is spent as it travels (rules rev 24)", () => {
}
it("a victim at the wave's far edge is carried one space, unhurt", () => {
const { me, Bn } = rig(24, "open");
const { me, Bn } = rig("open");
expect(cellKey(me.position)).toBe(cellKey(Bn));
expect(me.life).toBe(15);
});
it("only unspent force crushes: one space of push blocked is one damage", () => {
const { me, B } = rig(24, "wall");
const { me, B } = rig("wall");
expect(cellKey(me.position)).toBe(cellKey(B));
expect(me.life).toBe(14);
});
it("older revisions keep the flat full-range wash for replay fidelity", () => {
const { me, Bnn } = rig(23, "open");
expect(cellKey(me.position)).toBe(cellKey(Bnn));
expect(me.life).toBe(15);
});
});