Skip to content

Tournament Resolution

Stage 3 of a Sit-n-Go's lifecycle — completion: a tournament concludes, the winner is paid, and the results page shows the final standings.

The three stages:

  1. Registration — players buy in.
  2. Play — hands deal.
  3. Resolutionthis page.

Prediction markets on tournaments are in development.

The resolution chain

A tournament reaches ended either naturally (players bust down to a lone survivor) or via an operator forceEnd (for wedged/abandoned tables). Both paths run the same flow:

playing → ended → settling → closed

   ├─ elimination_order written to table_players (position 1 = winner)
   └─ onPlayingEnded():
        └─ payout → payout_intents → bank /api/withdraw → withdrawFor (on-chain) → confirmed → closed

One source of truth for the winner

The payout reads the winner from the authoritative DB table_players.elimination_order (readAuthoritativeStandings()), the same source the /tournaments/:id/results endpoint serves. It never reads the orchestrator's in-memory standings — which can drift from the DB on a reconstructed forceEnd and would otherwise pay the wrong player.

onPlayingEnded is shared by both end paths

The payout dispatch lives in onPlayingEnded, so both the natural bust-down and the operator forceEnd run the identical settlement — a force-ended tournament pays out exactly like one that concludes on its own.

The results page

TournamentResults (rendered at /table/[id] for a concluded tournament) reads the durable results — standings by finishing place, per-position prize, on-chain paid status. It's the loudest surface in the app by design.

Operator disposition

POST /admin/force-end-tournament { tableId } (service-key) ends a wedged tournament by current engine stack (deepest = winner) and runs the full resolution. Valid only from playing.

Known residuals

  • The natural bust-driven conclusion works but is slow (blind erosion); forceEnd is the fast disposition.