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 WagonerandClaude Fable 5 committed 2026-08-15 23:46:23 -04:00
1 parent 89961bbb59
commit 7cfffb8ab5
6 files changed
+203 -6

No files matched your search

+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;