Phase C's handler cutover: hold'em now runs on the shared-table runtime instead
of the solo game_live_hands blob. Solo is just a table nobody else has joined.
- holdem implements tableGame (name/timeout/stacks). timeout auto-checks-or-folds
a walked-away seat and marks it away; the audit is per-hand, with each pot's
rake on the winner's row alone so HouseTake cannot 4x itself.
- New endpoints: sit opens a table (or joins an open bot seat), leave gets you up
(LeaveTable + CloseTable behind the last human), plus tables (lobby), stream
(SSE), chat and say. The move path loads the player's table, applies at their
seat, commits under the version guard, and fans an SSE nudge.
- Engine grows Vacate/Occupy (a human leaving/joining between hands) and
TableSeats (a named human + bots). The view carries your_seat, since a shared
table has no seat-zero-is-you convention.
- Storage grows OpenSoloTable (stake+claim+create+seat in one tx), PlayerSeat,
and the abandoned-table reaper (AbandonedTables/ReapTable) — the seated-player
counterpart to the session reaper, since a walked-away stack is inside a blob
the session reaper cannot see. upsertSeat preserves last_seen so an auto-fold
never refreshes an away player's clock.
Not deployed, and the felt is not rewired yet: the frontend still assumes
seat zero is you, so this is browser-unverified. Solo sit/deal/play/leave and
two-human join/leave/reaper are covered by tests; the whole suite is green.
Claude-Session: https://claude.ai/code/session_013M5nD7PgUboJXoDcYHzpuJ
Phase B runtime: the turn clock, the session reaper the plan noticed nobody had
ever wired, and the game-agnostic seam the engines will plug into.
- tableGame interface + a games() registry keyed on storage name, so the clock,
the reaper and (soon) the handlers never know whether they drive poker or UNO.
- The turn clock is the first goroutine in Pete to mutate game state. It obeys
rule 1 (DueTables returns a plain slice — the rows are closed before any lock,
or the scan would hold the one connection a locked write needs) and the version
guard (act only if the table is still the version the scan saw). Tested against
the exact double-move the plan warned of: a real move lands in the same tick the
scan fired, bumps the version, and the clock steps aside instead of folding the
next player who still had 25 seconds.
- PushDeadlines on boot shoves every live clock out by a grace period, so the
first tick after a deploy doesn't auto-fold the whole room at once.
- ReapIdleSessions finally has a caller. A seated player is invisible to it —
their chips are inside a table blob — so it only ever reaps loose idle chips.
- publishTable fans a minimal version-carrying nudge through the hub; the frame
is seat-blind, so a hole card never rides a broadcast that reaches the table.
Clock wired into main.go behind gamesReady(). Still no engine implements
tableGame, so the registry is empty and nothing a player can see has changed.
Claude-Session: https://claude.ai/code/session_013M5nD7PgUboJXoDcYHzpuJ