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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
|
# GUI plan
Decisions and rationale live in `docs/adr/0001-gui-frontend-architecture.md` and
`0002-version-lockstep.md`. This file is the order of work.
## Non-goals
Audio, 3D, macOS, auto-update, a browser client, and pixel-golden tests in CI.
## Phase 0 — version handshake (v4.0.0)
Breaking wire change, released for client and server together.
- `HELLO <version>` is the first line after TLS. The server answers
`HELLO <version> latest=<version> url=<download-url>` and rejects every other
message with `ERROR HELLO required` until it has seen one.
- MAJOR.MINOR must match; PATCH may differ. A mismatch is refused before any
credentials are sent.
- The client measures its connect latency with the `HELLO` round trip and
prints a notice when the server advertises a newer version.
- `DOWNLOAD_URL` is read from the environment by the server, with the public
site as the fallback.
**Gate:** mismatch refused in both directions, `HELLO` required before auth,
existing game flows unchanged, new tests for version parsing and gating.
## Phase 1 — core boundary and roulette (done)
Complete in v4.0.7. Every game plus `Lobby` and `History` are structured views;
the core builds the view and frontends send `Command`. The client transport
(TCP, rustls, TOFU, `HELLO`, auto-login) lives in `client::transport` behind a
trust callback. The command line moved out of the core in two pieces:
`client::input` owns the editor, history and key mapping, and `client::shell`
parses a finished line into a core `Command` or an adapter action (connect,
login, register, set-credential, host maintenance). `ClientCore` no longer
holds a `Key`, a prompt buffer, or any of the old TUI-shaped effects
(`Interactive`, `SetPrompt`, `ListHosts`, `ForgetHost`, `AnimateRoulette`); the
roulette wheel animation is derived from the `Result` view by the adapter.
"Log out" (`logout`) and "forget saved login" (`forget-login`) are distinct: the
latter drops the credential for the pinned key and keeps trust.
The `Connect` and `Trust` screens are adapter-owned for now: the terminal asks
in the transcript, and the GUI will draw them as its own screens over the same
`client::transport` callback. They are not core state because the core never
sees a certificate.
- Introduce `Command` and `View`; move terminal-only concerns (prompt editing,
wheel animation, interactive prompts) into the TUI adapter.
- App screens: `Connect`, `Trust`, `TrustMismatch`, `Login`, `Lobby`,
`History`, `Game`.
- Port roulette first: `PlaceBet(BetTarget)`, `SetChip`, `ClearBets`, `Spin`,
and a `FeltView` carrying chips, cursor, and the last result.
- Add `forget-login` so "log out" and "forget saved login" are distinct in both
frontends.
- Auto-login stays: a stored credential for the pinned fingerprint logs in
automatically after the trust check.
**Gate:** met. The TUI behaves as before (same boards, keys and prompts), its
tests drive the real adapter path (`shell::parse`, `Editor`, `game_input`), and
`ClientCore` contains no terminal types.
## Phase 2 — the other three games
Slots, blackjack, crash ports with their own `GameView` variants and commands.
Server push (`CRASH_BUSTED`) feeds the same `View`. The TUI keeps animating the
crash board from its local clock.
**Gate:** all four games playable in the TUI through `Command`/`View`.
## Phase 3 — `gui/` skeleton and the style spike (done)
- `gui/` is a workspace member (`zagan-bet-gui`). It depends on the core with
`default-features = false`, so neither egui nor native-tls enters the TUI or
server build graph. The build uses the workspace's `gui-release` profile
(opt-level `z`, LTO, stripped).
- `gui/src/theme.rs` holds the tokens: the terminal-derived dark palette, felt
green kept for the table, a 4/8/12/16/24 spacing scale, radii, and the type
scale. Two embedded fonts: Open Sans (OFL) for text, Hack (MIT) for numbers,
chips and labels. Licences sit next to the files in `gui/assets/fonts/`.
- `gui/src/roulette.rs` draws the felt, chips and wheel from a `RouletteView`;
it owns the geometry, not the rules. The felt is pointer-first: the hover
snaps to the fine lattice, so the inner half of a cell is a straight, an edge
is a split and a crossing is a corner. Left click adds the selected chip,
right or middle click takes one back, and a translucent chip previews the
target under the pointer.
- `gui/src/app.rs` composes the persistent header (connection, account,
balance) with the table and the betting sidebar. The demo state animates a
2.4 s wheel spin, highlights the winning pocket and cell, and reports the
amount paid and the net for the round. SPIN and CLEAR sit side by side so
clearing is never below the fold.
- `gui/tests/layout.rs` covers the pure maths (money grouping, wheel angle,
table aspect, bet names). UI wiring tests arrive with `egui_kittest` in
Phase 4.
Measured on Linux at 1080p with llvmpipe software GL:
| Check | Result |
| --- | --- |
| Linux binary | 6.0 MB |
| Windows x86_64 binary | 4.7 MB |
| Cold start to window | 52 ms |
| Idle RSS | 137 MB |
| Idle CPU | 0% |
The Phase 4 screenshots in `docs/screenshots/` supersede the spike captures;
they show the same layout with the real session and the pinned action bars.
**Gate:** met. The style was approved and the spike grew into Phase 4.
## Phase 4 — GUI wiring (code complete, v4.2.0)
- `gui/src/net.rs` is the adapter's network worker. One blocking thread owns the
TLS stream; `Command` goes in, `Event` comes out (connected, failed, trust,
line, closed), and every event calls `Context::request_repaint`. There is no
async runtime.
- First contact is a modal that answers the transport's trust callback through
a channel. A changed key is a hard stop with an explicit forget-and-retry;
a version mismatch links to the download page; a saved credential auto-logs
in before the login screen is ever shown.
- Screens: connect (remembered hosts, forget trust), login/register with a
"remember this login" choice, lobby with the update notice, history table,
and the four games. "Log out" and "forget saved login" are separate. Leaving
a live hand or round asks first and forfeits the reservation.
- Games are painted from `View`: slots with a 0.9 s reel animation and payline
highlights, roulette with the wheel spin, the hover lattice, chip placement
and payouts, blackjack with vector-drawn suits and legal actions, crash with
a sampled curve, cashout and a server-drawn crash point. A refused spin shows
the error and never runs the animation: the reels and the wheel wait for the
server's answer, not for the click.
- Sidebars pin their action block below the content, so SPIN, CLEAR, DEAL and
START never move as the bet slip or a hand grows. The embedded fonts have no
suit or arrow glyphs, so suits are shapes and navigation uses plain words.
- The header balance is held at the staked amount while the reels or wheel
turn, then jumps to the settled value: the GUI never reveals a payout before
the animation does. Refused spins cancel the animation and show the error.
The crash multiplier keeps full precision in the game state so the chart is
a smooth line; the terminal still formats it to two decimals.
- `gui/tests/layout.rs` covers money, wheel angles, lattice snapping, table
aspect and bet names.
Verified live against a v4.2.0 server: register, login, auto-login, trust,
all four games settling, history, logout, disconnect, the leave-forfeit
confirmation, and the version-mismatch screen (new client against a 4.1.1
server).
Screenshots: `docs/screenshots/gui-{lobby,slots,roulette,blackjack,crash,
history}-1080p.png`, plus the roulette result state.
Measured on Linux at 1080p with llvmpipe software GL:
| Check | Result |
| --- | --- |
| Linux binary | 8.3 MB |
| Windows x86_64 binary | 6.8 MB |
| Cold start to window | 61 ms |
| Idle RSS | 131 MB |
| Idle CPU | 0% |
**Gate:** the screenshot set is ready for review. Remaining for the gate:
frame rate on integrated graphics and the Windows interactive pass.
## Phase 5 — packaging
## Phase 5 — packaging
- `publish.sh` emits `zagan-bet-gui-<version>-<platform>-<arch>[.exe]`.
- Bash and PowerShell installers that verify the SHA-256, install to the user
directory, and create `.desktop`/Start-menu entries.
- Download page lists raw binaries and installers; add the source-code link.
**Gate:** `deploy.sh` verifies both artifact kinds; a clean machine installs
from the page and launches.
## Testing split
- `View`/`Command` tests in the core: flows, gating, state. Always.
- The TUI adapter remains the regression net through Phases 1–2.
- `egui_kittest` for wiring that `View` cannot see; no pixel goldens in CI.
- Manual for feel, animations, and layout at different window sizes.
|