Appearance
Room State Persistence
Date: 2026-04-15 Status: Implemented
Decision
Tables are persistent entities backed by DB and contract. The game-server reads participant state from DB, not from a Map. In-memory state is a cache, not the source of truth.
Table Lifecycle
- Created — DB row with status
pending(settings stored, no on-chain presence) - Active — first deposit calls
registerAndDepositon-chain (atomic: register table + deposit funds) - Closed — last withdrawal brings pool to 0 → closing ceremony (settle rake,
closeTableon-chain, mark closed in DB)
No table exists on-chain without funds. No orphaned registrations.
Participant Persistence
Every state change writes to room_players via participant-db.ts:
| Event | DB Write |
|---|---|
| Enter room | Upsert row: state, wallet, userId |
| Deposit credited | Update chipBalance |
| Sit down | Update state='seated', seatIndex, chipBalance |
| Hand end | Checkpoint: chipBalance, availableChips, lastCheckpointAt |
| Stand up | Update state='at_table', seatIndex=null |
| Withdraw | Update chipBalance (decrease); if 0, markLeft |
Reconstruction on Startup
Mid-Hand Crash Recovery
Table Lifecycle (Full)
Tradeoffs Accepted
- Mid-hand crash voids the hand — chips return to pre-hand checkpoint. Standard for online poker. No mid-hand action replay.
- Additional DB writes per hand — one UPDATE per seated participant after each hand (~180 writes/hour for a 6-player table at 30 hands/hour).
- Room reconstruction adds startup latency — ~50-200ms to read participants from DB on first connect.
Key Files
apps/game-server/src/shared/participant-db.ts— DB read/write functionsapps/game-server/src/shared/room-handler.ts— BaseRoom withreconstructFromDb, DB write hooksapps/game-server/src/shared/participant-manager.ts—restoreSeatmethodpackages/contracts/contracts/facets/TableFacet.sol—registerAndDeposit,closeTablepackages/db/src/room-queries.ts— query helpers
Full ADR
The full ADR is retained in the project's internal planning archive.