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 ++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 docs/adr/0001-gui-frontend-architecture.md (limited to 'docs/adr/0001-gui-frontend-architecture.md') 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. -- cgit v1.2.3