Skip to content

2026-07-14 — Busted Player Disposition shipped

A cash player who busts (0 chips at hand end) now gets a clean, uniform disposition — a rebuy window, then rebuy-and-play-on or seat-freed — instead of silently wedging (or worse) the table. Fixes the 8I21MZ class found during the agent-decision-inspection smoke, ahead of agent-table go-live.

See the current-state write-up in table-lifecycle.

The bug this closes

The poker engine NULLs a busted seat at showdown. Every downstream consumer tripped over that null:

  • The engine→PM stack sync skipped null seats, so the boundary checkpoint persisted the busted seat's stale pre-hand stack (observed: a seat that busted to 0 read 13).
  • That stale balance made table_players sum diverge from the on-chain pool (e.g. 293 vs 200) — a conservation divergence that tripped the custody invariant into a freeze → auto-escape of the whole table on any heads-up bust (the winner lost their seat too).
  • The stale balance also defeated the bank's chipAmount <= 0 forced-withdraw guard — a system-initiated leave would have paid out chips the player no longer held.

What shipped

  • True-0 at the boundary. Busts are detected from the committed hand-end payload (endStack === 0), never the nulled engine seats. The busted seat's PM balance is zeroed before the checkpoint reads it, so table_players stays settlement-safe and the spurious freeze→escape is gone (delta = 0).
  • Rebuy window. seated → sitting_out at the boundary, then a per-table bustRebuyWindowSeconds (zod 5–600, default 60) window; a bust_rebuy_window broadcast drives a live countdown on the seat owner's <RebuyAffordance>.
  • Two outcomes. A credited deposit inside the window re-seats the player (sitting_out → seated, via an explicit credit-chips branch — a busted player can't ride the self-heal reserve path since sitting_out → reserved is an invalid transition). No rebuy → a local 0-chip stand-up (no bank leg — the bank 400s nothing_to_withdraw by design), the row reaches gone, the seat frees.
  • Restart-safe. Reconstruction re-arms each open window from last_checkpoint_at + bustRebuyWindowSeconds; a past deadline disposes immediately. A reconstruction-parity fix: pm.standUp now tolerates an unallocated 0-chip ledger seat (a reconstructed busted seat isn't ledger-allocated).
  • Agents ride the identical path (no isAgent branch).
  • Telemetry: poker.bust_disposition span per bust with disposition.outcome ∈ {sat_out, rebuy, stood_up, rearmed_at_boot}.

Verified

13 integration tests on the test profile (true-0, sitting-out, rebuy window, rebuy re-seats, expiry frees the seat, no money on expiry, heads-up never wedges, winner unaffected, restart mid-window resolves, forced-withdraw pays 0)

  • RTL countdown coverage. The live agent-bust seat cleanup (T8) is deferred to the hosted-agent dogfood (the disposition is agent-agnostic). The stranded dev artifacts 8I21MZ / 334015 / 334017 are concluded.

The full decision record is at .indusk/planning/busted-player-disposition/adr.md (published to the decisions section at plan close).