feat(engine): true 169-class abstraction mode (Premium-A Phase 1.5 unblock, #68)#171
Merged
Merged
Conversation
…block, #68) True Path B follow-up to PR #167 (which shipped the hybrid 1326-combo engine + wrapper-aggregation path). The Rust engine now operates on 169-element strategy/regret/reach vectors directly. Leaf payoffs pre- bake per-(combo_a, combo_b) blocker mass into each (class_i, class_j) cell, so the inner CFR kernel is dimension-169 not dimension-1326. ## What ships - `crates/cfr_core/src/preflop_rvr.rs` — `HandResolution` enum, `Class169Combos` enumerator, `Class169TerminalCache` with pre-baked blocker-mass-weighted leaf payoffs, `Class169VectorDCFR` traversal, `solve_hunl_preflop_rvr_class169()` entry point. (+1024 LOC) - `crates/cfr_core/src/lib.rs` — Python binding `_rust.solve_hunl_preflop_rvr_class169` (+85 LOC). - `poker_solver/blueprint.py` — `generate_blueprint(hand_resolution=...)` defaults to CLASS_169 for the perf win; COMBO_1326 hybrid path preserved unchanged for diff-test + legacy use. - `tests/test_true_path_b_diff.py` — 6 differential tests including closed-form AA-vs-KK, cross-resolution L1 drift, monotone-convergence, speedup gate, memory footprint, pushfold-equivalence (+440 LOC). - 5 new Rust unit tests in `preflop_rvr::tests`. ## Measured speedup (matched iter count, M4 Pro arm64) | Stack | Iters | 1326-combo | 169-class | Speedup | |---|---|---|---|---| | 15 BB | 1000 | 312.7 s | 1.75 s | 178x | | 40 BB | 200 | 410.6 s | 1.01 s | 406x | | 100 BB | 100 | 445.4 s | 0.99 s | 448x | Projected 25k-iter wall on Phase 1.5 (27 blueprints): - 1326-combo hybrid: ~17-40h (matches task brief) - 169-class true Path B: **~1.5-2h** total ## Convergence equivalence `test_class169_matches_hybrid_within_l1_tolerance` (15 BB, 1000 iters, 6084 cells compared): **max L1 = 0.0000** between 169-class engine and `aggregate_to_169_classes(1326-combo engine)`. Lane-for-lane match modulo FP precision when default reach is combo-weighted (the natural full-deck interpretation; `vec![6.0, 4.0, 12.0, ...]` for pairs/suited/offsuit). ## Memory footprint 7.85x reduction in strategy storage (1326 -> 169 per infoset per action), matches theoretical 1326/169 ratio. ## Backwards compatibility - `solve_hunl_preflop_rvr` (1326-combo) untouched. - `aggregate_to_169_classes` wrapper untouched. - `_stub_rust_solver` test path opt-in to COMBO_1326 (one-line change).
This was referenced May 28, 2026
amaster97
added a commit
that referenced
this pull request
May 28, 2026
Premium-A Phase 8 documentation pass for the preflop blueprint feature (task #68). Adds: - docs/blueprint_user_guide.md (~190 lines) — end-user explainer: what the blueprint is, what ships in the .dmg, when to use blueprint vs custom solve, stack-depth coverage + interpolation behavior, ante config selection, common questions (Nash multiplicity vs published charts, blueprint vs aggregator, custom-range slowness). - docs/blueprint_developer_guide.md (~370 lines) — engineering reference: how to regenerate blueprints (CLI flag matrix), asset + manifest schema, 169-class vs 1326-combo paths, True Path B engine internals, how to add new depths or ante configs, blueprint -> postflop subgame wiring, validation pipeline. - README.md — new "Preflop blueprint mode" section linking both docs. - USAGE.md §5.9 — Python API example using BlueprintLoader (Phase 2) and interpolate_strategy (Phase 3), with the standard "what falls through to live solve" decision table. All examples cite real shipped APIs (BlueprintLoader.from_dir / .lookup / .actions / .available_depths; interpolate_strategy from blueprint_interp). Validation references point to preflop_100bb_chart_validation_v2_2026-05-28.md for Nash-multiplicity divergence framing. PRs cited: #163 (subplan), #167 (Phase 1 hybrid), #171 (True Path B 169-class kernel), #173 (interpolation), #174 (loader). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
amaster97
added a commit
that referenced
this pull request
May 28, 2026
Comprehensive reference for the major Rust performance optimizations landed during the v1.9.0 burst. Each entry includes: - problem context (what was slow) - optimization technique + code path - measured speedup vs baseline - diff-test gate that proves correctness - caveats on workload-dependence Headline numbers: - DCFR vs vanilla CFR: ~100x - PR #114 vector forward walk + TerminalCache: 213x on river RvR - PR #157 preflop perf: 6.5x wall / 10.4x kernel - PR #162 BR walk non-terminal cache: 2.5x on river chance-enum - PR #170 vector-form BR walk: 6.27x on W2.3 fixture - PR #171 true 169-class abstraction: 178x / 406x / 448x Compounded: 80,000x combined for Premium-A blueprint workload (weeks of vanilla CFR -> 38 minutes True Path B). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 tasks
amaster97
added a commit
that referenced
this pull request
May 28, 2026
Completes the user-requested J7o (and 27o, Test 4) 40 BB walkthrough first asked for prior to the cs-bug fix (PR #165) and True Path B engine (PR #171). All four tests use the 169-class True Path B fast engine (`_rust.solve_hunl_preflop_rvr_class169`) at 10000 DCFR iterations. Tests: - T1 baseline: SB opens J7o, BB calls, equity walk preflop->flop->turn->river - T2 3-bet/4-bet: SB J7o vs BB 3-bet vs SB 4-bet vs BB call line - T3 postflop raise: J7o equity vs BB's likely raise range on A89dd (engine is preflop-only; postflop equity from MC enumeration) - T4 off-distribution: 27o + 5x open, engine crash-check + defend dist Findings: J7o opens majority at 40 BB (matches published chart); SB prefers 2x over 3x open (GTO-consistent at 40 BB stack depth); 72o folds 100%; AA opens 100%; engine doesn't crash on off-tree play. Total wall: ~52s (29s solve + 23s postflop equity MC). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 tasks
amaster97
added a commit
that referenced
this pull request
May 28, 2026
Preemptive draft for v1.9.0 — ready for ship-trigger after audit clears. Covers the Premium-A blueprint shipping milestone: - 27 precomputed preflop blueprint shards (PR #171 + asset commit 1783bef): 169-class engine 178x/406x/448x speedup; ~21 MB bundle shipped under assets/blueprints/. - Blueprint loader API (PR #174), stack-depth interpolation (PR #173), postflop subgame wiring (PR #177), top-level SolverRouter (PR #181), UI integration (PR #178). - Vector-form BR walk (PR #170): W2.3 strict-PASS, persona table 17/0/0/0 (recorded in PR #184). - CS-bug fix (PR #165): preflop State::initial honors config.initial_contributions. - B10 per-combo frequency train (PRs #149/#154/#158/#160): W2.2 Sarah Range.diff -> PASS. - DCFR optimization ledger (docs/rust_optimization_ledger.md): empirical speedups for PRs #114/#139/#150/#157/#162/#170/#171. Known limitations disclosed honestly: - Flop live-solve OOMs in v1.9.0; flop = blueprint-only. v1.10 task #70 addresses (docs/v1_10_postflop_optimization_plan.md). - top_k_per_side is a speed knob, not a correctness flag. - "Realtime postflop" claim scoped precisely: turn + river only. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
True Path B follow-up to PR #167 — Rust engine now operates on
169-element strategy/regret/reach vectors directly instead of 1326-combo.
Pre-bakes per-(combo_a, combo_b) blocker mass into the (class_i, class_j)
leaf payoff cells so the inner CFR kernel is dimension-169 not
dimension-1326.
HOLD FOR USER REVIEW per task brief — engine refactors flagged as
high-risk. Diff test result locks in lane-for-lane equivalence with the
hybrid path.
Measured speedup (matched iter count, M4 Pro arm64)
Phase 1.5 projection for 25k-iter blueprints (27 cells):
Convergence equivalence
test_class169_matches_hybrid_within_l1_tolerance(15 BB, 1000 iters,6084 cells compared): max L1 = 0.0000 between 169-class engine and
aggregate_to_169_classes(1326-combo engine). Lane-for-lane matchmodulo FP precision when the default reach is combo-weighted (the
natural full-deck interpretation;
[6, 4, 12, ...]forpairs / suited / offsuit). Test brief target: L1 ≤ 0.05.
Memory footprint
7.85× reduction in strategy storage (1326 → 169 per infoset per
action), matches theoretical 1326/169 ratio.
What ships
crates/cfr_core/src/preflop_rvr.rs(+1024 LOC) —HandResolutionenum,
Class169Combosenumerator,Class169TerminalCachewithpre-baked blocker-mass-weighted leaf payoffs,
Class169VectorDCFRtraversal,
solve_hunl_preflop_rvr_class169()entry point + 5 newRust unit tests.
crates/cfr_core/src/lib.rs(+85 LOC) — Python binding_rust.solve_hunl_preflop_rvr_class169.poker_solver/blueprint.py(+99 LOC, -10 LOC) —generate_blueprint(hand_resolution=...)defaults to CLASS_169 forthe perf win; COMBO_1326 hybrid path preserved unchanged.
tests/test_true_path_b_diff.py(+440 LOC) — 6 differential tests:closed-form AA-vs-KK, cross-resolution L1 drift, monotone-convergence,
speedup ≥ 2× gate, memory footprint ≥ 5× gate, pushfold-equivalence.
Test plan
cargo test --release --lib— 81 passed (9 preflop_rvr + 5 new169-class tests inline)
cargo test --release --test preflop_rvr_smoke— 4 passedcargo clippy --lib --tests --release -- -D warnings— cleanpytest tests/test_true_path_b_diff.py— 6 passedpytest tests/test_blueprint_pipeline.py— 24 passed(stub-solver test updated to opt-in COMBO_1326)
pytest tests/test_preflop_rvr_diff.py— 4 passed (existing1326-combo path unchanged)
pytest tests/test_kuhn_dcfr tests/test_leduc_dcfr tests/test_dcfr_diff— passed (no regression on small-gamediff tests)
ruff check poker_solver/blueprint.py tests/test_true_path_b_diff.py— cleanmypy poker_solver/blueprint.py— cleanBackwards compatibility
solve_hunl_preflop_rvr(1326-combo) untouched.aggregate_to_169_classeswrapper untouched (still works forexact per-combo strategies + legacy callers).
_stub_rust_solvertest path: opt-in to COMBO_1326 (one-line changeto
tests/test_blueprint_pipeline.py:397).Risks identified
from hybrid at indifference manifolds (deep all-in spots). Empirically
the diff test shows max L1 = 0.0000 at 15 BB / 1000 iters; should
hold for deeper stacks too because the equivalence is mechanical
(lane-for-lane reach algebra).
root_reach_p0/root_reach_p1defaults to combo-weighted[6, 4, 12, ...]. Callerspassing
[1, 1, 1, ...](uniform-over-classes) get a differentequilibrium — documented in the docstring.
~25 ms per leaf (vs 0.4 ms for 1326x1326). At 30 iters / 100 leaves
this is ~750 ms one-time cost. At 25k iters the cost is amortized;
at < 1k iters the hybrid path may be faster on tiny configs.
Out of scope
-O3autovectorizes; measured speedup is already 100-400× so no headroom
push needed).
References
crates/cfr_core/src/preflop_rvr.rsassets/preflop_equity_169x169.npzGenerated with Claude Code