Persistent games: rooms survive server restarts by log replay

Every room is now an append-only JSONL file (data/rooms/CODE.jsonl):
a birth-certificate meta line, then every join, start, and command.
Because the engine is deterministic, seed + log IS the game — on boot
the server replays each file and reconstructs the exact state, hands,
deck order, and chronicle. Verified live: create, join, play, KILL
the server, restart ("restored 1 room(s) from disk"), rejoin with the
seat token — identical positions, deck count, and hand, with the
chronicle history redelivered redacted per player. The client
remembers its seat (name, room, token) in localStorage and walks back
to the table automatically on connect, clearing the memory if the
seat is stale; a quiet "leave table" control forgets it on purpose.
This is the foundation phase 2 (play-by-turn) sits on: games now wait
indefinitely for their players — and debugging restarts cost nothing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Eric Wagoner
2026-08-15 23:46:23 -04:00
co-authored by Claude Fable 5
parent 89961bbb59
commit 7cfffb8ab5
6 changed files with 203 additions and 6 deletions
+11
View File
@@ -413,6 +413,7 @@
<span class="mast-sub">sixth edition</span>
{#if net.roomId}
<span class="mast-room">room <b>{net.roomId}</b></span>
<button class="mast-leave" onclick={() => net.leave()}>leave table</button>
{/if}
<span class="mast-status" class:offline={net.status !== "connected"}>
{net.status === "connected" ? "" : "reconnecting…"}
@@ -694,6 +695,16 @@
.mast-room { margin-left: auto; font-size: 0.85rem; color: #a49c86; }
.mast-room b { color: #e9e1cb; letter-spacing: 0.12em; }
.mast-status { font-size: 0.8rem; color: #c98a2a; }
.mast-leave {
background: none;
border: none;
color: #8d8672;
font-family: "Archivo Narrow", sans-serif;
font-size: 0.8rem;
text-decoration: underline;
cursor: pointer;
}
.mast-leave:hover { color: #d8d2c0; }
.toast {
background: #6d2119;