v0.8.4 — the multiplayer transport: server and browser
The second release from the audit. Every fault here was invisible in solitaire, and four of the five server faults were in the one file no test had ever stood up; `http.ts` now has an end-to-end suite on a real port. CHANGELOG has the reasoning. SERVER. Leaving a lobby freed the chair and kept the token, so a leaver could stream and move for whoever took the seat next — revoked now, in memory and on disk. The browser numbered intents from 1 per page load while the server remembered the seat's last number, so the first move after a reload was swallowed as a resend — the connect push carries the count and the client continues from it. Nothing serialised moves within a game and every write shared one `.tmp` name, so two moves at once tore `game.json` (measured: 6 of 200), and the boot's bare `JSON.parse` then took every game down — per-path write queues, a per-game move queue, and a boot that skips one bad file. An error after the SSE head was sent crashed the process. Bodies were unbounded before any secret check. BROWSER. A double-click did the thing twice: one submit in flight at a time. A failed submit is `false`, not an unhandled rejection. The documentation renderer flattened nested bullets into a literal "- " mid-sentence on the published home-deck page. The make-up panel promised cars the engine refuses; it asks `acceptsCar` now. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FrCWubm9GAftYCm2hWdKwK
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
4d222a7eba
commit
e47cd3d400
@@ -107,7 +107,9 @@ clashing join is refused (`NAME_TAKEN`, compared trimmed and case-insensitively)
|
||||
suffixed: a player should play under the name they chose, or be asked for another.
|
||||
|
||||
**Anybody may leave, and the host may clear a chair.** `Lobby.Leave` frees the seat, drops the token
|
||||
from `joinOrder`, and passes host rights on exactly as a dropped connection does. Naming somebody
|
||||
from `joinOrder`, **revokes it** — in memory and in `sessions.json`, since v0.8.4; until then the
|
||||
leaver's token still opened the seat the next arrival took — and passes host rights on exactly as a
|
||||
dropped connection does. Naming somebody
|
||||
else's `seat` is host-only. When the last human leaves, the lobby is deleted outright — code, file and
|
||||
index row — rather than left as a table of bots waiting for a host who no longer exists. Before this
|
||||
existed a mis-join or a player who wandered off wedged the whole table, since Start needs every chair
|
||||
|
||||
@@ -151,8 +151,14 @@ multiplayer work: everything else degrades gracefully, a redaction bug hands a p
|
||||
- Events carry a monotonic sequence per game. Clients apply strictly in order and request a replay on
|
||||
a gap rather than guessing.
|
||||
- Intents carry a client `seq`. The server ignores a repeat of one it has already applied, so a
|
||||
reconnecting client can safely resend anything it is unsure about.
|
||||
reconnecting client can safely resend anything it is unsure about. **The count is the server's,
|
||||
for the life of the game**: the connect push carries the seat's last accepted `seq` (`lastSeq`)
|
||||
and the client continues from it, never from 1 — a page that restarted its own count after a
|
||||
reload re-sent a number the server had already applied, and the move was silently swallowed as
|
||||
a resend (v0.8.4).
|
||||
- **The server never applies two intents concurrently within a game.** A per-game queue is sufficient
|
||||
and there is nothing cleverer to do at this scale. Note this is a serialisation rule, not a
|
||||
and there is nothing cleverer to do at this scale. It is a real queue (`http.ts`'s `inTurn`), not a
|
||||
reliance on the single thread: the handler awaits the disk write between applying and answering,
|
||||
and two moves arriving together used to interleave across that await (v0.8.4). Note this is a serialisation rule, not a
|
||||
one-actor-at-a-time rule: per-player turn state (`turns: Map<PlayerIndex, TurnState>`) means several
|
||||
players may hold an open turn at once, and their intents still land one at a time.
|
||||
|
||||
Reference in New Issue
Block a user