There is no consensus chart to be wrong against
The published preflop sources disagree with each other by 23 points on the most drilled defence in 6-max. That spread, not our gap to any one of them, was the number that mattered.
Two weeks ago I wrote about fitting a 27 GiB poker solve into 8.6 GiB on a Mac Mini. That post was about memory. This one is about what we did with the solver once it fit, which was to try to make our own preflop charts for Hash.Poker’s Train mode instead of licensing them. We expected to spend twelve days closing the gap between our charts and the industry-standard solver’s. We spent most of them finding out that the gap was not the thing to close.
One spot, four answers
Take the most drilled decision in 6-max: the big blind facing a button open, 100bb deep, 2.5bb open. How much of its range does the big blind continue with (call or 3-bet)?
| Source | Continue | What it is |
|---|---|---|
| Product A | 55.3% | The industry-standard commercial preflop solver, run on our own preflop tree, rakeless, with its own bucketed postflop |
| Product B | 44.0% | A published chart library, raked game, same open size, its own 3-bet size and postflop tree |
| Research bot | 55.2-67.1% | 95% interval over 256 observed decisions by a published poker AI |
| Ours | 39.3% | Our preflop tree, rakeless, postflop from 500 flops solved without abstraction (call 23.8, 3-bet 15.6) |
The three outside figures span 23 points. We’re 4.7 points from the nearest one, Product B. That is about a fifth of the disagreement already between them.
Two caveats count against us, so here they are up front. Product B’s chart is raked, and rake should make a defence tighter, not looser. Ours is rakeless and still tighter. And against Product B the comparison is close rather than exact, because its 3-bet size and postflop tree aren’t fully published. Against Product A the preflop tree is identical by construction, so only the postflop differs.
So “how close is our chart to the reference?” turned out to be the wrong question. Any audit that treats one source as truth is measuring agreement with that source.
How our charts are made
Solving postflop inside a preflop solve is too big for one machine, so we don’t. We solve sampled flops in full with our own engine: every turn and river card, no card abstraction. From those we fit a realization model, a per-hand-class correction to raw equity that says how much of its equity a hand actually wins once the betting starts. The preflop solve (CFR) then plays against that model.
The idea isn’t ours. Replacing the rest of the game with an estimate of its value is how the strongest no-limit agents got small. DeepStack learned a value function for the next street with neural networks [1], and depth-limited solving built a master-level heads-up agent on a 4-core CPU with 16 GB of memory [2]. Ours is the plainest member of that family: no network, a fitted correction per hand class, line and role, trained on full postflop solves. Underneath, the postflop engine runs discounted CFR [3], with CFR+ [4] as the comparison, and re-solves the river by decomposition [5], as the first post describes.
The gap I set out to close was this one. Our big blind folded about 60% against a button open, and Product A folded 44.7%. Below is how we ruled out each likely cause, in roughly the order it cost us time.
Ruling ourselves out
Not the fit. We tried three forms of the realization model: a share of the pot, the same share held to a band of 0.5x to 1.5x, and an expected-value form that can value a hand at more than the pot (aces need that). Across the three, the fit’s own error moved tenfold. The charts’ per-class mix error against Product A (the share of each hand’s frequencies that differs) stayed within 0.02 across all three. The fit was never what limited us.
Not the sample, once the sample was big enough. A hand class’s value on one flop runs from nothing to nearly two pots. At 4 and 13 boards the fold width swung by 15 points, and for a while we read those swings as signal. With 50 or more boards it settled: across runs from 50 to 500 boards, and two different input ranges, the fold width stayed between 58.0 and 60.7. Under about 50 boards a line tells you nothing about a single hand class.
Not the input ranges. The flops are solved at some assumed preflop ranges, and the preflop solve returns new ones, so in principle you iterate to a fixed point. We did. The fold width stayed put while the big blind’s input range went from 591 combos to 466, and the solve returned 340 to 346 combos from both. We repeated the check on a second chart (big blind against the cutoff, 60 boards): the big blind’s values moved by 0.006 pots on average, and 8 of 169 hand classes changed their call decision. For this problem the fixed point is close to free, and we stopped paying for it per chart.
Not the tree. Our full Train tree (geometric sizes on every street, unlimited raises, all-in only at low stack-to-pot) moved per-class values by 0.002 pots against a small tree, at ten times the cost per board (1,850 s against 184 s).
Not our implementation. We ran TexasSolver, the open-source solver from the first post, which shares no code with ours, on the same boards, ranges and tree. This covers two boards. (A third hit the other solver’s memory ceiling after 12 seconds, which is a limit, not a disagreement.) On the big blind’s medium offsuit hands the two agree to a few thousandths of a pot, in pots on those boards: K7o 0.040 against our 0.037, 65o 0.038 against 0.037, 87o 0.167 against 0.168, T8o 0.196 against 0.197, J8o 0.198 against 0.199. On the board without a pair, over the whole range, the other solver sits 0.004 pots above us on average, RMS 0.016 (one board; the per-class spread across both is 0.003 to 0.016). On the board with three nines, a board full of ties, trips and quads differed by up to 0.1 pots while the medium hands still agreed to 0.003.
What’s left: abstraction
Product A doesn’t publish per-board values, so the comparison with it has to be at the class level. To call, the big blind puts 1.5bb into a pot of 5.5, so a hand needs a 0.273 share of it. Product A’s node implies those medium offsuit hands are worth 0.27 to 0.29, just over the price, so it calls them. Averaged over our 500 flops, our solver puts them about 0.1 pots lower, and folds them. (The cross-check values above are on two particular boards, so they don’t compare directly with an average over all flops.)
Every source above ours whose method is published rests on bucketed postflop abstraction, where hands are grouped and played as a group. A coarse bucket puts a medium hand together with better ones and so flatters it, which helps the weaker range, here the caller. That’s the direction we see. It isn’t news that abstraction can mislead a solver: a 2009 paper showed, in a poker game small enough to solve exactly, that refining an abstraction can even produce a weaker strategy [6]. What we haven’t seen published is the size of the effect on a chart players actually drill.
I don’t claim this makes us right. Two unabstracted solvers agreeing is one kind of witness, not a proof, and three sources sit above us. What I can say precisely: against Product A, where the preflop tree is identical, the whole gap comes from the postflop side. Ours sits at the tight end of the spread. And the two solvers that model postflop without buckets agree.
A preflop chart is a claim about a postflop model, not a fact about poker. When charts disagree about K7o, the disagreement is usually inherited from how each one approximated the flop, turn and river.
Why it can’t be settled on our own data
I wanted a referee, and there isn’t one. I’m writing down why so the next person with the same idea finds the answer.
Logged hands can’t settle it. The disagreement is worth about 2 bb per 100 hands at this node. The per-hand result in these pots has a standard deviation near 12 bb, so resolving 0.02 bb a hand at two standard errors takes about (2 x 12 / 0.02)² = 1.4 million hands per side at the node. Our beta has about 1,700 hands in total, and about five of them are K7o defending a button open. Variance reduction helps but doesn’t close that gap. AIVAT corrects each hand’s result using the known strategy of one of the players [7], and a 2026 paper building on it reports a median 54x reduction in variance over 71,439 heads-up hands [8] (the 74x in its title is a different measure, the cost of a whole evaluation). Even 54x fewer is about 27,000 hands at this node, against our five.
A simulated referee can’t either, because every postflop policy takes a side. The tempting design is to play both preflop charts against one “neutral” postflop policy for millions of hands and count chips. But the disputed quantity is the postflop value of the caller’s medium hands, and the two charts differ exactly in what they believe it is. A passive referee (check down, call by pot odds) lets a called junk hand realise its equity and hands the win to the looser chart. An aggressive referee punishes the caller and hands it to the tighter chart. There is no policy that belongs to neither side. The only referee-free judge is best response on both sides, and that is a postflop solve, which is what we already have twice. Cost was never the objection: about a day to build and minutes of laptop time to run.
What would settle it: a third unabstracted solver with a different tree, a hand-history corpus two orders of magnitude bigger than ours, or the abstraction’s accuracy measured against a full-range solve by whoever built it.
The gate nobody could have passed
This is the discovery that cost us the most days.
Our first gate compared all 18 charts our solver produces (Train ships six today) with Product A’s: each action’s width within 3 points, and a per-class mix error of at most 0.10. The early pass-or-fail rested on the three blind-vs-blind charts, because once the action folds to the small blind what remains is a small piece of the full game that can be solved exactly. After a four-day pilot (75 postflop solves on the Mini) the fitted model passed 1 of the 18, the same as raw equity, the baseline it was supposed to beat.
The model wasn’t the problem. Those three blind-vs-blind charts are the only three in the set where Product A itself is close to indifferent. 69.5%, 82.5% and 57.9% of those ranges have Product A’s top two actions within 0.02 bb of each other. The median gap between the best and second-best action is 0.003 to 0.013 bb there, against 0.07 to 0.47 bb on every other chart. Asking our model to match Product A’s chosen action there was asking it to reproduce a coin flip. At our precision, no model could have passed that gate. Before grading a chart against a solver’s actions, measure how far apart the solver’s own top two actions are. Where they’re within noise, grade on EV: you can’t pass or fail a coin flip.
Where the benchmark is measurable, the same model behaved. It took the button’s opening width from 16 points away from Product A’s to 1.0, and brought the cutoff, hijack and under-the-gun opens to 2.5, 3.4 and 4.2.
So we switched to EV. We price our chart’s frequencies at Product A’s own per-class values: how much a player loses, in bb per 100 hands reaching the node, by playing our chart in Product A’s game. That convention takes Product A as truth, so it’s our real cost if Product A is right, and an overstatement if its abstraction is what’s off. For scale, Product A’s own second run priced against its first costs 0.01 bb/100 (0.30 points of width). Raw equity costs 9.4. The hand-authored chart Train ships today costs 3.7. Ours costs 2.0 to 3.2 across every sample size and range state we tried. So ours beats today’s chart by a little, while still folding K7o, 65o, 87o, T8o and J8o, all of which Product A calls.
Precision: we bought the wrong kind
The first post’s quality bar was a gap under 1% of the pot per board. For a realization model that turned out to be backwards.
What the model needs is each hand class’s mean value across boards, and that error is dominated by which boards you sampled (a spread of about 0.45 pots per class for the swingy ones), not by how converged each board is. We had been solving 13 to 25 boards to a 1% gap each when the answer needed 500 to 1,000 boards at about 10%.
So we measured how far a cheap solve drifts from an expensive one, on the same 13 boards. Against 300 iterations, 30 iterations left a range-wide error of 0.010 pots, and the big blind’s call decision flipped on 3.0% of its range. At 50 iterations it was 0.004 pots and 1.1%. The drift is a transient that shrinks with iterations, not a floor of the method. A board at 50 iterations takes 187 seconds against 1,115 at 300, six times cheaper, and lands at a 3-6% gap per board. We accept that on purpose. A per-board gap of 15% or more still gets flagged.
This is the change that let us solve 500 boards for one chart in about a day of machine time instead of a week. For a model fitted from sampled flops, buy boards, not per-board precision, and check how far a cheap solve drifts from an expensive one before paying for convergence.
A curve I should not have published internally
Not everything held up. At 200 boards I fitted the EV cost against board count to 1.6 + 18.8 / sqrt(boards) bb/100, flagged it as plus or minus 0.5, and used it to plan. It predicted about 2.4 at 500 boards. The 500-board run came in at 3.21, up from 2.92 at 200.
The curve had been fitted to three points that changed two things at once: the board count, and for one of them the input ranges. At 200 boards the cost was 2.0 at the iterated ranges (466 combos for the big blind) and 2.92 at the earlier ones (591). A drop I credited to more boards was partly the range change. Past 200 boards it was wrong even about the direction. The honest statement is flatter: on this chart the cost sits at 2 to 3.2 bb/100 from 120 to 500 boards, and more boards don’t lower it. The floor is the model’s, not the sample’s. Don’t fit a curve to points that change two things at once.
One more trap we fell into on the way: a falling cost can hide a model that still gets the same hands wrong. From 13 to 200 boards (while the ranges were also iterated and the 3-bet pot got its own data) the cost fell from 6.5 to 2.0, but it fell because the mistakes got cheaper, not fewer.
Smaller things that took too long
- Our first 75 solves were made at the wrong ranges. We seeded the pilot from our own raw-equity preflop solve. In that game the big blind calls 91% against a button that opens 27%, which doesn’t look like poker. Every fit from those solves inherited it.
- A folded blind is dead money. Our flop-spot generator refused big-blind-vs-opener lines because a guard demanded no dead money in the pot, which is only true blind against blind. The small blind’s folded 0.5bb is in every one of those pots.
- The monotone question from the first post is closed on our side. Our two hand evaluators agree on all 1,271,256 seven-card hands from Qh8h4h and on 300,000 heart-heavy random hands, both order every flush exactly as a brute-force oracle does, and monotone solves match our exact reference to about 1e-13 chips. Our first attempt at this check printed FAIL without ever running the test (cargo was invoked from the wrong directory). A failed check can look like a clean result. The 15.3% and 10.7% Qh8h4h gap against TexasSolver from the first post is still open: our side is clean, so it is now a question about the other solver or the tree.
Three process lessons, each paid for in Mini days
These aren’t poker, but they’ll save someone a week.
- Grade one item before the batch. The scoring tool for the pilot already existed and ran in about two seconds. Nobody ran it until the four-day batch ended. The problem was visible in the fit’s output from the first spot: multipliers on hands with no data ran to 688 for aces and 2,368 for sixes, against a sane range of 0.5 to 1.5. One solve and one score, about three hours, would have shown it.
- A fallback must test itself when you arm it. We armed a recovery step to re-solve any board that missed its bar at higher precision. It fired correctly, then found the branch it needed had never been pushed, released its hold, and let the batch run three more days as if it didn’t exist. Everything it needed could have been checked in one command when we armed it.
- A long run must refuse to start twice, and a reported guard isn’t a guard. Our coverage run was started twice, 17 minutes apart. Two processes wrote one checkpoint from two different models at half speed each, and the pid file recorded the duplicate instead of preventing it. A person noticed after 2 h 50 min, because the dashboard had sat on one step too long. The same run was reported to stop after its approved scope, and the script on disk had no such stop. The lock is in now, tested against a live process.
How we worked
Same as last time. Claude Code was my engineering partner: it wrote and ran the fits, the pricing and the cross-checks with me over SSH on the Mac Mini, and kept the ledger and the GitHub issues this post is drawn from. One M2 Mac Mini, 16 GiB, no rented machines. Every number here comes from a run log or an issue comment, and I’ve left the ones that went against us in.
Where it stood on 28 October
This is a snapshot at the halfway mark, not a result. A run that covers more of the postflop lines behind each chart was running then, about seven to eight Mini days in all, and three of its six groups were done. Summed over all 18 charts the EV cost has fallen from 13.27 to 9.64 bb per 100 hands dealt. On the six charts Train ships today it went from 4.31 to 2.64, against 2.46 for the current hand-authored set, so across those six the hand-authored set is still slightly ahead even though ours leads on the big blind against the button. I’ll write up the result when the run is finished.
References
- M. Moravčík, M. Schmid, N. Burch, V. Lisý, D. Morrill, N. Bard, T. Davis, K. Waugh, M. Johanson, M. Bowling. DeepStack: Expert-Level Artificial Intelligence in No-Limit Poker. 2017. https://arxiv.org/abs/1701.01724
- N. Brown, T. Sandholm, B. Amos. Depth-Limited Solving for Imperfect-Information Games. 2018. https://arxiv.org/abs/1805.08195
- N. Brown, T. Sandholm. Solving Imperfect-Information Games via Discounted Regret Minimization. 2018. https://arxiv.org/abs/1809.04040
- O. Tammelin. Solving Large Imperfect Information Games Using CFR+. 2014. https://arxiv.org/abs/1407.5042
- N. Burch, M. Johanson, M. Bowling. Solving Imperfect Information Games Using Decomposition. 2013. https://arxiv.org/abs/1303.4441
- K. Waugh, D. Schnizlein, M. Bowling, D. Szafron. Abstraction Pathologies in Extensive Games. AAMAS 2009. https://poker.cs.ualberta.ca/publications/AAMAS09-abstraction.pdf
- N. Burch, M. Schmid, M. Moravčík, M. Bowling. AIVAT: A New Variance Reduction Technique for Agent Evaluation in Imperfect Information Games. 2016. https://arxiv.org/abs/1612.06915
- B. Li, Y. Chen, L. Huang. AV-AIVAT: 74x Cheaper Agent Evaluation with Certified Anytime-Valid Stopping in Imperfect-Information Games. 2026. https://arxiv.org/abs/2608.06362
- TexasSolver: https://github.com/bupticybee/TexasSolver