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
@@ -143,13 +143,16 @@ describe("illusion wall", () => {
expect(after.ok).toBe(true);
});
it("opponents test the illusion when they bump it — some see through, some believe", () => {
it("an untested illusion blocks the bump; a tested eye rolls its verdict", () => {
let believed = 0, sawThrough = 0;
for (let seed = 1; seed <= 12; seed++) {
const { state, caster, side } = setupIllusion(seed);
const other = state.players.find((p) => p.id !== caster)!;
other.position = { ...state.players.find((p) => p.id === caster)!.position };
let s = must(state, caster, { type: "endTurn", draw: 0 });
// Bumping blind is refused: eyes must be tested first.
expect(applyCommand(s, other.id, { type: "move", direction: side }).ok).toBe(false);
s = must(s, other.id, { type: "testIllusion", cell: other.position, side });
const result = applyCommand(s, other.id, { type: "move", direction: side });
if (result.ok) sawThrough++;
else believed++;
@@ -223,11 +226,16 @@ describe("sector manipulation", () => {
type: "cast", instanceId: rel.instanceId,
target: { kind: "cell", cell: dest }, params: { cell: me.position },
});
// The maze zero-anchors after the move; measure against the sector's
// origin as it settled, not the requested landing.
const after = state.players.find((p) => p.id === me.id)!;
const dx = dest.x - myOrigin.x, dy = dest.y - myOrigin.y;
const newOrigin = state.board.placements[idx]!.origin;
const dx = newOrigin.x - myOrigin.x, dy = newOrigin.y - myOrigin.y;
expect(after.position).toEqual({ x: posBefore.x + dx, y: posBefore.y + dy });
expect(after.home).toEqual({ x: homeBefore.x + dx, y: homeBefore.y + dy });
expect(state.board.placements[idx]!.origin).toEqual(dest);
// My sector sits east of the other, as asked.
const otherAfter = state.board.placements[idx === 0 ? 1 : 0]!.origin;
expect(newOrigin).toEqual({ x: otherAfter.x + 5, y: otherAfter.y });
// The map reassembled: every treasure/wizard cell exists on the new board.
for (const t of state.treasures) {
if (t.position) expect(state.board.cells[cellKey(t.position)]).toBe(true);
@@ -319,8 +327,8 @@ describe("6e card-face corrections", () => {
params: { damage: 4, knockback: 0 },
});
state = must(state, defender.id, { type: "counteract", instanceId: "wall-of-fire#WOF" });
// The fire is a total stop: the attacker's pass settles it.
state = must(state, attacker.id, { type: "pass" });
state = must(state, defender.id, { type: "pass" });
expect(state.players.find((p) => p.id === defender.id)!.life).toBe(15);
// But it cannot counter a fireball.
@@ -337,7 +345,7 @@ describe("6e card-face corrections", () => {
describe("relocation past the origin (rules rev 11)", () => {
function rev11Game() {
const { state } = createGame({ playerIds: ["alice", "bob"], seed: 42, sets: ["basic"], deckRev: 11 });
const { state } = createGame({ playerIds: ["alice", "bob"], seed: 42, sets: ["basic"] });
return state;
}
@@ -438,7 +446,7 @@ describe("relocation past the origin (rules rev 11)", () => {
describe("junction alterations roll for their sector (rules rev 20)", () => {
it("a conjured wall on the seam obeys the die: 1-2 stays, 3-4 travels", () => {
const { state: fresh } = createGame({ playerIds: ["a", "b"], seed: 42, sets: ["basic"], deckRev: 20 });
const { state: fresh } = createGame({ playerIds: ["a", "b"], seed: 42, sets: ["basic"] });
let state = toRound2(fresh);
// The 2p board is two sectors stacked: the seam runs between them.
const [p0, p1] = state.board.placements;
@@ -503,7 +511,7 @@ describe("sight tracing", () => {
describe("illusions are tested by choice (rules rev 29)", () => {
function shimmerRig() {
let { state } = createGame({ playerIds: ["a", "b"], seed: 42, sets: ["basic"], deckRev: 29 });
let { state } = createGame({ playerIds: ["a", "b"], seed: 42, sets: ["basic"] });
state = toRound2(state);
const caster = activePlayer(state);
const mark = state.players.find((p) => p.id !== caster.id)!;