Skip to content

feat(pvm): Complete sit-in / sit-out / leave table state logic (cash games) #51

Description

@bitcoinbrisbane

Source Document: Block52 – Sit In _ Sit Out _ Leave Table State Logic (Cash Games).pdf in repo root

Overview

This ticket defines the complete player seating, sit-in, sit-out, leave-table, and all-in protection logic for Block52 cash games.

The system must:

  • Treat seats as placeholders
  • Require explicit opt-in to play
  • Prevent blind evasion
  • Allow graceful or immediate exits
  • Protect all-in players from accidental forfeits
  • Enforce rules server-side without confusing the player

1) Player States — DONE

SDK Reference: PlayerStatus enum in sdk/src/types/game.ts:51-61
Issue: block52/poker-vm#1831 | PR: block52/poker-vm#1834 (merged)

PDF Term SDK Enum (PlayerStatus) Status
SEATED SEATED Aligned
SIT-IN PENDING SITTING_IN Renamed — use SITTING_IN
PLAYING (Active) ACTIVE Renamed — use ACTIVE
SITTING OUT SITTING_OUT Aligned
REMOVED (not required — player removed from seat array) Struck — not a status

Additional SDK statuses not in this document (used during hand play):

  • FOLDED — player folded current hand
  • ALL_IN — player is all-in in current hand
  • BUSTED — player has 0 chips (cash game: needs top-up, SNG: eliminated)
  • SHOWING — player is showing cards at showdown
  • WAITINGRemoved in PR block52/poker-vm#1834. Was unused (zero references). Redundant with SITTING_IN.

Sub-issue: block52/poker-vm#1831 — Align player state naming between document and SDK

SEATED (Placeholder / Not Active)PlayerStatus.SEATED

  • Player holds a seat
  • Player is not dealt in
  • Player must choose how to enter play

SIT-IN PENDINGPlayerStatus.SITTING_IN

  • Player has clicked Sit In
  • A sit-in method is selected
  • Player is waiting for eligibility conditions
  • Player is not dealt in

PLAYING (Active)PlayerStatus.ACTIVE

  • Player is dealt cards
  • Player can post blinds and act
  • Player participates normally

SITTING OUT → PlayerStatus.SITTING_OUT

  • Player retains seat
  • Player is not dealt in
  • Player is skipped for blinds and action
  • Removal counter is active (cash games)

REMOVED (not required — player is simply deleted from seat array)

  • Player is fully removed
  • Seat is released

2) Initial Join Behavior (Defaults) — DONE

Sub-issue: #50 (closed) | PRs: block52/poker-vm#1836 (merged), block52/poker-vm#1841 (merged)

Current Implementation

When a player joins via NonPlayerActionType.JOIN:

During ANTE (no hand in progress) → player enters PlayerStatus.ACTIVE immediately:

// pvm/ts/src/engine/texasHoldem.ts:516-531
joinAtSeat(player: Player, seat: number): void {
    this._playersMap.set(seat, player);
    if (this.isHandInProgress()) {
        player.updateStatus(PlayerStatus.SITTING_OUT);
    } else {
        player.updateStatus(PlayerStatus.ACTIVE);
    }
}

During a hand (PREFLOP+) → player enters PlayerStatus.SITTING_OUT, becomes ACTIVE on next hand.

Unit tests confirming this behavior:

  • texasHoldem-join-and-leave.test.ts:63"should set player to ACTIVE when joining during ANTE"
  • texasHoldem-join-and-leave.test.ts:73"should set player to SITTING_OUT when joining during PREFLOP"
  • texasHoldem-join-and-leave.test.ts:100"should activate SITTING_OUT players when new hand starts"
  • texasHoldem-join-and-leave.test.ts:136"should not deal cards to SITTING_OUT players"
  • joinAction.test.ts:46"should successfully join a player with valid amount and seat"

What the Document Specifies (Not Yet Implemented)

The document says when a player joins:

  • They should enter PlayerStatus.SEATED (not ACTIVE or SITTING_OUT)
  • A Sit In action should be available (NonPlayerActionType.SIT_IN)
  • Sit-in options are shown with defaults:

Sit In options:

  • Sit in on Next Big Blind (default pre-selected)
  • Sit in on Next Available Hand and Post Required Blinds

Gap Analysis

Behavior Document Says Current Code Does
Status on join SEATED (placeholder) ACTIVE (during ANTE) or SITTING_OUT (mid-hand)
Sit-in required? Yes — explicit opt-in via sit-in options No — auto-activates on join
Sit-in options 2 options (Next BB / Post Now) Single SIT_IN action, no method selection
SIT_IN action Changes SITTING_OUTACTIVE Same, but no blind-posting or BB-wait logic

Current SitInAction (no options yet)

// pvm/ts/src/engine/actions/sitInAction.ts:6-44
class SitInAction extends BaseAction implements IAction {
    verify(player: Player): Range {
        if (player.status !== PlayerStatus.SITTING_OUT) {
            throw new Error("Sit in action is not allowed if player is not sitting out.");
        }
        return { minAmount: 0n, maxAmount: 0n };
    }

    execute(player: Player, index: number, _amount: bigint): void {
        this.verify(player);
        player.updateStatus(PlayerStatus.ACTIVE);
        this.game.addAction({ playerId: player.address, action: NonPlayerActionType.SIT_IN, index }, round);
    }
}

Note: Current SitInAction only works from SITTING_OUTACTIVE. The document requires a new flow: SEATEDSITTING_IN (pending) → ACTIVE (when eligible).


3) Sit In Options — DONE

Sub-issue: #38 (open) | PVM PRs: block52/poker-vm#1836 (merged), block52/poker-vm#1841 (merged) | UI PR: block52/poker-vm#1843

Implementation Summary

PVM side (merged):

  • SitInAction now accepts method=next-bb or method=post-now via action data
  • sitInMethod is persisted on the player object
  • Chip validation prevents zero-chip sit-in

UI side (PR block52/poker-vm#1843):

  • PlayerActionButtons rewritten with three states:
    • Options panel — two radio buttons ("Next Big Blind" / "Post and Play Now") + CONFIRM button
    • Pending state — pulsing waiting indicator + CANCEL button (calls sit-out)
    • Sit Out button — unchanged existing behavior
  • sitIn() hook passes method as 4th data arg to performAction()
  • handleSitIn accepts method parameter

What Remains for Full Document Compliance

Feature Document Says Current Status
Sit-in options UI 2 methods (Next BB / Post Now) Done (PR block52/poker-vm#1843)
Default "Next Big Blind" pre-selected Done
Blind posting on sit-in Post missed blinds Not implemented — needs blind tracking
Dynamic UI labels "Post Big Blind" / "Post Small Blind and Big Blind" Not implemented — PVM doesn't expose missed blinds data yet
Zero-chip sit-in blocked Section 5 Done (PR block52/poker-vm#1841 — chip validation)

4) Entry Restrictions for "Next Available Hand" (Silent Backend Enforcement)

Critical UX Rule

Entry restrictions are enforced silently in the backend only.
The player is never prevented from selecting the option.

Backend Eligibility Checks (Evaluated at Hand Start)

When processing a pending "Next Available Hand..." sit-in, the system must verify:

The player must NOT be brought in if they would be:

  • Dealer Button
  • Small Blind

Exception: Post-Flop Last-to-Act Protection

Even if the above passes, the player must not be brought in if:

  • Due to dead or empty seats behind them,
  • Their entry would make them last to act post-flop

This includes scenarios with:

  • Dead dealer button
  • Multiple seats sitting out or leaving simultaneously

If Entry Is Not Allowed

  • Entry is skipped for that hand only
  • Player remains in PlayerStatus.SITTING_IN
  • No UI message or error is shown
  • Eligibility is re-evaluated automatically on the next hand

If Entry Is Allowed

  • Required blind(s) are posted
  • Player is dealt cards
  • Player transitions to PlayerStatus.ACTIVE

5) Insufficient Chips to Post Blinds

If a player attempts to sit in but does not have enough chips to post the required blinds:

  • Sit-in is blocked
  • Show message: "Insufficient chips to post blinds. Please buy in."
  • Provide Buy-In / Add Chips action (see block52/poker-vm#774)
  • Player remains in their current non-playing state

All-in posting for owed blinds is NOT allowed.


6) Sit Out Options (While Playing ACTIVE)

While PlayerStatus.ACTIVE, the player may click Sit Out (NonPlayerActionType.SIT_OUT), revealing:

  • Sit Out Next Hand
  • Sit Out Next Big Blind

Sit Out Next Hand

  • Clicked during a hand → applies after the hand completes
  • Clicked between hands → applies immediately to next deal

Sit Out Next Big Blind

  • Player continues playing until they would be BB
  • On that hand:
    • Player does not post BB
    • Player becomes PlayerStatus.SITTING_OUT
    • Player is not dealt in

7) Sitting Out Removal Counter (Cash Games)

Removal Rule

  • Player is removed once the dealer button has passed their seat 3 times while sitting out
  • This may result in slightly more than 3 full orbits depending on timing

Counter Behavior (Anti-Gaming)

  • Counter increments only while in PlayerStatus.SITTING_OUT
  • When player clicks Sit In → counter is paused
  • If player cancels sit-in and sits out again → counter resumes from previous value
  • When player successfully becomes PlayerStatus.ACTIVE → counter resets to 0

8) Leave Table & All-In Lock (Critical)

All-In Lock Rule (Non-Negotiable)

If a player is PlayerStatus.ALL_IN in the current hand, they are LOCKED into the hand and cannot forfeit it by clicking "Leave Table".

This prevents accidental forfeits, disputes, and integrity violations.

Leave Table Behavior by State

A) Player clicks Leave Table while NOT in a hand

  • Player is removed immediately
  • Seat is released

B) Player clicks Leave Table while IN a hand and NOT all-in

  • Server immediately processes FOLD if they are required to act and a CHECK is not an option (i.e., if they are facing a bet)
  • No client timeout is waited for
  • Player is removed from the hand
  • Chips already committed remain committed
  • Seat is released
  • See: pvm/ts/src/engine/actions/forfeitAndLeaveAction.ts

C) Player clicks Leave Table while IN a hand and ALREADY ALL_IN

ALL-IN LOCK APPLIES

System behavior:

  • Player is NOT folded
  • Player is NOT removed from the hand
  • Hand runs to completion normally
  • Player remains eligible for all pots they are entitled to

After the hand completes:

  • Player is automatically removed from the table
  • Seat is released

Recommended UI behavior:

  • Allow click but show tooltip: "You are all-in. You will leave the table after this hand completes."

Disconnect Edge Case

  • If an all-in player disconnects or leaves client-side:
    • All-in lock still applies
    • Server must keep them in the hand until completion

9) Graceful Exit (Sit Out → Leave Table)

  • Player may Sit Out first
  • Finish obligations cleanly
  • Click Leave Table while sitting out
  • Exit without disrupting live action

Definition of Done

  • Player states are deterministic and unambiguous (#1831 ✅)
  • No blind evasion possible (needs Section 4 + 6)
  • No accidental all-in forfeits possible (needs Section 8)
  • Sit-in eligibility enforced server-side only (needs Section 4)
  • Leave Table behavior matches live poker expectations (needs Section 8 + 9)
  • Removal counter cannot be gamed (needs Section 7)

Pinned Dev Rule

Seat ≠ Playing. All-in ≠ Foldable. Eligibility is enforced silently.


Sub-Issues & PRs

Section Issue PR Status
1) Player States block52/poker-vm#1831 block52/poker-vm#1834 Merged
2) Initial Join #50 block52/poker-vm#1836, block52/poker-vm#1841 Merged
3) Sit In Options (PVM) #38 block52/poker-vm#1836, block52/poker-vm#1841 Merged
3) Sit In Options (UI) #38 block52/poker-vm#1843 🔄 In Review
4) Entry Restrictions TBD ⬜ Not started
5) Insufficient Chips TBD ⬜ Not started (chip validation done in block52/poker-vm#1841, UI message pending)
6) Sit Out Options TBD ⬜ Not started
7) Removal Counter TBD ⬜ Not started
8) Leave Table & All-In Lock TBD ⬜ Not started
9) Graceful Exit TBD ⬜ Not started

Related Issues

  • block52/poker-vm#1086 - Sit-in options after missing blinds (post now or wait for BB)
  • block52/poker-vm#1793 - Dead small blind and dead button rules
  • block52/poker-vm#1548 - Sitting-out "Leave table" event corruption bug
  • block52/poker-vm#1192 - Auto-sit-out on timeout
  • block52/poker-vm#1343 - Player action timer
  • block52/poker-vm#774 - BUY CHIPS top-up (ties into insufficient chips flow)

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    UIUI related issuesepicLarge multi-issue initiativefeatureFeature enhancement groupgame-flowGame flow, auto-actions, and lobby behaviorgame_logicGame logic related issuesmvpCritical for minimum viable productsdkSDK related issuesseatingAnything to do with seating

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions