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
@@ -227,8 +227,6 @@
|
||||
const k = `${c.x},${c.y}`;
|
||||
if (seen.has(k)) continue;
|
||||
seen.add(k);
|
||||
// Before rev 11 the grid stopped at zero; newer games renormalize.
|
||||
if (view!.deckRev < 11 && (c.x < 0 || c.y < 0)) continue;
|
||||
if (origins.some((o) => o.x === c.x && o.y === c.y)) continue;
|
||||
const next = origins.map((o, j) => (j === idx ? c : o));
|
||||
if (next.every((o, j) => next.some((p, m) => m !== j && adjacent(o, p)))) out.push(c);
|
||||
@@ -255,7 +253,6 @@
|
||||
});
|
||||
clearSelection();
|
||||
}
|
||||
const holdingWard = $derived(view?.yourHand.some((c) => c.cardId === "ward") ?? false);
|
||||
/** A live moment to interrupt: another wizard acts, no attack is pending. */
|
||||
const canInterruptNow = $derived(
|
||||
view != null && !isYourTurn && !view.stack && !view.chaosPending &&
|
||||
@@ -400,7 +397,7 @@
|
||||
// Fully reflecting a SWAP MEET: "lets the other player decide which
|
||||
// objects, if any, will be swapped" — the choice rides the counter.
|
||||
if (youMustRespond && card.cardId === "full-reflection" &&
|
||||
view.stack?.attackCard?.cardId === "swap-meet" && view.deckRev >= 27) {
|
||||
view.stack?.attackCard?.cardId === "swap-meet") {
|
||||
reflectSwapCard = card;
|
||||
reflectSwapMine = null;
|
||||
return;
|
||||
@@ -912,11 +909,8 @@
|
||||
reflectSwapCard = null;
|
||||
reflectSwapMine = null;
|
||||
}
|
||||
// Older rooms' rules trade only object-typed cards (rev < 26): the picker
|
||||
// must offer exactly what that room's engine will honor.
|
||||
function tradableHere(cardId: string): boolean {
|
||||
if (!view) return false;
|
||||
return view.deckRev >= 26 ? isMovableObject(cardId) : cardDef(cardId).cardType === "object";
|
||||
return view != null && isMovableObject(cardId);
|
||||
}
|
||||
/** What one side can put on a trade table: hand (yours) or displayed
|
||||
* cards (theirs), plus a carried treasure. Their treasure is claimable
|
||||
@@ -1753,9 +1747,6 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if view.yourWardArmed}
|
||||
<div class="slip ambush-note">🗡 Your ward is set — a thief who grabs your treasure bleeds for 3.</div>
|
||||
{/if}
|
||||
{#if youMustShield}
|
||||
<div class="slip urgent">
|
||||
Chaos comes for your hand — tap your Full Shield to sit out, or
|
||||
@@ -1808,20 +1799,12 @@
|
||||
{/if}
|
||||
{#if isYourTurn && view.sustained.some((e) => e.cardId === "idiot" && e.targetId === view.you)}
|
||||
<div class="slip urgent">
|
||||
{#if view.deckRev >= 37}
|
||||
🥴 "What am I doing here…?" — IDIOT drives you to the nearest
|
||||
of your own treasures. Spend your movement marching toward it;
|
||||
you may cast only counteractions, spells that speed the trip,
|
||||
and Drop Object to shake your own treasure from a thief's
|
||||
arms. It lifts when you stand on your treasure. (The marching
|
||||
is yours to honor.)
|
||||
{:else}
|
||||
🥴 "What am I doing here…?" — IDIOT drives you to the nearest
|
||||
of your own treasures. Spend your movement marching (the maze
|
||||
will steer your steps); you may cast only counteractions and
|
||||
spells that speed the trip. It lifts when you stand on your
|
||||
treasure.
|
||||
{/if}
|
||||
🥴 "What am I doing here…?" — IDIOT drives you to the nearest
|
||||
of your own treasures. Spend your movement marching toward it;
|
||||
you may cast only counteractions, spells that speed the trip,
|
||||
and Drop Object to shake your own treasure from a thief's
|
||||
arms. It lifts when you stand on your treasure. (The marching
|
||||
is yours to honor.)
|
||||
</div>
|
||||
{/if}
|
||||
{#if actionsSpent}
|
||||
@@ -2136,10 +2119,6 @@
|
||||
onclick={() => dispatch({ type: "pickUpObject", instanceId: obj.instanceId })}>
|
||||
Pick up {cardDef(obj.cardId).name}</button>
|
||||
{/each}
|
||||
{#if holdingWard && view.deckRev < 31}
|
||||
<button class="stamp" onclick={() => dispatch({ type: "armWard", armed: !view.yourWardArmed })}>
|
||||
{view.yourWardArmed ? "Stand down the ward" : "Set the ward"}</button>
|
||||
{/if}
|
||||
<button class="stamp" class:primary={punchWallMode} disabled={view.turn.attackUsed && !punchWallMode}
|
||||
onclick={() => (punchWallMode = !punchWallMode)}>
|
||||
{punchWallMode ? "Click the wall to punch — or cancel" : "Punch a wall…"}</button>
|
||||
|
||||
@@ -63,8 +63,6 @@ function describe(view: GameView, cmd: Command): string {
|
||||
return `set the monster on ${cmd.targetId}`;
|
||||
case "setAmbush":
|
||||
return `lay an ambush with ${nameOf(view, cmd.instanceId)}`;
|
||||
case "armWard":
|
||||
return cmd.armed ? "arm the Ward over the gold" : "stand the Ward down";
|
||||
case "discard":
|
||||
return `shed ${cmd.instanceIds.map((id) => nameOf(view, id)).join(", ")} to make room`;
|
||||
case "endTurn":
|
||||
|
||||
@@ -136,7 +136,6 @@ class LocalGame {
|
||||
seed,
|
||||
sets: expansion ? ["basic", "expansion1"] : ["basic"],
|
||||
...(colors ? { colors } : {}),
|
||||
deckRev: 36,
|
||||
};
|
||||
const { state, events } = createGame(config);
|
||||
for (const e of events) {
|
||||
|
||||
@@ -418,6 +418,18 @@ class Net {
|
||||
if (g.yourTurn && !was) this.notifyTurn(g);
|
||||
this.lastYourTurn.set(key, g.yourTurn);
|
||||
}
|
||||
// A seat the server answered for but did not list is gone — the
|
||||
// room was deleted (or the seat's token voided). Quietly drop it
|
||||
// from the ledger rather than showing "unreachable" forever.
|
||||
// (The server checks at most 50 seats per ask; never prune blind.)
|
||||
if (this.seats.length <= 50) {
|
||||
const listed = new Set((msg.games as GameSummary[]).map((g) => `${g.roomId}:${g.name}`));
|
||||
const kept = this.seats.filter((s) => listed.has(`${s.roomId}:${s.name}`));
|
||||
if (kept.length !== this.seats.length) {
|
||||
this.seats = kept;
|
||||
saveSeats(this.seats);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "error":
|
||||
|
||||
Reference in New Issue
Block a user