From 32066e3c4903b96242cc7cbc82a43138517f3439 Mon Sep 17 00:00:00 2001 From: salmonik Date: Sun, 1 Nov 2026 17:33:17 +0100 Subject: docs: record the GUI architecture and version lockstep; adopt GPL-3.0-or-later --- docs/adr/0001-gui-frontend-architecture.md | 89 ++++++++++++++++++++++++++++++ docs/adr/0002-version-lockstep.md | 55 ++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 docs/adr/0001-gui-frontend-architecture.md create mode 100644 docs/adr/0002-version-lockstep.md (limited to 'docs/adr') diff --git a/docs/adr/0001-gui-frontend-architecture.md b/docs/adr/0001-gui-frontend-architecture.md new file mode 100644 index 0000000..44dca32 --- /dev/null +++ b/docs/adr/0001-gui-frontend-architecture.md @@ -0,0 +1,89 @@ +# 0001 — GUI is a second frontend over a shared core + +Status: accepted, 2026-11-01 + +## Context + +The client is a terminal UI built around `ClientCore`, which is already +headless and tested without a terminal (`tests/client_core.rs` drives it with a +fake screen). The GUI must add a second presentation of the same product with +these constraints: + +- Installed desktop application. No browser, no webview, no macOS. +- Linux x86_64 and Windows x86_64, cross-compiled where possible; i686 stays + TUI-only. Windows 10 is the floor. +- "Lightweight": ~40 MB binary, ~150 MB idle memory, ~1 s cold start. +- Effects are tweened 2D animation: reels, wheel, cards, chips, particles, a + live crash curve. No 3D, no audio for now. +- egui is chosen for rendering; the repository is GPL-3.0-or-later. + +The existing boundary is terminal-shaped: `Effect` carries lines of text, +`handle_key` carries arrow keys, and `Game::render` returns a formatted board. +None of it can be reused by a GUI as-is. + +## Decision + +1. **One brain, two frontends.** `ClientCore` owns state, navigation, and + flows. Frontends send `Command` and render `View`. The TUI is the first + adapter and stays green throughout the refactor; roulette is ported first + because it has the largest UI surface. +2. **The boundary is semantic, not textual.** `Command` covers app flows + (connect, trust, login, play, leave) and game actions (`PlaceBet(BetTarget)`, + `Spin`, `Hit`, `CashOut`). `View` covers app screens + (`Connect`, `Trust`, `TrustMismatch`, `Login`, `Lobby`, `History`, + `Game(GameView)`) and per-game state (reels, felt with chips and cursor, + hand with legal actions, crash phase and multiplier). +3. **The core owns navigation.** Which screen is showing and which game is open + is state, not presentation; both frontends must behave identically. +4. **egui (eframe).** Immediate mode suits per-frame custom drawing; forms, + text input, and scrolling come free; MIT/Apache; ~10–20 MB. Slint was + rejected for its retained/declarative model, which fights per-frame drawing + of the crash curve and particles. Macroquad was rejected because every input + field and list would be hand-built. Webviews were rejected on memory and + system dependencies. +5. **A `gui/` workspace member.** The existing package keeps the core, TUI, and + server bins and all current test paths. egui's ~150 transitive crates must + never enter the TUI or server build graph — the same mistake as the + package-wide `native-tls` dependency. +6. **A blocking network thread with channels.** The core is I/O-free; the + adapter owns the TLS stream. One thread reads/writes, forwards every line, + and wakes the UI via `egui::Context::request_repaint`. No async runtime: + the protocol has one request in flight plus pushes. Request deadlines live + in the shared adapter so both frontends time out identically. +7. **First contact is explicit.** The handshake completes with a record-only + decision, then the adapter asks before sending anything. New key: modal with + the fingerprint. Changed key: hard stop with an explicit "forget and + reconnect". The TUI keeps its print-and-accept prompt via the same state + machine. +8. **Single-file binaries with embedded assets**, named + `zagan-bet-gui---[.exe]`, plus bash/PowerShell + installers. The site lists both. No auto-update; the lobby points at the + download page when the server advertises a newer release. + +## Consequences + +- The client core is rewritten at its boundary before any GUI code exists: + `Effect`, the `Game` trait, and `handle_key` all change. This is the cost of + keeping one implementation of reserve/settle/forfeit and payout rules. +- The TUI's terminal concerns (prompt editing, wheel animation, interactive + input) move into the TUI adapter, where they belong. +- The GUI must be themed deliberately; egui's defaults look like a debug tool. + A style spike on one reference screen gates the game ports. +- Windows binaries are unsigned; SmartScreen will warn until a certificate is + bought. No auto-update means players download again. +- The refactor is testable headlessly: the TUI adapter and the core tests + remain the regression net, and `View` can be asserted without pixels. + +## Alternatives considered + +- **Separate GUI client** reusing only rules and protocol: faster to start, + duplicates connection/session/timeout flows and lets the two clients drift on + the invariants in `AGENTS.md`. Rejected. +- **Hybrid: GUI reads `GameState` directly**: avoids the refactor but creates a + second parser of stringly-typed data, guaranteeing renderer drift. Rejected. +- **Slint**: best declarative animation story and smallest runtime, but the + retained model is wrong for per-frame custom drawing. Rejected after the + license objection (GPL) was removed by open-sourcing. +- **Tauri / webview**: easiest effects, but a system webview breaks the memory + budget and adds a distro-dependent dependency. Rejected. +- **tokio**: no concurrency to exploit. Rejected. diff --git a/docs/adr/0002-version-lockstep.md b/docs/adr/0002-version-lockstep.md new file mode 100644 index 0000000..1e7f8ba --- /dev/null +++ b/docs/adr/0002-version-lockstep.md @@ -0,0 +1,55 @@ +# 0002 — Client and server must share MAJOR.MINOR + +Status: accepted, 2026-11-01 + +## Context + +`AGENTS.md` previously described MINOR as backwards compatible: an old client +should work with a newer server and vice versa. In practice the wire format, +game messages, and client flows have changed in every MINOR so far +(`PLAY_START` payloads, `CRASH_*`, `HISTORY`, cursor and bet semantics). Silent +half-compatibility produces confusing failures: a new client sends a command +the old server echoes back as text, or an old client misparses a new field. + +The project now ships multiple frontends (TUI and GUI) and a server, all +released together from one repository. + +## Decision + +1. **Version compatibility is MAJOR.MINOR lockstep.** A client and server may + differ only in PATCH. Any difference in MAJOR or MINOR refuses the + connection. +2. **The first line after the TLS handshake is `HELLO `.** The server + replies with its own version, the latest release version, and the download + URL. Any other message before `HELLO` is rejected. No credentials are sent + before the versions agree. +3. **The server is authoritative about the latest release.** It advertises + `latest` and the download URL in the handshake; the client compares its own + `CARGO_PKG_VERSION` and offers a link when it is behind. The download site + is not queried by clients: that would add a second TLS stack and a web PKI + trust domain for one string. +4. **A MAJOR bump covers a wire change; a MINOR bump covers a feature that + changes the protocol or the shared semantics.** Both require releasing + clients and server together. + +## Consequences + +- Every MINOR release must update the server and all clients simultaneously; + a stale server is a hard failure, not a degraded experience. Deployment order + matters. +- `AGENTS.md`'s MINOR rule is rewritten; the old "old clients work with new + servers" promise is withdrawn. +- The handshake is itself a breaking wire change: this lands as v4.0.0. +- The update check needs no extra dependency and works for both frontends, at + the cost of only being as fresh as the server you connect to. + +## Alternatives considered + +- **Keep MINOR backwards compatible** and negotiate per-feature: more work, + and every feature so far would have needed a capability flag. Rejected. +- **Fetch `version.json` from the download site**: site-authoritative updates, + but adds an HTTP client and a CA trust store with different rules from the + pinned game server. Rejected. +- **Semver-range matching**: allows broader compatibility but makes the + failure modes depend on which optional features each side implemented. + Rejected for a small, tightly released product. -- cgit v1.2.3