Skip to content

feat(engine): 169-class abstraction mode + blueprint generation pipeline (Phase 1, #68)#167

Merged
amaster97 merged 1 commit into
mainfrom
feat/premium-a-phase-1-pipeline
May 28, 2026
Merged

feat(engine): 169-class abstraction mode + blueprint generation pipeline (Phase 1, #68)#167
amaster97 merged 1 commit into
mainfrom
feat/premium-a-phase-1-pipeline

Conversation

@amaster97

Copy link
Copy Markdown
Owner

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

  1. poker_solver/blueprint.py (965 LOC) — Blueprint + BlueprintConfig + Manifest dataclasses, gzipped JSON asset format, combo-weighted aggregation, label reconstruction, validator. 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, label reconstruction, validator catches malformed data, manifest round-trip, filename canonicalization (3 ante configs), CLI dry-run smoke, idempotent resume.
  4. docs/blueprint_generation.md — operator guide.
  5. Asset format: preflop_169class_{depth}bb_{ante}.json.gz shards + manifest.json. Per-shard sha256 over uncompressed JSON (reproducible).

Smoke test result

100 iters at depth 40, no ante (on M4 Pro arm64):

Metric Value
Wall time 18.3 s
Infosets 150
Strategy rows 25,350 (150 x ~169 classes)
Compressed size 672 KB
AA fold rate at root 0.0 (correct)
72o fold rate at root 0.9999 (correct)
Validator warnings 0

Phase 1.5 estimate (extrapolated from smoke)

  • 25K iters ≈ 75-90 min per cell
  • 27 cells single-threaded ≈ 34-40 h wall
  • Parallelizable across two terminals ≈ 17-20 h wall
  • Compressed total: ~30-60 MB (within 90 MB target)

Test plan

  • pytest tests/test_blueprint_pipeline.py — 24 PASS
  • pytest 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 PASS
  • ruff check — clean
  • ruff format --check — clean
  • mypy poker_solver/blueprint.py — clean (0 errors; pre-existing 6 errors on cli.py/chained_tab.py are not from this PR)
  • cargo clippy --lib --tests — clean (pre-existing 2 bench errors on main are not from this PR)
  • Live end-to-end engine smoke (scripts/generate_preflop_blueprint.py --depth 40 --ante none --iterations 100) — PASS

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 for per-iter speedup)
  • Phase 2 loader API + Phase 4 postflop anchoring (separate PRs)

Risks

  • R1 (wrapper-aggregation accuracy) — wrapper aggregation is lossless within the engine's 3-variant equity approximation; divergence against a true 169-vector engine at deep-cap depths should be measured before any cross-solver strict gate locks (Phase 7).
  • R2 (Phase 1.5 wall-time estimate) — extrapolated from 100-iter smoke; may be ~10-20% high if DCFR discount cost amortizes per iter.
  • R3 (label reconstruction) — mechanical-replay-based; if the engine adds new action paths the wrapper's fallback a{idx} labels preserve schema validity but lose readability.

References

Generated with Claude Code

…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>
@amaster97 amaster97 merged commit 4416d2f into main May 28, 2026
5 of 8 checks passed
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).
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant