Today the Trainer lets you practice one seat per hand — you're the Button, everyone else is a bot. This PR teaches the backend a second mode where you play every seat that faces a real decision. One hand, up to six perspectives.
Think of a chess coach who makes you play both sides of the board so you stop seeing the game from just one chair. Same idea here: instead of grinding "Button vs Big Blind" a hundred times, you sit down and the hand comes to you — whichever seat has a decision worth making, you make it.
The engine lets bot seats act automatically (using solver frequencies) until a seat reaches a decision that's actually interesting to train.
Before handing you a seat, the backend peeks at what the solver would do there. If that seat would just fold, it folds quietly and moves on — you never get interrupted for a trivial fold. If the seat would do something real (call, raise), it becomes yours.
The seat is dealt a hand consistent with the action it's about to face, its cards flip face-up for you, and it stays yours for the rest of the hand. You can end up controlling several seats at once.
All your moves — across all your seats — get the usual correctness grading and show up in stats and hand history, per seat.
A single dealt hand now generates several graded decisions instead of one. Higher density = faster improvement and longer sessions.
It reuses the existing trainer engine, solutions, and grading. No new solver work, no new infrastructure — it's a smarter loop around what we already have.
Off by default. It's gated by a feature flag per user, and old clients that never send the new setting keep today's behavior exactly.
The mode requires "full hand" training scope — it deliberately doesn't combine with street/spot drills in v1. Smaller surface, fewer edge cases.
| Decision | Plain-language meaning | Risk level |
|---|---|---|
| Old fields kept alive | The app's old "hero hand" field still works so existing app versions don't break. We carry a little duplicate data for a while. | Low — cleanup later |
| Short-lived cache not versioned | In-progress hands live in a 1-hour cache. We didn't build migration logic for old cached hands — they simply expire within the hour of deploy. | Low — self-heals in 1h |
| Rare recovery loses one detail | If that cache expires mid-hand (rare), the hand is rebuilt from permanent storage. One edge case: a hand where you hadn't taken over any seat yet quietly resumes as a classic single-seat hand. Logged and tracked; a lossless fix is scoped if it ever matters. | Low — rare + graceful |
| Setting can be "unset" | Users who never touched the setting have it empty, which means "classic mode". Matches how our other trainer settings behave. | None — by design |
This PR — approved, all checks green, mergeable. A live per-PR preview environment is being provisioned for hands-on testing.
Companion PR #6686: a "Play Mode" toggle in Trainer settings (Single position / All positions), taken-over seats shown face-up. CI green, awaiting review.
Merge both → deploy to dev → R17 load check → enable the flag for internal users → decide the rollout cohort.