Results
Three layers are measured: the engine against PokerKit, a reference implementation of the rules; the CFR code against OpenSpiel on games with a known solution; and the heads-up blueprint in duplicate matches against rule-based agents.
Table 1Rule verification against PokerKit
| Check | Mismatches | Cases | What is compared |
| Decisions | 0 | 914,873 | Legal actions, stacks and bets before every decision |
| Pots | 0 | 164,897 | Final pot sizes and payouts |
| Showdowns | 0 | 94,922 | Winner and hand ranking |
| Side pots | 0 | 46,798 | Pots created by all-in players |
| Short all-in raises | 0 | 18,888 | Raises below the minimum and the betting they reopen |
| Split pots | 0 | 9,185 | Pots shared between equal hands |
| Odd chips | 0 | 3,433 | The remainder chip in a split pot |
The engine is checked against PokerKit, a reference implementation of the rules. Every decision of 100,000 random hands with 2 to 9 players is compared: legal actions, stacks and bets before each decision, pots and payouts at the end.
To confirm that the comparison can fail, rule faults were injected on purpose. Each was found in 12 to 2,930 of 3,000 hands, except one that removed a redundant check and left behaviour unchanged. The same seed produced identical hands on the cloud sandbox and on the workstation.
Table 2Engine throughput, hands per second on one core
| Configuration | Heads-up | Six players |
| Plain Python, workstation | 4,000 | 1,367 |
| Numba, cloud sandbox | ≈465,000 | 277,000 to 306,000 |
| Numba, workstation | 558,803 | 309,642 |
| Decisions per hand | 4.58 | 9.85 |
Random-action playouts. CFR also reads and writes regret tables at every node, so these figures are an upper bound for training speed, not a forecast. Numba is about 140 times faster than plain Python heads-up and about 226 times faster with six players.
Table 3CFR against OpenSpiel [10]: exploitability in chips per hand
| Algorithm | Iterations, Kuhn / Leduc | Kuhn | Leduc | Leduc, OpenSpiel |
| CFR | 10⁴ / 10³ | 1.13 × 10⁻⁴ | 1.18 × 10⁻² | 1.18 × 10⁻² |
| CFR+ | 10⁴ / 10³ | 9.6 × 10⁻⁶ | 2.56 × 10⁻⁴ | 2.57 × 10⁻⁴ |
| Discounted CFR | 10⁴ / 10³ | 2.4 × 10⁻⁵ | 1.74 × 10⁻⁴ | 1.44 × 10⁻⁴ |
| External sampling | 10⁶ | 7.2 × 10⁻⁴ | 1.90 × 10⁻² | 2.16 × 10⁻² |
| Outcome sampling | 10⁶ | 2.9 × 10⁻³ | 0.20 | – |
Sampling methods are means of 10 seeds. On Kuhn the full-width variants match OpenSpiel to 10⁻¹³ at every checkpoint, and the value for the first player is −0.055556 (−1/18). On Leduc [13] they agree to 10⁻¹³ for about 100 iterations and then drift apart; perturbing the project's own regrets once by a factor of 1 + 10⁻¹⁵ produces differences of the same size, so the drift is floating-point chaos, not a different algorithm. External sampling here draws all chance events once per traversal, OpenSpiel once per branch; with OpenSpiel's scheme the result is 0.0219 against 0.0216. Outcome sampling: 0.515 against 0.523 at 10⁵ iterations, 40 seeds.
Table 4Heads-up blueprint against rule-based agents, mbb/hand
| Opponent | M5 blueprint | 95% CI | M4 blueprint | 95% CI |
| Calling station | +2,144 | 1,963–2,325 | +2,258 | 2,070–2,446 |
| Pot raiser | +4,365 | 3,815–4,915 | +4,880 | 4,324–5,437 |
| Random | +2,131 | 1,856–2,405 | +2,045 | 1,773–2,318 |
| Equity threshold | +1,457 | 1,224–1,691 | +1,571 | 1,333–1,810 |
Each opponent plays 20,000 duplicate pairs, 40,000 hands: every deal is replayed with the seats swapped. The M5 blueprint is the final average strategy of external-sampling Monte Carlo CFR [9] on 23.2 million infosets: 169 lossless preflop classes, 500 buckets per later street from k-medians clustering of equity histograms under the earth mover's distance [11], 46,520 betting nodes and stacks of 200 big blinds. Compared with M4 it adds raises of four times the pot at the first and second preflop raise. The run took 988 million iterations in 8.0 hours on 10 threads, with 4.3 GB peak memory. Played against each other on the same deals, the M5 blueprint beats the M4 blueprint by 152 mbb/hand (95% CI 26 to 277). The checkpoint with the lowest local best response, at 944 million iterations, is not measurably different from the final strategy, so the final one is used here and in the game above.
The opponents always check or call, raise the pot whenever allowed, choose fold, call or a pot raise at random, or act on Monte Carlo equity against a random hand. They are weak, so the table shows that the blueprint plays sound poker, not that it is strong. Local best response [5] fell from between 822 ± 127 and 1,056 ± 126 mbb/hand in the first four evaluations to between 680 ± 126 and 786 ± 126 from 381 million iterations on (16 evaluations, 40,000 hands each). Bets outside the abstraction remain the main weakness: when the local best response may also bet 0.75, 1.5 or 3 times the pot, it wins 1,308 instead of 689 mbb/hand against the M5 blueprint and 1,561 instead of 924 against the M4 blueprint (60,000 hands each). Against the pot raiser the M5 blueprint left its betting tree 1,383 times in 40,000 hands and fell back to check or call. Real-time subgame solving is meant to close that gap.