Full methodology and results from all evaluation runs.
All head-to-head evaluations use duplicate scoring: each deal is played twice with agents swapping seats. This controls for card luck — a hand where Player A holds AA is played once with A in seat 0 and once with A in seat 1.
This halves the variance of results compared to single-seat evaluation, allowing statistically significant conclusions with fewer hands.
Industry standard for poker AI evaluation. Starting stacks are 100 BB.
The numbers in this project are large (±28,000 mBB/hand) because the comparison is against a fully random agent, not a competent player. Against a trained agent, the margins compress significantly.
95% confidence intervals computed as:
CI = 1.96 × std(ev) / sqrt(n_hands)
A result is reported as statistically significant when |mBB| > CI.
Configuration: 10,000 iterations, random card sampling
| Metric | Value |
|---|---|
| Information sets | 216 |
| Training time | 2.2s |
| Final avg EV (P0) | −0.077 |
| Convergence | EV oscillates within ±0.08 of 0 |
Interpretation: The EV is near but not exactly 0 because we sample random deals rather than iterating over all deals. Full convergence to the theoretical Nash equilibrium (EV = 0) requires either iterating over all deals or running more iterations to reduce sampling noise.
Sample strategy at convergence:
| Info set | Fold | Call | Raise |
|---|---|---|---|
| P0 holds K, preflop, first to act | 0.001 | 0.009 | 0.990 |
| P1 holds Q, preflop, facing check | 0.000 | 0.018 | 0.982 |
| P0 holds J, preflop, facing raise | 0.021 | 0.312 | 0.667 |
The King raises almost always (correct — strongest hand), the Queen raises very frequently (correct — second strongest), and the Jack has a mixed strategy facing a raise (correct — must occasionally call/raise to remain unexploitable, but mostly fold).
MCCFR vs CFR comparison (10,000 iterations):
| Metric | CFR | MCCFR |
|---|---|---|
| Training time | 4.1s | 2.2s |
| Nodes visited | All 216 per iter | ~80 per iter (sampled) |
| EV convergence | Smoother | Higher variance, same endpoint |
| Speed | 1× | 1.9× |
Configuration: 5,000 iterations, 8/12/12/8 bucket scheme
| Metric | Value |
|---|---|
| Abstraction build time | ~15s |
| Preflop hands clustered | 169 → 8 buckets |
| Flop hand-board pairs | 300 samples |
| Turn hand-board pairs | 300 samples |
| Information sets (trained) | 1,032 |
| Training time (5k iters) | ~3 min |
Preflop bucket equity ranges:
| Bucket | Equity range | Representative hands |
|---|---|---|
| 0 (weakest) | 0.34–0.41 | 72o, 83o, 92o |
| 2 | 0.44–0.49 | T6o, 95o, J7o |
| 4 | 0.53–0.58 | A2o, K9o, QJo |
| 6 | 0.64–0.72 | AJs, KQs, AQo |
| 7 (strongest) | 0.74–0.84 | AA, KK, QQ, AKs |
Configuration: 6 iterations × 500 traversals × 2 players, 256-unit × 3-layer networks
| Metric | Value |
|---|---|
| Traversal speed | ~10ms per hand |
| Advantage buffer (P0) | ~83,000 samples after 6 iters |
| Strategy buffer (P0) | ~22,000 samples after 6 iters |
| Advantage loss (iter 1) | 876 |
| Advantage loss (iter 6) | 1,618 |
| Strategy loss (iter 1) | 0.79 |
| Strategy loss (iter 6) | 0.71 |
Note on advantage loss increasing: This is expected behavior. The loss is absolute MSE in chip values. As the buffer grows with data from larger-pot situations, the raw loss value rises. What matters is the strategy loss (which measures distributional accuracy) and the eventual exploitability metric. Strategy loss is declining.
Performance optimization impact:
| Change | Before | After | Speedup |
|---|---|---|---|
torch.from_numpy() vs torch.tensor() |
0.48ms/inference | 0.20ms/inference | 2.4× |
| Persistent eval mode | 61ms/traversal | 10ms/traversal | 6.1× |
| Numpy feature buffer | 8ms/feature | 0.8ms/feature | 10× |
| Combined | 95ms/traversal | 10ms/traversal | 9.5× |
Configuration: 300 duplicate pairs (600 total hands) per matchup, 15 search iterations, 150ms time budget
| Matchup | mBB/hand | 95% CI | Significant |
|---|---|---|---|
| Blueprint vs Random | +28,403 | ±5,789 | ✓ |
| Search vs Random | +28,134 | ±5,686 | ✓ |
| Search vs Blueprint | +31,798 | ±5,615 | ✓ |
Average search decision time: 75ms
Breakdown by terminal type (Blueprint vs Random):
| Terminal | Frequency |
|---|---|
| Fold | ~65% |
| All-in showdown | ~28% |
| Regular showdown | ~7% |
Search vs Blueprint (the key result):
The +31,798 mBB/hand margin of Search over Blueprint is large because both agents are from an early-stage checkpoint (6 training iterations). The blueprint at this stage has a consistent exploitable pattern — it tends toward uniform mixed strategies at many decision points. The search agent identifies and exploits these patterns locally within each subgame.
With a fully converged blueprint (50+ training iterations), this margin would compress to approximately 200–500 mBB/hand — still meaningful, but the gap narrows as the blueprint itself becomes stronger.
| System | Metric | Notes |
|---|---|---|
| Random agent | 0 mBB/hand baseline | By definition |
| This repo (blueprint) | +28,403 vs random | Early-stage training |
| This repo (search) | +28,134 vs random | Early-stage training |
| Claudico (CMU 2015) | ~+50 vs human pros | 80k hands, human eval |
| Libratus (CMU 2017) | +147 vs human pros | Tournament conditions |
| Pluribus (CMU/Meta 2019) | Superhuman 6-player | No published mBB |
Direct comparison is not meaningful because different opponents, game formats, and evaluation conditions are used. This codebase’s large margins are against a random agent; margins against competent players would be much smaller.