From 6e2787d792de64c003eff03b62a2df798ef851c0 Mon Sep 17 00:00:00 2001 From: salmonik Date: Thu, 29 Oct 2026 20:43:09 +0100 Subject: v2.3.0: add crash with local multiplier and pushed crash event --- docs/GAMES.md | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--- docs/TESTING.md | 18 ++++++++++++++++ 2 files changed, 80 insertions(+), 3 deletions(-) (limited to 'docs') diff --git a/docs/GAMES.md b/docs/GAMES.md index ae15d50..844d1b0 100644 --- a/docs/GAMES.md +++ b/docs/GAMES.md @@ -7,15 +7,52 @@ | Slots | Done | 5 reels x 3 rows, 5 paylines, 98.6% RTP | | Roulette | Done | European wheel, split bets, 97.3% RTP | | Blackjack | Done | Six-deck shoe per hand, S17, 3:2, hit/stand/double | +| Crash | Done | Local multiplier, server-owned clock and crash event, 99% RTP | ## Planned | Game | Shape | Main open decisions | | --- | --- | --- | -| Crash | Real-time round | Crash-point distribution; cashout latency; provable fairness | +| — | — | Provable fairness for the crash point (`hash` commit/reveal) | + +Crash rounds are private to one player and use the same request/response socket +plus one server-initiated line. A shared multiplayer round would need a lobby +and broadcast, which is a different product. + +## Crash + +- The multiplier is `exp(0.1386294 * t)`: 1.00x at the start, 2.00x at 5s, + 10x at ~16.6s, capped at 1000x. +- The crash point is drawn when the round starts: + `max(1.0, (1 - 0.01) / (1 - u))` for a uniform `u`. That gives + `P(crash >= m) = 0.99 / m`, so a fixed-target cashout returns 99% of the + stake. The cap trims the RTP by a hair. +- The crash point is **not** sent to the client until the round ends. Knowing it + early would let a patched client cash out at `crash - epsilon` every round. +- The client draws the multiplier from its own monotonic clock, anchored when + `CRASH_START` arrives. That number is display only. +- The server computes the cashout multiplier from its own clock when the + request arrives. The client never sends a multiplier. A cashout that arrives + after the crash time loses the bet. +- Because `P(crash = 1.00x) = 1%`, an instant cashout returns the stake 99% of + the time and nothing 1% of the time. There is no risk-free window, which is + why there is no cashout lockout. +- Payouts are `floor(bet * multiplier)` in whole PLN, so anything under 1.10x + on a 10 PLN bet returns 10. +- When the crash time passes, the server pushes `CRASH_BUSTED` on the same + connection without being asked. The client's event loop polls for keys and + drains the socket every 25ms. +- Leaving with Q or disconnecting during a round forfeits the reserved bet and + writes a `result = forfeit` audit row. -Crash needs a timed round loop and a cashout action, so it should be its own -protocol/loop change. +Client controls: + +``` +C cash out at the server's current multiplier +SPACE start the next round ++ or = / - change the next round's bet +Q forfeit an active round (bet is lost), or leave when idle +``` ## Blackjack @@ -92,6 +129,9 @@ plays ( // blackjack {"player":["10S","6H","5D"],"dealer":["9D","8C"],"player_total":21,"dealer_total":17,"bet":10,"result":"win","winnings":20} + +// crash +{"bet":10,"crash_point":3.0,"started_at":12.44,"phase":"cashed","result":"cashed","multiplier":2.0,"payout":20} ``` The balance update and the `plays` insert happen in one transaction @@ -124,3 +164,22 @@ BLACKJACK_STATE `BLACKJACK_STATE` carries the player/dealer cards, totals, actions, balance, result and winnings. The dealer's hole card is `?` until the hand is done. + +Crash sends one line when the round starts, one line when it crashes (without a +request), and answers the cashout: + +``` +PLAY_START crash +CRASH_START + +CRASH_CASHOUT +CRASH_STATE + +CRASH_BUSTED # pushed when the crash time passes +CRASH_FORFEIT +CRASH_STATE +``` + +`CRASH_STATE` carries `session_id`, `phase` (`running`, `cashed`, `crashed`, +`forfeit`), `bet`, `balance`, the current/final `multiplier`, `potential`, +`crash_point` (null while running), `payout` and `result`. diff --git a/docs/TESTING.md b/docs/TESTING.md index ec5c681..3c7fa61 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -14,6 +14,7 @@ shared global database. Each test owns its state. | `tests/client_core.rs` | Client command/game flows via a headless screen applying logical effects. | | `tests/client_ui.rs` | Help, history, completion, bet parsing, display width, cursor math. | | `tests/blackjack.rs` | Card totals, naturals, hit/stand/double, dealer bust, S17, state parsing. | +| `tests/crash.rs` | Multiplier curve, payout floor, crash-point distribution, house edge, cap. | | `tests/protocol.rs` | Newline buffering and timeout/error classification. | | `tests/roulette.rs` | European wheel, split-bet payouts, zero handling, result parser. | | `tests/server_core.rs` | Handler, bcrypt migration, SQL injection, transaction rollback, `plays` audit, sessions. | @@ -93,6 +94,23 @@ Use an 80-column terminal because several output lines are width-sensitive. double), winnings, and the final cards/result in `details`. A forfeit has `result = forfeit`, `winnings = 0`. +## Manual crash checklist + +1. `play crash` shows the ready board with a 1.00x multiplier; Space starts and + reserves the bet. +2. The multiplier rises smoothly with no network traffic. Cash out with C and + confirm the board shows the cashout multiplier, the payout and the new + balance. +3. On another round, press nothing. The board must switch to `CRASHED at Xx` + by itself when the crash point is reached, and the bet must be lost. +4. Press C just as a round crashes. Either it pays or the crashed result wins; + a late cashout must never show an error over a finished round. +5. Q during a live round forfeits the bet; Q when idle just leaves. +6. Check the audit rows: one per round, `game_type = crash`, the reserved bet, + the payout, and `crash_point`/`multiplier`/`result` in `details`. +7. Confirm the crash point is absent from the `CRASH_START` line (it must only + appear once the round is over). + ## Manual server checks - Register, log out, log in again (bcrypt path). -- cgit v1.2.3