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 | Final blueprint | 95% CI | Best-LBR blueprint | 95% CI |
| Calling station | +2,258 | 2,070–2,446 | +2,606 | 2,392–2,820 |
| Pot raiser | +4,880 | 4,324–5,437 | +4,897 | 4,333–5,462 |
| Random | +2,045 | 1,773–2,318 | +2,428 | 2,145–2,710 |
| Equity threshold | +1,571 | 1,333–1,810 | +1,822 | 1,568–2,076 |
Each opponent plays 20,000 duplicate pairs, 40,000 hands: every deal is replayed with the seats swapped. The blueprint is the average strategy of external-sampling Monte Carlo CFR [9] on 19.5 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], 39,096 betting nodes and stacks of 200 big blinds. The run took 644 million iterations in 8.2 hours on 10 threads, with 8.9 GB peak memory. The best-LBR blueprint is the checkpoint with the lowest local best response.
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] stayed between 822 ± 180 and 1,247 ± 177 mbb/hand at all eleven checkpoints without a downward trend, so the exit criterion of M4 is only partly met. Bets outside the abstraction are the main weakness: against the pot raiser the blueprint left its betting tree 1,345 times in 40,000 hands and fell back to check or call. Depth-limited subgame solving (M5) is meant to close that gap.