Skip to content

feat(engine): true 169-class abstraction mode (Premium-A Phase 1.5 unblock, #68)#171

Merged
amaster97 merged 1 commit into
mainfrom
feat/true-path-b-169class-engine
May 28, 2026
Merged

feat(engine): true 169-class abstraction mode (Premium-A Phase 1.5 unblock, #68)#171
amaster97 merged 1 commit into
mainfrom
feat/true-path-b-169class-engine

Conversation

@amaster97

Copy link
Copy Markdown
Owner

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)

Stack Iters 1326-combo 169-class Speedup
15 BB 1000 312.7 s 1.75 s 178×
40 BB 200 410.6 s 1.01 s 406×
100 BB 100 445.4 s 0.99 s 448×

Phase 1.5 projection for 25k-iter blueprints (27 cells):

  • 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 the default reach is combo-weighted (the
natural full-deck interpretation; [6, 4, 12, ...] for
pairs / 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) — HandResolution
    enum, Class169Combos enumerator, Class169TerminalCache with
    pre-baked blocker-mass-weighted leaf payoffs, Class169VectorDCFR
    traversal, solve_hunl_preflop_rvr_class169() entry point + 5 new
    Rust 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 for
    the 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 --lib81 passed (9 preflop_rvr + 5 new
    169-class tests inline)
  • cargo test --release --test preflop_rvr_smoke4 passed
  • cargo clippy --lib --tests --release -- -D warningsclean
  • pytest tests/test_true_path_b_diff.py6 passed
    • AA-vs-KK closed-form: AA fold < 1%, commit > 99% ✓
    • Cross-resolution L1 drift: max L1 = 0.0000 (lane-for-lane) ✓
    • Monotone convergence: d(100→500)=0.0208, d(500→2000)=0.0063 ✓
    • Speedup ≥ 2×: measured 11.6× at 40 BB / 30 iters ✓
    • Memory footprint ≥ 5×: 7.85× ✓
    • Pushfold equivalence at 15 BB ✓
  • pytest tests/test_blueprint_pipeline.py24 passed
    (stub-solver test updated to opt-in COMBO_1326)
  • pytest tests/test_preflop_rvr_diff.py4 passed (existing
    1326-combo path unchanged)
  • pytest tests/test_kuhn_dcfr tests/test_leduc_dcfr tests/test_dcfr_diffpassed (no regression on small-game
    diff tests)
  • ruff check poker_solver/blueprint.py tests/test_true_path_b_diff.py — clean
  • mypy poker_solver/blueprint.py — clean

Backwards compatibility

  • solve_hunl_preflop_rvr (1326-combo) untouched.
  • aggregate_to_169_classes wrapper untouched (still works for
    exact per-combo strategies + legacy callers).
  • _stub_rust_solver test path: opt-in to COMBO_1326 (one-line change
    to tests/test_blueprint_pipeline.py:397).

Risks identified

  • R1 (Nash multiplicity): 169-class engine could in principle drift
    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).
  • R2 (Reach semantics): caller-supplied root_reach_p0 /
    root_reach_p1 defaults to combo-weighted [6, 4, 12, ...]. Callers
    passing [1, 1, 1, ...] (uniform-over-classes) get a different
    equilibrium — documented in the docstring.
  • R3 (Leaf-table build cost): building the 169x169 leaf table costs
    ~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

  • Postflop solver — preflop-only refactor per task brief.
  • 169-class abstraction for the chained orchestrator / RvR aggregator.
  • Engine kernel SIMD tuning for the 169-dim slices (LLVM -O3
    autovectorizes; measured speedup is already 100-400× so no headroom
    push needed).

References

Generated with Claude Code

…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).
@amaster97 amaster97 merged commit a7a23ff into main May 28, 2026
5 of 8 checks passed
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>
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>
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>
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