feat(engine): 169-class abstraction mode + blueprint generation pipeline (Phase 1, #68)#167
Merged
Merged
Conversation
…ine (Phase 1, #68) Premium-A subplan Phase 1: build the offline preflop blueprint generation pipeline + asset format design, ahead of the Phase 1.5 overnight compute (27 cells = 9 depths x 3 ante configs). ## Path taken Hybrid Path B per the task brief's escape clause. The engine continues to run the 1326-combo vector-form CFR; the pipeline post-aggregates each infoset's strategy into a 169-class strategy via uniform combo-weighted average within each class. This is lossless within the engine's existing suit-variant approximation (the 169x169x3 equity table already integrates suit overlap). True Path B (169-vector inner loop) requires building an "effective" 169x169 leaf table that integrates blocker-respecting combo-weighted equity per class pair — a focused 1-2 day Rust refactor that does not fit the 4 hour Phase 1 budget. The asset schema is forward-compatible with that follow-up. ## What this PR delivers 1. ``poker_solver/blueprint.py`` (965 LOC) — Blueprint dataclasses, gzipped JSON asset format, combo-weighted aggregation, label reconstruction, manifest, validator. Also ships ``HandResolution`` enum (``COMBO_1326`` / ``CLASS_169``) as the forward-compatible switch. 2. ``scripts/generate_preflop_blueprint.py`` (280 LOC) — CLI driver with single-cell + batch (--all-depths --all-antes) modes, idempotent resume, progress logging + ETA, --dry-run for pipeline validation. 3. ``tests/test_blueprint_pipeline.py`` (24 tests) — schema round-trip, aggregation correctness (uniform + averaging variants), label reconstruction, validator catches malformed data, manifest round-trip, filename canonicalization (3 ante configs), CLI dry-run smoke, idempotent resume, batch grid (27 cells), enum semantics. 4. ``docs/blueprint_generation.md`` — operator guide with CLI flags, asset schema, Phase 1.5 invocation, validation contract. 5. Asset format: ``preflop_169class_{depth}bb_{ante}.json.gz`` shards + ``manifest.json``. Per-shard sha256 over uncompressed JSON (reproducible across platforms). ## Phase 1 deliverables (per task brief) - [x] Engine work: HandResolution enum added to public surface; follow-up will swap engine internals. - [x] Generation script with single + batch modes, idempotent. - [x] Asset format: schema + manifest documented + tested. - [x] Tests: smoke + schema validation + roundtrip. - [x] Documentation: docs/blueprint_generation.md. ## Smoke test result Single cell, 100 iters, depth 40, no ante (on M4 Pro arm64): - Wall: 18.3 s - Output: 150 infosets, 25,350 strategy rows (150 x ~169 classes) - Compressed size: 672 KB - AA fold rate at SB root: 0.0 (correct — AA must commit) - 72o fold rate at SB root: 0.9999 (correct — 72o folds) - Validation: 0 warnings, schema clean ## Phase 1.5 estimate (extrapolated from smoke) - 100 iters cost = 18 s; 25K iters ≈ 250x ≈ 75 min per cell - 27 cells x ~75-90 min ≈ 34-40 h single-threaded - Compressed total: ~30-60 MB (within 90 MB target) - Parallelizable across two terminals to ~17-20 h wall. ## Risks identified - The wrapper-side 1326 -> 169 aggregation is lossless WITHIN the engine's 3-variant equity approximation. True 169-vector engine would integrate blocker-respecting combo weights more carefully — divergence between the two implementations at deep-cap is expected to be small but should be measured before any cross-solver gate locks down (Phase 7). - Phase 1.5 wall-time estimate is extrapolated from 100-iter smoke. If per-iter cost scales sub-linearly with iter count (DCFR discount cost grows with t^alpha), the estimate may be ~10-20% high. - Label reconstruction for non-root infosets is mechanical-replay-based; if the engine's action menu adds a new code path, the wrapper's label fallback (``a{idx}``) preserves schema validity but loses readability. ## Tests - 24 new unit tests in tests/test_blueprint_pipeline.py — all PASS. - Existing tests/test_preflop_rvr_diff.py (4 tests) — all PASS. - tests/test_kuhn_dcfr + test_leduc_dcfr + test_dcfr_diff (16 tests) — all PASS. ## Lint - ``ruff check``: clean. - ``ruff format --check``: clean. - ``mypy poker_solver/blueprint.py``: clean. - ``cargo clippy --lib --tests``: clean (bench errors pre-existing on main). ## NOT in this PR - Phase 1.5 (the actual 27-cell overnight compute) — user runs on terminal. - True 169-vector engine inner loop — follow-up PR for per-iter speedup. - Phase 2 loader API (downstream PR). - Postflop subgame anchoring (Phase 4). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 28, 2026
amaster97
added a commit
that referenced
this pull request
May 28, 2026
…block, #68) (#171) 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>
6 tasks
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
Phase 1 of the Premium-A subplan (
docs/premium_a_blueprint_subplan.md, task #68). Ships the offline preflop blueprint generation pipeline + asset format design, ahead of the Phase 1.5 overnight compute (27 cells = 9 depths x 3 ante configs).Path taken
Hybrid Path B per the task brief's escape clause. The Rust engine continues to run the 1326-combo vector-form CFR; the pipeline post-aggregates each infoset's strategy into a 169-class strategy via uniform combo-weighted average within each class.
This is lossless within the engine's existing suit-variant approximation (the 169x169x3 equity table already integrates suit overlap). True Path B (169-vector inner loop) requires building an "effective" 169x169 leaf table that integrates blocker-respecting combo weights per class pair — a 1-2 day Rust refactor that does not fit the 4 hour Phase 1 budget. The asset schema is forward-compatible with that follow-up.
What this PR delivers
poker_solver/blueprint.py(965 LOC) —Blueprint+BlueprintConfig+Manifestdataclasses, gzipped JSON asset format, combo-weighted aggregation, label reconstruction, validator. ShipsHandResolutionenum (COMBO_1326/CLASS_169) as the forward-compatible switch.scripts/generate_preflop_blueprint.py(280 LOC) — CLI driver with single-cell + batch (--all-depths --all-antes) modes, idempotent resume, progress logging + ETA,--dry-runfor pipeline validation.tests/test_blueprint_pipeline.py(24 tests) — schema round-trip, aggregation correctness, label reconstruction, validator catches malformed data, manifest round-trip, filename canonicalization (3 ante configs), CLI dry-run smoke, idempotent resume.docs/blueprint_generation.md— operator guide.preflop_169class_{depth}bb_{ante}.json.gzshards +manifest.json. Per-shard sha256 over uncompressed JSON (reproducible).Smoke test result
100 iters at depth 40, no ante (on M4 Pro arm64):
Phase 1.5 estimate (extrapolated from smoke)
Test plan
pytest tests/test_blueprint_pipeline.py— 24 PASSpytest tests/test_preflop_rvr_diff.py— 4 PASS (existing engine, unchanged)pytest tests/test_kuhn_dcfr tests/test_leduc_dcfr tests/test_dcfr_diff— 16 PASSruff check— cleanruff format --check— cleanmypy poker_solver/blueprint.py— clean (0 errors; pre-existing 6 errors oncli.py/chained_tab.pyare not from this PR)cargo clippy --lib --tests— clean (pre-existing 2 bench errors on main are not from this PR)scripts/generate_preflop_blueprint.py --depth 40 --ante none --iterations 100) — PASSNOT in this PR
Risks
a{idx}labels preserve schema validity but lose readability.References
docs/premium_a_blueprint_subplan.mdcrates/cfr_core/src/preflop_rvr.rsassets/preflop_equity_169x169.npz43ed53e)Generated with Claude Code