From 058432130628d00764663ecf819a9adc6793768d Mon Sep 17 00:00:00 2001 From: salmonik Date: Sun, 8 Nov 2026 03:29:35 +0100 Subject: v5.0.0: protocol v5 and the browser frontend Protocol v5 replaces newline lines with bounded [u32 len][text] frames and REQ/RES/EVT envelopes. Requests carry correlation ids and the server replays the last 256 replies per connection, so a gateway retry cannot double-debit and a late reply cannot clobber a newer request. HELLO/PONG carry server time; crash rounds announce started_at and push coalesced 10 Hz ticks. Crash now commits sha256(seed) before a round and reveals the seed when it ends, so clients recompute the crash point. Adds the webd/ gateway: one blocking thread, one ClientCore and one pinned upstream TLS connection per session, an HttpOnly session cookie, takeover, and a 60-second grace window. The browser sends Command JSON and renders View JSON; it never speaks the game protocol or stores credentials. Adds the vanilla TypeScript browser app at /play/ (login, lobby, all four games, history, notices, crash verification), the nginx /play and /ws routes, and the headless DOM smoke test. webd and the app stay out of the TUI/GUI/server build graph. The client/server version moves to 5.0.0 in lockstep; v4 peers are refused at HELLO. --- AGENTS.md | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 62 insertions(+), 3 deletions(-) (limited to 'AGENTS.md') diff --git a/AGENTS.md b/AGENTS.md index ab794f8..3827957 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -37,20 +37,79 @@ transaction. ## Crash -- The client draws the multiplier from its own clock for display only. A - cashout carries a session id and nothing else; the multiplier and payout come - from the server's clock at receipt. +- The client draws the multiplier from its own clock, corrected by the server + time in `HELLO`/`PONG`, for display only. A cashout carries a session id and + nothing else; the multiplier and payout come from the server's clock at + receipt. - Never send the crash point to the client before the round ends. A client that knows it can cash out at `crash - epsilon` every round for a guaranteed win. - The crash point is `max(1.0, (1 - edge) / (1 - u))`; do not "fix" the 1% of rounds that crash at 1.00x. That is where the house edge is charged on an instant cashout, and it is why no cashout lockout is needed. +- Each round draws a 32-byte seed. `commit = sha256(seed)` goes out with + `CRASH_START`; the seed itself is revealed only when the round ends. The + crash point is `crash_point_from_seed(seed)` (first eight bytes, big-endian, + over 2^64), so a client can recompute it. Never reveal the seed while the + round is live. The guarantee is "fixed before you acted", not "ungrindable"; + see ADR 0004. - The crash event is a server-initiated line (`CRASH_BUSTED`), not a reply. Client code must drain the socket on a timer and route pushed lines by prefix instead of assuming the next line answers the last request. - `ServerCore::poll` must be idempotent: it settles the round, removes the session, and pushes once. A second poll must produce nothing. +## Protocol v5 +- Every packet is `[u32 big-endian length][UTF-8 payload]`, at most 64 KiB. + Never read a declared length above the limit into memory; close instead. +- Payloads are `REQ [idem=] `, `RES ` and + `EVT `. The command/body text grammar is unchanged. +- `ClientCore` allocates request ids. Adapters must return a reply with + `handle_reply_id`; a reply with an unknown id is ignored, never applied to + whatever request happens to be pending. +- The server caches the last 256 request ids **per connection** and replays a + matching reply. A reused id with a different payload is an error. Do not make + this durable without a new ADR. +- `HELLO` and `PONG` carry Unix milliseconds. The offset is for display and + interpolation; the server settles crash. Never derive a payout from a + browser or gateway clock. +- While a crash round runs, `poll` pushes at most one `crash.tick` per 100 ms + per session. Settlement (`CRASH_BUSTED`) is never dropped or replaced by a + tick. +- A wire change is a MAJOR bump. Do not add a compatibility mode for v4. + +## Web gateway (`webd/`) +- The browser is a frontend, not a client. It sends `Command` JSON and renders + `View` JSON. It must never parse the game wire, compute a payout, or hold a + credential. +- `webd/` is a separate workspace member. Its dependencies (tungstenite, + serde_json, cookie handling) must not enter the TUI, GUI or server build + graph. +- One session thread owns one `ClientCore` and one upstream TLS connection. + No async runtime, no browser TOFU: `ZAGAN_UPSTREAM_FINGERPRINT` is required + and a mismatch refuses to connect. +- The session cookie is HttpOnly, SameSite=Strict, and Secure on HTTPS. Never + store a password or credential in the browser or gateway. Session-only auth; + no remember-me without a new ADR. +- Close is not forfeit: the gateway holds upstream for `ZAGAN_GRACE_SECONDS` + (default 60). Expiry closes upstream, which is the server's forfeit. A new + socket with the same cookie takes over; tell the old tab it was replaced. +- Bind to loopback and let nginx terminate TLS. Check `Origin` against + `ZAGAN_WEB_ORIGIN`. `/ws` is the only gateway route; `/play/` is static. +- A gateway restart loses live sessions and forfeits their rounds. Do not add + protocol-level resume here without an ADR. + +## Web app (`web/play/`) +- Vanilla TypeScript + esbuild. No framework runtime, no game rules, no wire + protocol. DOM for the shell, canvas for the game surface. +- Colours and spacing come from `web/play/style.css`, which mirrors + `gui/src/theme.rs`. Felt green is only ever the roulette table. +- Build with `cd web/play && npm ci && npm run build`; commit the generated + `app.js` so the site needs no Node at deploy time. +- `/play/` is the app; `/` stays the download page. Web v1 is play-money only: + no deposits, withdrawals or KYC copy. +- The browser cookie covers refresh and the grace window only. A closed tab + that returns later logs in again. + ## GUI - The GUI is a second frontend over `ClientCore`, not a second client. Frontends send `Command` and render `View`; the core owns navigation and -- cgit v1.2.3