An independent research framework for learning how poker opponents play and testing when exploiting them is worth the risk. I built the simulation, profiling, game-solving, recommendation, and evaluation tools around seeded experiments and independent checks.
How many hands do you need to understand an opponent, and when does acting on that information help? I built Poker Lab to test both questions: first learn an opponent from the information a player could actually see, then measure the gain and risk of changing strategy.
The framework simulates no-limit hold'em with 2–10 players. Its equilibrium and exploitation experiments use defined games where strategies can be evaluated: Kuhn poker, Leduc hold'em, and heads-up preflop push/fold hold'em. It does not solve unrestricted no-limit hold'em or establish a winning strategy against real players.
What I built
Completed, redacted histories feed separate profiling and push/fold modeling paths. Live advice receives only the advised seat's own cards and public state.
Simulation, records, and information boundaries
Wrapped PokerKit's rules engine and integrated phevaluator for faster showdowns. Seeded dealing, rotating seats, parallel leagues, and independent per-table seeds make simulations reproducible across worker counts. A recorded parallel benchmark reached about 2,500 hands/second with 12–15 workers.
Built nine-parameter bots sampled from five overlapping archetypes: nit, TAG, LAG, calling station, and maniac. Ground-truth parameters stay separate from the hand histories used for learning.
Designed validated, versioned JSONL hand records with chip accounting, strict serialization, and schema migration. Schema v2 adds big-blind antes, straddles, and configurable minimum bets. Differential tests replay scenarios through unmodified PokerKit.
Built the sealed hand store, identity-checked seat keys, redacted completed histories, and an advisor interface restricted to a SeatView and completed-hand profiles. Tests check hidden-card invariance, forged inputs, and object-graph access.
Added an offline PHH importer/exporter with action-by-action replay, typed rejection reasons, and finishing-stack checks. The documented schema-v2 import accepts all 10,000 Pluribus hands, 11 WSOP hold'em hands, and one historical hand; split-pot rounding differences are recorded explicitly.
Built a capture-ingestion interface for manual entries and card observations, with typed conflicts for low confidence, duplicate cards, incompatible actions, and payout mismatches. The detector and recording UI remain separate future work.
Opponent profiling and machine learning
Extracted player statistics with explicit opportunity counts, then fit empirical-Bayes priors on training players to stabilize small samples. Profiles persist across sessions without exposing unshown cards.
Implemented archetype classifiers, parameter regression, and next-action prediction, with grouped player splits, temperature calibration, ablations, and population/oracle baselines.
Built a PyTorch GRU over raw action sequences and compared it with interpretable statistics on the same held-out players. I also fit priors to real professionals and checked whether models trained on synthetic bots transferred.
On the 13-player Pluribus sample, a prior fitted to other professionals estimated a held-out player's statistics with 4.8 percentage points RMSE after 25 hands, versus 13.8 for a uniform prior.
Game solvers and recommendations
Current default policy: instability forces equilibrium play. A shown-hand fit failure warns about the posterior intervals but does not automatically reject an exploit.
Compiled small-game trees into flat NumPy arrays and implemented sequence-form LP solving, exact best responses, and exploitability evaluation. Benchmarked CFR, CFR+, Linear CFR, Discounted CFR, external-sampling MCCFR, and Deep CFR against exact baselines.
Solved heads-up push/fold across 169 hand classes and stacks up to 20 big blinds, using exact card-removal weights and a Monte Carlo matchup-equity table. The advisor recognizes supported preflop spots; unsupported situations use a fallback or raise OffTree.
Implemented best response, restricted Nash response, and data-biased response to measure the tradeoff between expected gain and exploitability.
Built a push/fold recommender using a Laplace-based importance sampler over opponent parameters. Closed-form payoffs make scoring candidate strategies against posterior draws practical. It reports intervals and exploitability, and ranks equilibrium, restricted-Nash, and Bayes responses by the 5% quantile of gain over equilibrium.
Added posterior predictive checks for shown-hand fit and changes in behavior. A fresh-seed confirmation experiment selected the current default: fall back on a stability failure, but treat shown-hand misfit as a warning about the intervals.
Evaluation and research tools
Implemented paired duplicate deals, all-in EV comparisons, and AIVAT control variates. For the small games, I computed exact estimator moments to check bias and variance instead of relying only on noisy sampled win rates.
Tested static, misspecified, gear-changing, and adapting opponents. These experiments separate the value of observing an individual from the value already available in a population prior.
Built a self-contained offline results explorer with precomputed views, interactive charts, tables, metric controls, and light/dark themes. Phase runners regenerate it alongside the experiment outputs.
Results and what they mean
Identifying simulated opponents: on held-out bots from the five-archetype population, the stats-based classifier reached 80% accuracy after about 44 hands and 90% after about 194. A 25-hand profile reduced next-action log-loss by 11.2% versus a situation-only population baseline. These are simulation results, not human-player accuracy claims.
Checking the solvers: LP solutions matched Kuhn's −1/18 and Leduc's approximately −0.0856064 game values, with exploitability below 1e−9. In the recorded 10,000-iteration Leduc comparison, CFR+ was about 1,800× less exploitable than vanilla CFR. Push/fold call thresholds matched all 169 reference-chart entries within 0.1 big blinds.
Conservative recommendations: in the standard, stationary push/fold bot experiment, the ranked recommendation underperformed equilibrium against at most 2.7% of bots, versus 25% for the Bayes best response after 10 hands. Its gain was smaller at low sample sizes, and posterior intervals became unreliable under some misspecifications.
Variance reduction: in Leduc, AIVAT with equilibrium value estimates needed a median 27× fewer hands for the equilibrium hero when all cards were observable afterwards. With folded cards hidden from the evaluator, the gain was only about 1.05×.
Baselines and transfer mattered: the GRU did not beat the logistic model with 300 training bots; it overtook that baseline with 1,500, while boosted trees became strongest at larger observation counts. On the 13 Pluribus professionals, bot-trained models were overconfident and their action predictions transferred poorly. Adaptive-opponent experiments also showed that exploitation gains can disappear as the opponent changes.
Engineering and reproducibility
Grew the completed branch's recorded test suite to 471 cases, covering contracts, hand-worked examples, differential replay, information leaks, migrations, and experiment outputs.
Pre-registered primary experiment predictions, labeled post-hoc analyses, and confirmed the stability-only fallback on fresh seeds. Built phase runners and a comparison tool for result fields, summaries, and figure pixels.
Profiled the expensive paths, then added exact-input LP caching, reuse of sequence embeddings, an advice-only fast path, and parallel sequence replicates. HiGHS basis reuse made a restricted-Nash sweep about 3.7× faster in the documented development benchmark; a same-session adaptive quick benchmark improved from about 27 to 16 seconds.
Checked optimizations against stored results. The earlier caching changes preserved outputs exactly; later LP hot starts introduced documented floating-point differences while summary lines and figure pixels stayed unchanged. Added pytest-xdist scheduling with long tests first, bringing the 471-test suite to about 54 seconds in the recorded benchmark.
Current scope
The simulation, small-game solvers, push/fold bridge, and Phase 3 experiments are implemented. A multi-player duplicate-dealing library is in development, with its full registered evaluation still pending. The next phases cover camera-based home-game capture and live human win-rate evaluation.