1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
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-<version>-<platform>-<arch>[.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.
|