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