Executive Summary
This document describes the production gameplay loop.
The supported contract is the combination of:
- table lifecycle and state transitions in
src/game/table/ - event delivery and reconciliation in
src/websocket/,packages/shared/src/, andclient/src/hooks/useGameSocket.ts - Table V2 rendering and animation behavior in
client/src/pages/game/V2PokerTable.tsxandclient/src/components/game/v2/ - deterministic scenarios in
npm run gameplay:verify
Known future work is listed separately at the end of this document. It is not part of the supported runtime contract.
Core Modules
| Area | Files | Runtime role |
|---|---|---|
| Table lifecycle | src/game/table/table.ts | Owns table configuration, seating, hand start, forced posts, variant selection, action timers, and next-hand scheduling. |
| Action and round flow | src/game/table/table-actions.ts | Validates actions, advances betting rounds and streets, runs showdowns, distributes chips, applies rake, and completes hands. |
| State serialization | src/game/table/table-state.ts | Produces personalized table snapshots for seated players, spectators, and debug/admin views. |
| Player lifecycle | src/game/table/table-players.ts | Handles join, leave, sit out, sit back in, pending rebuys, and forced leave/fold behavior. |
| Variant progression | src/game/flow/variant-state-config.ts, src/game/flow/mixed-game-engine.ts | Maps runtime street progression and, for Mixed Game, rotates variants only at safe hand boundaries. |
| Turn and betting order | src/game/flow/turn-manager.ts, src/game/flow/stud-turn-order.ts | Tracks current actor, acted players, raise reopening, and Stud door-card order. |
| Showdown | src/game/flow/showdown.ts | Resolves standard and Hi-Lo showdowns, side pots, tied lows, and odd-chip assignment. |
| WebSocket bridge | src/websocket/table-manager.ts, src/websocket/broadcast.ts, src/websocket/pipeline/ | Turns table events into public broadcasts, private guaranteed-delivery prompts, personalized snapshots, and sequence-aware resyncs. |
| Shared client reconciliation | packages/shared/src/hooks/, packages/shared/src/stores/gameStore.ts | Applies sequence tracking, stale-event rejection, hand transitions, and mixed-game metadata consistently across clients. |
| Web client | client/src/hooks/useGameSocket.ts, client/src/store/ | Reconciles table_state, public events, private prompts, reconnect snapshots, and animation state into Table V2. |
| Table V2 UI | client/src/pages/game/V2PokerTable.tsx, client/src/components/game/v2/ | Renders board/no-board variants, Stud visibility, Hi-Lo breakdowns, mixed-game labels, and winner/forced-bet/chip animations. |
Table Lifecycle
Table.startHand() starts only from WAITING or COMPLETE, requires at least two eligible players, and refuses to start while the table is paused, closed, awaiting settlement, or held by a tournament hand-for-hand gate.
Current hand-start sequence:
- Clear any pending auto-start timer.
- Apply any pending Mixed Game rotation before the next hand is initialized.
- Reset per-hand state through
TableStateManager.resetHandState(). - Increment
handNumberand transition toSTARTING. - Rotate the dealer button after the first hand and calculate actor/forced-post positions for the active variant.
- Emit
hand_startedand run thegame:hand:startedplugin hook. - Post forced bets and deal, in the order the variant requires:
- Hold’em and Omaha: optional antes plus small blind and big blind, then hole cards.
- Short Deck: antes for all eligible players plus a button blind, then hole cards. The first player after the button acts first.
- Stud and Stud Hi-Lo: third street is dealt first, because the bring-in depends on the door cards. The lowest door card posts the bring-in, with ties broken by suit (clubs lowest, then diamonds, hearts, spades). Voluntary action continues clockwise from the bring-in, with fixed-limit completion.
- Emit canonical
forced_betsdata. Blind games also emit legacyblinds_posted. - Emit private
hole_cardspayloads with hand context for seated players. - Transition into the first betting round for the active variant.
- Start the action timer for the current actor.
- Emit
betting_startedand personalizedtable_statesnapshots.
Hand completion is symmetric:
- Finish the street, or end early when only one player remains. That player wins without a showdown and any uncalled bet is returned first.
- If at most one player can still act and others are all-in, run out the remaining streets automatically. Contested all-in hands are revealed in a
showdownevent withrevealTiming: 'all_in_before_runout'before the runout cards are dealt. - Run the standard or Hi-Lo showdown when more than one live player reaches it, and emit
showdownwith best-hand and Hi-Lo context. - Apply rake once, to the contested total. Uncalled returns are paid back at face value without rake.
- Distribute winnings and odd chips.
- Emit
hand_complete. The WebSocket layer broadcastshand_resultto clients. - Record Mixed Game progress, then apply pending config changes and rebuys.
- Schedule the next hand.
For Mixed Game tables, variant changes are deferred until hand boundaries. The next hand starts with the rotated game configuration already applied, including betting structure, card/dealing behavior, and table-state metadata.
Street Progression
Board Variants
Hold’em, Omaha, Omaha Hi-Lo, and Short Deck use the standard board progression:
preflop -> flop -> turn -> river -> showdown -> complete
At each round boundary:
- Round bets are snapshotted for collect-to-pot animation.
- Acted flags and per-round bet state reset.
- The next community-card street is dealt.
community_cardsis emitted with the new cards and cumulativeallCards.betting_startedis emitted with the next actor, current bet, pot,street, andbettingRound.
Stud Variants
Stud and Stud Hi-Lo use explicit runtime street semantics:
third -> fourth -> fifth -> sixth -> seventh -> showdown -> complete
Internally, the compatibility state machine still maps Stud streets onto base GameState values. The supported external contract no longer relies on clients inferring that mapping. Hand-critical public and private payloads carry explicit street and bettingRound fields, so Table V2 and reconnect logic can treat Stud streets as first-class runtime data.
Current Stud behavior:
- third street deals two down and one up card
- fourth through sixth streets deal one up card each
- seventh street deals one down card
- bring-in starts on third street, later streets act high-hand-first from exposed cards
- no community-card area is rendered in Table V2 during live Stud hands
- if eight players reach seventh street, the deck cannot cover eight more cards, so a single face-up community card is shared by all players as their seventh card
Event Contract
Public Gameplay Events
The current production contract is:
table_state: authoritative personalized snapshot for seated players and spectator-safe snapshot for watchersforced_bets: canonical forced-post event for live chip movement and forced-post labelsbetting_started: current betting round contextcommunity_cards: board reveal events for board variantsplayer_acted: validated player actions and stack contribution changespot_update: running pot and side-pot changesshowdown: revealed/mucked cards plus high and low breakdowns when applicablehand_result: final winners, payout amounts, and Hi-Lo result context
Every hand-critical public event is emitted with table context:
tableIdhandNumbergameTypestreetbettingRound
Sequenced public events are enriched with _seq and _ts. Critical public events run through the guaranteed-delivery path before Table V2 reduces them.
forced_bets is the canonical forced-post contract. Current supported runtime usage is:
- Hold’em and Omaha:
small_blind,big_blind, and optionalante - Short Deck:
anteandbutton_blind - Stud and Stud Hi-Lo:
bring_in
blinds_posted still exists only as a guarded legacy fallback. New runtime or UI work should not depend on it.
Private Hand-Critical Events
The supported private events are:
hole_cardsaction_required
Both include the same table context as public hand-critical events:
tableIdhandNumbergameTypestreetbettingRound
They are delivered through the user-targeted guaranteed-delivery path so reconnect and stale-event handling share the same context model as public events.
Resync Contract
request_table_state is the recovery entry point for seated-player reconnects and stale-sequence repair:
- The server responds with the current personalized
table_statewithout inventing a new gameplay sequence. - Shared sequence helpers seed the current sequence from that response.
- The server resends the current
action_requiredprompt when the requester is still the current actor. - The client rejects stale
hole_cardsandaction_requiredpayloads by hand number and street.
Client Reconciliation Model
client/src/hooks/useGameSocket.ts is the runtime bridge between WebSocket events and Table V2. The supported reconciliation model is:
- Apply
table_stateas the authoritative snapshot. - Preserve hero-private cards when later public snapshots omit them.
- Apply sequenced public events in order through shared sequence helpers.
- Reject duplicate or stale events by sequence, hand number, street, and betting round.
- Preserve showdown cards and winner state through the hand-transition window.
- Queue chip animations from
forced_bets,player_acted, collect, and payout events. - Defer next-hand state until the winner-cleanup window finishes.
- Restore state and the current action prompt after reconnect via
request_table_state.
Table V2 depends on that model for:
- board vs Stud layout switching
- Stud face-up and face-down card visibility
- Short Deck ante/button-blind chip movement without compatibility SB/BB badges
- Hi-Lo showdown overlays for split, scoop, no-low, and tied-low outcomes
- Mixed Game active/next game labels and clean variant-to-variant resets
- reduced-motion and animation-disabled behavior without losing readable state
Production Variant Baseline
| Game type | Supported runtime behavior |
|---|---|
TEXAS_HOLDEM | 52-card deck, 2 private hole cards, blinds, standard preflop/flop/turn/river progression, standard showdown, side pots, rake with no-flop-no-drop by default, sequenced reconnect-safe client state, and Table V2 board/chip/winner rendering. |
OMAHA | 4 private hole cards, board runout, exactly 2 hole plus 3 board evaluation, pot-limit default betting, and Table V2 private-card rendering. |
OMAHA_5 | 5 private hole cards, board runout, exactly 2 hole plus 3 board evaluation, pot-limit default, 8-seat cap for 52-card deck capacity, and Table V2 five-card private-card layout. |
OMAHA_HI_LO | Omaha high-hand rules plus 8-or-better low qualification, split/scoop/no-low/tied-low showdown payload parity, and Table V2 Hi-Lo presentation. |
SHORT_DECK | 36-card deck with ranks 2-5 removed, ante-for-all plus button blind forced posts, repo-default Short Deck ranking where flush beats full house, no SB/BB compatibility badges, and normalized forced-bet animations. |
STUD_7_CARD | Third street starts with two down and one up card, bring-in by lowest door card with suit tie-break, completion/fixed-limit street progression through seventh street, per-card visibility in table state, no board area, and Table V2 BI/up-card/down-card rendering. |
STUD_HI_LO | Stud runtime behavior plus 8-or-better low qualification, split/scoop/no-low/tied-low payload parity, and Hi-Lo Table V2 display. |
MIXED_GAME | Ordered rotation across TEXAS_HOLDEM, OMAHA, OMAHA_5, OMAHA_HI_LO, SHORT_DECK, STUD_7_CARD and STUD_HI_LO, changing after a configured handsPerVariant and only at hand boundaries, persisted current/next-game state, per-variant betting/dealing/card-count/seat-cap overrides (8 seats for OMAHA_5 and Stud components, otherwise 10), reconnect-safe mixedGame snapshots, and Table V2 active/next game display with clean board/Stud layout resets. |
The deterministic verification command is npm run gameplay:verify.
Local Runtime Verification
For gameplay or Table V2 work, the expected local sequence is:
./scripts/game.sh start
./scripts/game.sh status
curl -sS http://localhost:3000/health/live
curl -sS http://localhost:3000/health
npm run gameplay:verify
./scripts/game.sh stop
With the game running, check visual and table behaviour in a browser at http://localhost:5173.
Known Future Work
The following items are not required to treat the current runtime as supported:
- broader browser automation beyond the current deterministic suite
- additional mixed rotations or game families beyond the supported component variants already in the runtime
- future UI redesign work for Table V2
- operational scaling work such as Redis-backed multiplayer fan-out, larger load testing, and capacity tuning