Appearance
Seeded Table Pool
The lobby is find-and-join: the platform keeps at least one registering SnG open per enabled table template, seeding a replacement when one fills. Players never create tournaments — they join the format they want. Admin one-offs (the admin new-tournament page) still exist for special events.
How it works
Three pieces, all in game-server:
- The template registry —
apps/game-server/src/shared/table-templates.ts. Each template is a complete, validated SnG archetype: settings (seats, entry fee, parametric blind structure, payouts), audiencekind,moneyKind(play|real), and display metadata for the lobby.templateRevOf(t)fingerprints everything that shapes the created row (display excluded — relabeling a poster never recycles live tables). - Enablement — the
enabledTableTemplatesglobal game-setting (env-scoped, opt-in: unset = pool off), edited from the admin config switchboard's "seeded table pool" card. Enable/disable is runtime; changing a template's numbers is a deploy (the registry is code in v1). - The keeper —
apps/game-server/src/shared/table-pool-keeper.ts. A reconciler (boot pass +TABLE_POOL_KEEPER_MStick, default 60s,TABLE_ENVIRONMENT-scoped) that counts liveregisteringinstances per enabled template and seeds through the SAMEcreateTableCorethePOST /tablesroute uses — one validator, two callers. Seeded rows carrysettings.templateKey+settings.templateRev.
Lifecycle rules
- Fill → respawn. A seeded table that fills auto-starts (the normal register-first flow); its template's live count drops to 0 and the next tick seeds a fresh instance (~1 minute prod; tests drive passes deterministically via the triple-gated
POST /internal/test/pool-pass). - Disable → recycle. Unchecking a template on the switchboard closes its EMPTY instance next tick (
transitionTableState, reasontemplate_recycled— the lobby notifier fires). Re-enabling seeds a fresh one. - Edit → replace. A deploy that changes a template's settings changes its
templateRev; empty instances on the old rev are recycled. An instance with a paid registrant is untouchable — it plays out on the definition its players bought into. An instance with an in-flight deposit (non-terminaldeposit_intents< 1h) is deferred, not recycled — the chips haven't credited yet, so the funded predicate can't see the money (pool.deferred_inflighton the span). - No reaping. Empty
registeringtournaments are excluded from the idle reaper's empty-close (see Table lifecycle) — the keeper is their only closer. Funded-abandoned fund recovery is unchanged. - Cash templates (
moneyKind: 'real') seed only when the real economy is reachable (CHAIN=solanaandskpk_enabledon). Otherwise the keeper skips them (never a refused-create loop) and the lobby's Cash view renders them as a static coming-soon showcase — dollar-denominated (cents convention: 1 real chip = 1¢), money-green, no join affordance.
The lobby
The tournament tab groups by what the pool serves: each seeded row shows its template's display name ("6-handed Top-3 Turbo — 3/6 → register"). When a cash side exists, a Play ⇄ Cash toggle appears; Cash shows live real-money tournaments where the economy is on, else the coming-soon showcase (served by GET /table-templates, which reports each enabled template + available).
Who can create tables
Tournament creation is operator-only at the API (POST /tables mode:'sng' requires the service key or an admin-role JWT — coded tournament_create_operator_only otherwise). This is what makes the reap-removal safe: "creation is bounded" is enforced by the route, not by the lobby's missing button. Cash-table creation by players is unchanged. settings.templateKey/templateRev are keeper-reserved provenance stamps — every external caller (player JWT and service-key) is refused (template_stamp_reserved); only the in-process keeper writes them.
Adding a template
- Add an entry to
TABLE_TEMPLATESinapps/game-server/src/shared/table-templates.ts. The registry unit test runsvalidateTableSettingsover every entry — an invalid template fails CI, never the keeper at 3am. - Add its key + label to
TEMPLATE_OPTIONSin the admin config page (apps/admin/app/config/page.tsx) — the switchboard is a hardcoded field list; a template without an entry is operator-invisible. - Deploy, then check it on the switchboard. The keeper seeds it within a tick.
Telemetry
One table_pool.ensure span per pass: pool.environment, pool.templates_checked, pool.created, pool.recycled, pool.skipped_real, and pool.failures + error.code=pool_template_failed (span status ERROR) when any template's leg failed. Per-template failures are isolated — one bad template never starves the rest.