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
|
# Games and the play audit
## Implemented
| Game | Status | Notes |
| --- | --- | --- |
| Slots | Done | 5 reels x 3 rows, 5 paylines, 98.6% RTP |
| Roulette | Done | European wheel, split bets, 97.3% RTP |
## Planned
| Game | Shape | Main open decisions |
| --- | --- | --- |
| Blackjack | Multi-step hand | Dealer hits/stands soft 17; double/split/insurance; blackjack payout; shoe persistence |
| Crash | Real-time round | Crash-point distribution; cashout latency; provable fairness |
Blackjack needs server-side hand state and player actions. Crash needs a timed
round loop and a cashout action, so it should come after blackjack unless we
redesign the loop first.
## Roulette
- European wheel: 0-36, one green zero.
- Bets: red, black, even, odd, and straight numbers.
- Bets can be split across all of those; every winning bet is added together.
- Straight pays 35:1 (36x returned), outside bets pay 1:1 (2x returned).
- 0 loses all outside bets. RTP is 36/37 = 97.30%.
- Numbers are color-coded in the TUI: red numbers are red, 0 is green, and
black numbers stay at the default terminal color. Bet amounts and balances
are not colored.
Client controls inside roulette:
```
R / B / E / O add the current chip to red/black/even/odd
digits + Enter add the current chip to that number
C clear all bets
+ or = / - change the chip size
SPACE spin
Q leave the table
```
The result is shown as a CS:GO-style horizontal strip: the ball marker stays
fixed and the wheel numbers scroll beneath it, ending on the winning number.
## Audit schema
Every settled play is one row in `plays`:
```
plays (
session_id TEXT PRIMARY KEY,
username TEXT NOT NULL,
game_type TEXT NOT NULL,
bet_amount INTEGER NOT NULL,
winnings INTEGER NOT NULL,
balance_after INTEGER NOT NULL,
details TEXT NOT NULL DEFAULT '{}',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
```
`details` is game-specific JSON:
```json
// slots
{"reels":"CCCCLLCLODGSDGS","win_type":"win","win_lines":"1","win_cells":"0,3,6"}
// roulette
{"number":"17","color":"black","bets":"{\"red\":10,\"black\":10,\"even\":10,\"odd\":10,\"numbers\":[{\"number\":17,\"amount\":5}]}"}
```
The balance update and the `plays` insert happen in one transaction
(`database::apply_play`).
## Wire protocol
Slots keeps its original exchange:
```
PLAY_START slots <bet>
GAME_RESULT <session> <winnings> <bet> <balance> <reels> <win_type> <win_lines> <win_cells>
```
Roulette adds a compact bets payload and a roulette-specific result:
```
PLAY_START roulette <total> <bets-json>
ROULETTE_RESULT <session> <winnings> <bet> <balance> <number> <color> <bets-json>
```
Blackjack and crash still need a generic state/action exchange, for example:
```
GAME_START <game> <bet>
GAME_ACTION <action> [json]
GAME_STATE <json> # cards, bets, multiplier, legal actions, result
```
That will be its own protocol change.
|