Skip to content

2026-06-12 — Player-funds lifecycle audit shipped

The full audit closed across six phases. Table states mean something now, the player journey survives a restart at every stage, and the trace is the history.

What changed

  • One table-state machine. transitionTableState() is the only writer of tables.status / freeze_reason / closed_at — legal transitions enforced (open⇄frozen, open→closed, frozen→closed), atomic writes, a table.state_changed span per transition, live table_state_changed broadcast to connected clients, and the lobby drops frozen/closed tables. Custody freeze, close-on-empty, unfreeze, and escape all migrated onto it.
  • Participant end-of-life. Cash-out zeroes the row, frees the seat, and unbinds the decision agent in one atomic write (state='gone'). Forced withdraws re-read the row and 409 already_settled; sit_in gates on the authoritative DB balance (sit_in_no_chips).
  • No seated-with-0-chips window. The reserved→seated broadcast is deferred until the engine seat carries the chips.
  • Restarts heal. Reconstruction mirrors the engine (stack = chip_balance), pending blind requests are replayed to reconnecting clients with their original deadlines, sit-out state writes through to the DB, and mid-hand restarts void to the checkpoint without tripping the conservation alarm. Verified by a stage-parameterized restart matrix (test/lifecycle/restart-matrix.test.ts).
  • Escape pays for real. Two bugs fixed: the escape route was Sepolia-only (couldn't pay on local), and it read the wrong snapshot field (chipBalance vs chipCount) — every escape paid 0 while looking successful. Escapes now pay the exact per-player split on-chain and close the table.
  • Abandoned tables reach a conclusion. The idle reaper closes empty idle tables and escapes funded abandoned ones (thresholds in game_settings).
  • Telemetry completeness. participant.seated / participant.stood_up spans, a table.rejection span for every coded WS rejection (emitted at the sendTo chokepoint), table.reconstruction at boot, and the full journey is replayable from spans alone (T22).

Hardening pass (post-close)

A post-close hardening pass added seven confirmed-then-fixed fixes: reaper eligibility blind spots (in-flight deposits, registrant equity, non-poker liveness), escape underpaying sitting-out players, the seated broadcast firing on the engine-failure path, the state machine's same-state heal, and on-chain closeTable convergence (a new admin-server sweep — which found a real stranded table on its first boot). Escape memorialization (T33) was handed to the custody-layer plan.

Now-current pages

  • Table Lifecycle (new)
  • Table frozen — recovery runbook (rewritten around the working unfreeze/escape)