Phase C, the engine half: hold'em becomes multiway, and the redaction that was a
bug-in-one-handler becomes the security boundary the plan warned it would.
- const You is gone. A table is a list of seats and which are human is a per-seat
property, not the fixed index zero. New(tier, []SeatConfig, ...) seats the ring;
SoloSeats builds the old one-human-plus-bots shape the solo handler still opens.
- ApplyMove(state, seat, move) — seat identity enters the engine in exactly one
place; every helper below already worked on indices. The advance loop stops at
any human (not just seat 0), so one request plays the bots and hands control
back at whichever person is next to act.
- deal() now emits every seat's hole cards. The engine cannot redact a stream it
doesn't know the audience of, so it stops trying: the view layer builds each
viewer's redacted copy. viewHoldem/viewHoldemEvents take a viewerSeat.
- Rake attributed to Paid whenever a *human* wins, not just seat 0 — real house
income is rake off any player's pot, and bot pots are house-vs-house.
- Bust is per-seat: at a solo table it still ends the session (PhaseDone), at a
shared one a busted human just goes Out and the table plays on.
Tests, three ways, all green:
- the solo suite unchanged as a regression guard (a test-local You=0 alias);
- TestMultiwayChipsAreConserved — 100 games, two humans at seats 0 and 2, chips
counted after every move, proving the reshape actually plays;
- TestHoldemViewNeverLeaksAnotherSeatsCards — renders every seat's view and event
stream at every street and greps for anyone else's cards. Mutation-tested: undo
the redaction and it fails on the preflop deal.
No handlers rewired yet — the solo path still calls New(SoloSeats(...)) and renders
for seat 0, so nothing a player sees has changed. The table cutover is next.
Claude-Session: https://claude.ai/code/session_013M5nD7PgUboJXoDcYHzpuJ