# Project Conventions for zagan-bet ## Type Safety - SQLite `INTEGER` is `i64`. Never cast to `u64` without a bounds check. - Balance, bet, and winnings fields must agree on `i64` or `u64` across the entire codebase — pick one and enforce it. - Negative balance is a state the DB can represent; the application must reject plays that would produce one, not silently wrap via `as u64`. ## Game Logic - Never compute the same payout/result in two places. `process_action` and `calculate_result` must not both independently derive winnings. The canonical split: `process_action` mutates state and stores raw data (reels, win_type); `calculate_result` reads that data and produces the `GameResult`. One path, one formula. - When checking symmetry (e.g. two-of-a-kind), enumerate **all** pairs, not just adjacent indices. `[a, b, a]` is a valid match. ## Session Tracking - Every `PLAY_START` must produce a session entry before the balance is touched. The session is the audit trail for the deduction; without it the deduction is orphaned. - Clean up sessions on client disconnect. If a game is in progress when the connection drops, the server must roll back the bet. ## Server Handler - Never hold a lock, drop it, then re-acquire it in the same function to mutate the same resource. Acquire once, scope the lock to the operation. - All balance mutations (`SET balance = balance ± ?`) must happen in a transaction. ## Client TUI - `render_lines()` and `render()` must agree. If `render()` uses `\r\n`, count lines against the same string the cursor will erase. Prefer returning a fixed known count for the game's static layout and letting dynamic content fill it, rather than inferring the count. ## Error Paths - When a game action fails after a balance deduction, the refund must happen in the same transaction scope as the deduction, not as a separate `execute()` call. - Never silently ignore `Err` on balance mutations. If the refund fails, log the username, balance, and operation; surface to the operator. ## Tests - Every `calculate_payout` / `calculate_result` edge case gets a unit test in `tests/`. Tests that exercise game logic use a deterministic seed or a test helper that calls the function directly, not the network. - A change to game logic that breaks an existing payout pattern must fail a test before it reaches the server. ## TLS / TOFU - See `src/tofu.rs` for the trust model. The fingerprint is over the certificate's SPKI, not the whole certificate. - `save_host` must not wipe the fingerprint column. Use `ON CONFLICT ... DO UPDATE SET last_used` — never `INSERT OR REPLACE`.