Advertisement

← poker-tutor

docs/SPLIT-PLAN.md

20.2 KB Download file
# Tournament and cash: four apps from two codebases

Ken, 2026-09-11: "the poker apps are doing too much. Split them into tournament and cash versions
and make sure the advice for each is tailored to that style of play. For cash games, let the user
set the limits (size, limit or NL or PL) and give them 100 BB to play with. Tournament looks fine."
Decisions he made the same day: **separate apps, one codebase per game** (a build-time switch, so
every fix reaches all four); **all three betting structures at launch** for hold'em cash.

This file is identical in `~/poker-tutor` and `~/plo-tutor`; each repo builds its own two apps.

## The four products

| Product | Repo | Format | Structures | Bundle id | History folder |
|---|---|---|---|---|---|
| Poker Tutor | poker-tutor | tournament | no-limit | `com.thedigitaladlab.pokertutor` (unchanged) | `PokerTutor` (unchanged) |
| Poker Tutor Cash | poker-tutor | cash | fixed-limit, pot-limit, no-limit — the player picks | `com.thedigitaladlab.pokertutor.cash` | `PokerTutorCash` |
| PLO Tutor | plo-tutor | tournament | pot-limit | `com.thedigitaladlab.plotutor` (unchanged) | `PLOTutor` (unchanged) |
| PLO Tutor Cash | plo-tutor | cash | pot-limit only (PLO is pot-limit by definition) | `com.thedigitaladlab.plotutor.cash` | `PLOTutorCash` |

Each product has its own display name, icon mark (the cash apps carry a small chip-stack glyph on
the same suit), StoreKit product id (`…cash.yearly`), UserDefaults prefix, screenshots, web page
and listing. The existing two keep everything they already have; their "Cash game" button goes.

## The mechanism: `AppVariant`

`PokerSession.AppVariant` = `{ format: .tournament | .cash }` plus the identity strings above,
chosen ONCE by the app shell (`PokerTutorPhoneApp` / `PokerTutorCashPhoneApp`, two targets in the
same Xcode project; two Mac executables; Android = two Skip products) and handed to
`AppState(variant:)` → `TableSession(variant:)`. Nothing else reads a bundle id. Every
variant-specific behaviour keys on `variant.format`; every test that depends on it runs both.
`pokerlab` gains `--format cash|tournament`.

## Cash game rules (both games)

- **Setup screen** before sitting down: stakes and, for hold'em, the structure.
  Stakes are blind levels in the chips the app already has: 25/50, 50/100, 100/200, 500/1000.
  Structure (hold'em only): Fixed-limit / Pot-limit / No-limit. Remembered between sittings.
- **Buy-in 100 big blinds.** Stacks carry from hand to hand (that is what a cash game is). A
  player who busts, human or bot, rebuys 100 big blinds; rebuys are counted and shown. A bot that
  busts twice leaves and a new player takes the seat, so the table changes over an evening.
- **Blinds never move.** No antes. No "players left". The sitting ends when the player cashes
  out (Leave), and the result is shown as big blinds won or lost and per 100 hands.
- **Fixed-limit rules** (`BettingStructure.fixedLimit`): small bet = 1 BB before the flop and on
  it, big bet = 2 BB on the turn and river; a bet and three raises cap a round except heads-up;
  the big blind is a full small bet. The chip tray is not shown: Bet/Raise buttons carry the
  amount. `HandEngine` takes the structure in `HandConfig`; tournament apps pass their own
  (hold'em NL, PLO PL); PL hold'em uses the pot cap PLO already computes.

## The coaching, per format

**Tournament** (unchanged in substance): what exists today.

**Cash, no-limit and pot-limit**: the same evaluator with the short-stack branch off (stacks are
100 BB or deeper) and deep-stack judgement on: implied odds (calling to hit a set or a draw is
priced against what is left behind, not the pot alone); not stacking off 100 BB with one pair;
re-raised pots by stack-to-pot ratio; the value of position deeper. Cash habits replace the
tournament ones that do not apply (`committed-light`/`missed-commit` at 12 BB) with deep-stack
ones (`stacked-off-one-pair`, `set-mine-no-odds`, `missed-implied-odds`). Ladder = **stakes**:
the rungs are 25/50 → 500/1000 and moving up needs a measured win rate at the current stake over
the same 400-hand window the ladder already uses. Assessment: the short-stack setup is replaced
by a 200 BB "deep" one. Copy, glossary and lessons say "session", "buy-in", "cash out", "big
blinds per 100 hands", never "blinds climb", "players left", "the bubble".

**Cash, fixed-limit** (hold'em only — the largest piece, its own Phase like PLO was): bet sizes
are fixed, so the decisions are fold/call/raise and the price is nearly always right to draw.
Strategy: play more hands, raise for value with any advantage, three-bet for value not to
isolate, never cold-call a raise with a marginal hand, bet and raise draws for free cards, call
down when the pot is big, fold the river only with real reason. Habits: folding to a bet with a
draw the price pays for, not raising for value, cold-calling raises, calling down with nothing,
folding the river too much, missing the free-card raise, capping with a weak hand. Planted
players and sweeps derive every bar as always; a limit `HandReader` weighs outs against the fixed
price rather than against a bet size.

## Phases (each: tests, full suite, save to librarymo.de)

**Phase 0 and 1 done in poker-tutor, 2026-09-11** (`AppVariant`, two targets, `BettingStructure`
with pot-limit and fixed-limit suites, cash setup screen, carry-over stacks, rebuys, replacements,
cash-out result, fixed-limit action bar, `pokerlab --format cash`). The coach at a cash table runs
the tournament evaluator with the short-stack branch off; Phase 2 tailors it. Measured at
fixed-limit (`pokerlab shootout --structure fixedLimit --hands 6000`): the skill dial is positive
six-handed for every style (+0.7 to +5.8 bb/100), a solid regular beats a table of calling
stations by +18, and the ladder still slopes (+12 → −3). Hold'em tournament behaviour is unchanged
except one rules correction ported from PLO Tutor: a short big blind no longer lowers the first
raise for everyone else.

**Phase 2 done in poker-tutor, 2026-09-11 — the cash coach.** One process-wide switch,
`LeakCatalogue.format` (set with `AppVariant.use`, which every `TableSession` calls with its
variant and the suite resets before each suite), selects the edition of everything that differs:
the habit catalogue, the rating line, the assessment's arrangements, the ladder, the lessons, the
tutorial. `Coach.format` drives the evaluator. What the cash coach does that the tournament coach
does not: prices a call with a small pair or two suited connectors against the money behind
(twenty times the call for a pair, twenty-five for connectors — folding one the stacks pay for is
`missed-implied-odds`, calling a re-raise with one they do not is `set-mine-no-odds`); names a
new situation, *playing for a whole stack* (`SpotKind.deepStackOff`: the next chips are most of a
hundred-blind stack), prices every action that commits a stack against the strongest third of what
the opponent had, and calls one pair going in there `stacked-off-one-pair`; weighs position a
little more at 150 big blinds and deeper; never judges a short stack. The cash bots play the same
money-behind rule (`Bot.playsImpliedOdds`, on only at cash tables), so the tournament bots and
the tournament coach are untouched — `pokerlab coach --player control --hands 2000 --seed 4242`
reads 150 decisions, 13.2 bb/100, rating 60 before and after.

Bars: every shared habit was re-swept at the app's own nine-handed cash tables (`pokerlab sweep
--format cash --rung 0|3`, control 8 × 6,000 at the bottom rung and 4 × 6,000 at the top, each
planted player 6 × 3,000 against the control) and six moved — `fold-to-3bet`, `no-cbet`,
`fold-to-cbet`, `station-flop`, `river-hero-call`, `overbluffing` — with the bands beside them in
`LeakCatalogue.cashBars`. The stack-off habit was named at 30% over 12 chances (the flawed player
80-100%, the control in that spot one to six times in 6,000 hands — the fix round below made it
unnameable for exactly that reason); folding the hands the stacks pay for was set at 2% over 100
(control 0%, flawed 4-6% — re-swept and reset in the fix round); calling re-raises with small pairs stays
unnameable (0-3% against 0-0.1% over every re-raise faced — the two cannot be told apart by a
rate). A pot-limit control sweep (4 × 4,000) named nothing. The control is never named at either
end of the ladder over 12,000 hands (a test), and the cash rating line is
`87.2 − cost × 1.125 − penalty` (control 66 at 18.8 bb/100, the stack-off player 10 at 55.3; re-anchored
to `84.8 − cost × 1.242 − penalty` in the fix round below), with one
decision capped at 30 big blinds — before the cap a single river decision in a four-hundred-blind
pot was charged 384, and the stack-off player cost 1,605 bb/100 — and the stakes ladder's bars at
34/46/58 up and 14/26/38 down. The cash
assessment swaps the short-stack arrangement for a 200-blind one with measured priors. Words:
three cash lessons (`money-behind`, `one-pair-stack`, `re-raised-pots`) replace the short-stack
lesson; the tutorial's first page and one new page say what a cash table is; the ladder and the
cash setup panel talk in stakes and win rate.

**Phase 3 done in poker-tutor, 2026-09-12 — the fixed-limit coach.** `CoachFormat.limit` is a
third edition, chosen by `TableSession` whenever the cash setup's structure is fixed-limit (the
sitting, the assessment and the drills all apply it); `DecisionView.structure` lets the bots and
the coach ask the table how betting works. What is limit's own: `ReferenceRanges.Limit` (opens
16-50% by seat, three-bet for value from 7%, cold-call 6-25%, big blind 45-70%, cap 3-4%, call a
re-raise 9-14%); a preflop judgement that recommends the fixed raise, forgives a call behind
callers, and names `cappedLight`; `LimitEvaluator.swift` after the flop (candidates fold/call/the
one raise, fold rate 5-22%, called-equity discount a tenth of the no-limit one, the free card
priced at 0.45 of a big bet plus a twentieth of the pot, river calls with a pair never charged,
folds charged only with four outs or a share four points over the price); the range model narrows
less on a limit bet (72% and 85% instead of 55% and 70%); `SpotKind.freeCardChance`. Catalogue
`LeakCatalogue.limit`: the cash edition minus the bet-size and stack habits, four shared habits
replaced under the same situation and mistake (`limit-fold-to-price`, `limit-cold-call`,
`limit-call-down-nothing`, `limit-river-overfold`), four new (`limit-fold-turn-price`,
`limit-missed-value-raise`, `limit-no-free-card`, `limit-cap-light`). Bots at limit: ranges ×1.35
open / ×1.3 three-bet / ×1.4-1.8 call / ×2.4 versus a re-raise, call behind callers, value bets at
93%, top pair raises (threshold −0.20 for the sound styles), the free-card raise, four outs call at
five to one, any pair calls down the river, no-pair river calls get no price slack; shootouts at
6,000 hands: solid regular vs stations +41, loose passives +34, maniacs +49, nits +9, tight
aggressives +5; the ladder slopes +37 → +39 → +13 → 0 → −3; the six-handed dial is positive for
every style over three seeds (tight aggressive −0.5/+2.4/+1.8/+2.7 — the thinnest). Sweeps
(`pokerlab sweep --format cash --structure fixedLimit`, control 8 × 6,000 at rung 0 and 4 × 6,000
at rung 3, each planted player 6 × 3,000): every bar and band is in `Leaks.swift`; named 6/6 with
the control never named — draw-folder (68-84% vs 5-10%), capper (40-46% vs 0-2%), caller-down
(20-26% vs 1-4%), big-blind folder (54-60% vs 6-12%), limper (9-12% vs 0%), loose (37-43% vs
0-1%), river-checker (37-45% vs 14-29%); cold-caller 4-10% vs 0% (bar 3 over 60); river-folder
8-12% vs 1-2% (5 over 200, two of six named at the old bar); turn-checker 18-27% vs 9-17% (4/6 on
24 over 250, the thinnest named margin); unnameable with the bands recorded: `limit-missed-value-raise`
(15-19% vs 4-13%, touching) and `limit-no-free-card` (80% vs 29% over 8-27 chances — separable
but a floor of thirty is twelve thousand hands away). The control opens a review on 4.6-5.6% of
hands and is charged 8.2-8.5 bb/100. Rating line `77.0 − cost × 1.194 − penalty` (control 66, draw-folder
10; the stakes ladder's bars hold) — re-anchored to `76.7 − cost × 1.116 − penalty` in the fix round below. Assessment priors measured per setup (`Assessment.limitYields`).
Four limit lessons replace the three no-limit cash ones. Tournament: unchanged (150 / 13.2 / 60 on
seed 4242). No-limit cash: the control's sweep on seed 4242 is identical before and after.

0. **Variant scaffolding** — `AppVariant`, two targets per project, identity per product, Home
   per format, the cash setup screen, `--format` in pokerlab. Both repos.
1. **Cash engine** — `BettingStructure` in PokerCore (fixed/pot/no limit) with the rules above and
   a "betting rules" suite per structure; carry-over stacks, rebuys, seat changeover, cash-out
   result in `TableSession`; action bar for fixed-limit. Hold'em first (needs all three), PLO
   takes the shared parts.
2. **Cash coach (NL/PL)** — evaluator, catalogue, assessment setups, stakes ladder, copy/glossary/
   lessons; planted players; sweeps and calibration at 100 BB cash tables (both games).
3. **Fixed-limit coach** — hold'em cash only: strategy model, catalogue, planted players, sweeps,
   calibration, lessons.
4. **Store + web** — two new bundle ids, icons, listings, screenshots (the unattended script per
   product), web + privacy pages, Android products through Skip (the other session, briefed in
   `docs/FOR-ANDROID-SESSION.md`).
5. **Cold review** per product: rules, "anything untrue", jargon.

Scope note recorded for Ken: phase 3 is the size of the PLO strategy build; phases 0–2 together are
about the same again. The tournament apps are not changed by any of it beyond losing the cash
button.

## The cash review's fix round, 2026-09-12

A cold review of the cash and fixed-limit coaching found 33 faults (2 critical, 7 high, 11 medium,
13 low; 21 certain). Every certain one and every finding above "low" was fixed; the tournament
product is unchanged (`coach --player control --hands 2000 --seed 4242` = 150 / 13.2 / 60 before
and after). What changed, and the judgement calls:

- **The money-behind rule knows the seat and whose money it is.** It fired from every seat, so a
  fold of six-five three seats after the big blind against a first-seat raise was "a real mistake"
  at 97%. Now a pair calls with at most two players still to speak (or in position on the raiser),
  and two cards that fit together only in position with at most the blinds behind; the money
  behind is the raiser's (`DecisionView.effectiveStack(against:)` — the table-wide
  `effectiveStack` still takes the deepest opponent, on purpose: every tournament bar was swept on
  it). The bots play the same rule. The price and the gate now agree (a third of the raiser's money
  behind, never more than twelve calls' worth), and a preflop fold at a cash table is charged at
  most 6 big blinds — ace-king folded to a re-raise had been priced at 20, and a small pair folded
  to one seven hundred blinds deep at 24. Re-swept: control 0% (worst running rate 0% at every
  mark), the folding player 2% over ~1,000 chances → `missed-implied-odds` named at 1% over 200.
- **A stack-off or free-card decision that is some *other* mistake is filed under the flop or
  turn situation it always was**, so `fold-to-cbet` and `limit-fold-to-price` see it and its cost
  reaches the rating — folding a set to a flop shove had become free when the stack-off situation
  was carved out with one habit under it. Counting *every* uncatalogued cost was tried first and
  reverted: the scorecard that rebuilds the rating off saved hands counts habit-matched costs
  only, so the table and Home would have printed two ratings, and the control's cost rose from 15
  to 42 bb/100 on turn and river sizes the model cannot price to within a chip. The pairs that
  still fall through in every format are listed in `LeakProfile.record`; counting them is a
  re-anchoring of every scale, not a side effect of this round.
- **`stacked-off-one-pair` is unnameable.** No clean player ever reached its floor of twelve
  chances in 6,000 hands, so its bar was never measured against one; the file's own rule applies.
  Its cost counts in full. The "hands prepared to play for a stack" now include the draws that do
  (a flush draw to the queen or better, or a flush draw with a straight draw), so an overpair
  against a flop shove prices as the call it is rather than as one pair; counting every draw was
  tried and made stacking off with any top pair look right.
- **The confidence check builds its candidates exactly as the verdict did** (rounded to chips at a
  cash table, priced against the same stack-off range). Rounding the cash candidates without
  telling the check made "bet 175" and "bet 173" read as disagreement, and every cash bet's
  confidence collapsed — the stack-off habit went from 97% charged to 6% before this was found.
  Tournament candidates are priced unrounded as always and only the recommendation is rounded.
- **Fixed-limit:** the opening model's per-player fold chance is 0.62 at limit, not 0.84 (it was
  written the wrong way round); the free-card raise is credited a few tenths of a small bet, not a
  whole big bet (calling was charged 1.1 BB); the range model reads limit opponents off the limit
  charts; the cap lifts when only two players can still act; `limit-cap-light` is named for the cap
  it detects; `missed-turn-value` is unnameable at limit (its bands touch); `limit-fold-turn-price`
  moved to 6% over 150 (control worst 4%, flawed 9-12% after the free-card spot stopped swallowing
  draw folds). Re-anchored: `76.7 − cost × 1.116 − penalty` (control 9.6 bb/100 → 66, draw-folder
  46.3 → 10).
- **The ladder of stakes** keeps one record per stake AND structure (200 hands of fixed-limit and
  200 of no-limit at 50/100 were one run on one scale), reads hands on the scale of the game they
  were played at, and no longer breaks a run when a hand is played at another stake. Promotion
  stays on the rating, not the raw win rate (400 hands of win rate is noise); the card says
  "across at least 200 hands — the last 400 count".
- **A fixed-limit player reads the limit course from launch**, not from the first hand: the
  session's coach format follows the remembered structure the moment it is set.
- **Words:** the pot-odds lesson (one in six / 17%; one in three), the free-card worked example
  (raising costs the same 300 on price alone; the edge is elsewhere), the money-behind multiples
  (twenty and twenty-five, the raiser's money, in every lesson), the one-pair-stack example (a
  6,500 turn bet that actually commits), the limit river example (one in ten), the clock lesson and
  the three bet-sizing lessons out of the cash and limit courses, the fairness note per format, the
  tutorial without the chip page at fixed-limit, the rung line that promised a gate, the glossary's
  tournament/short-stack/cold-call entries, "about half the time" wherever 0.45 was "more often
  than not". A test now checks the arithmetic of every worked price in every course.
- **Re-anchored** (`calibrate --format cash --hands 3000 --seeds 4`): control 15.1 bb/100 (13.0-18.4) → 66; river 16.3 → 61; the folder of implied-odds
  hands 16.4 → 59; the set-miner 23.7 → 55; big blind 18.3 → 49; limper 20.0 → 48; flop-folder
  28.6 → 34; loose 30.2 → 32; the stack-off player 60.2 → 10. Line `84.8 − cost × 1.242 − penalty`;
  the stakes ladder's bars (34/46/58 up, 14/26/38 down) still spread the players and stay.
- **Not changed, on purpose:** `river-hero-call` stays nameable at cash (35 over 150; the control's
  running rate falls through 33% at 100 and its finishing rate is 20-29%); `set-mine-no-odds` stays
  unnameable (1% vs 0%); the stack-off gates still only reach the flop against a shove or a large
  check-raise — the three-street stack-off is out of reach by construction and the lesson says so;
  the 30 BB cap on one decision stands.