id: e8f71d7d83654dc785966db298b4c1db
parent_id: c15acaa9da4f4369811a01fcaa459910
item_type: 1
item_id: de72ff3ebcbd45689c988e50038e80b0
item_updated_time: 1783318285389
title_diff: "[]"
body_diff: "[{\"diffs\":[[0,\"odelling\"],[1,\" (State of the Art)\"],[0,\"\\\n\\\n> **Cu\"]],\"start1\":29,\"start2\":29,\"length1\":16,\"length2\":35},{\"diffs\":[[0,\"07-0\"],[-1,\"2. Current p\"],[1,\"6. P\"],[0,\"rodu\"]],\"start1\":82,\"start2\":82,\"length1\":20,\"length2\":12},{\"diffs\":[[0,\"tegy\"],[-1,\".** Wraps Gen 3 with a dynamic observer that tracks real opponent statistics (VPIP, PFR, AF, bet frequencies) via EMA, then uses those stats to build more accurate ranges and adapt thresholds.\\\n\\\n## Role in the Framework\\\n\\\nGen 4 is the **live production strategy**. It doesn't replace Gen 3's decision logic — it feeds Gen 3 better inputs:\\\n1. **Better ranges**: Observer tracks opponent actions → predicts tighter/more accurate ranges\\\n2. **Adaptive thresholds**: Table VPIP/AF observations override Gen 3 config defaults\\\n3. **Range narrowing**: Per-action within a hand, the opponent's range is narrowed based on their specific actions (bet, call, raise, check)\\\n4. **Bet sizing awareness** (new 2026-07-02): Overbets (>2/3 pot) tighten the range toward polarized holdings (value or bluff)\\\n\\\n## Preflop Maneuver\"],[1,\" is Gen 4.7** — matrix-based equity preflop + Gen 4 adaptive postflop. The strategy chain is: `g2_ke_hu_nash` (HU only) → `g47_equity_preflop` → `g4_adaptive_postflop`.\\\n\\\n## Gen 4.7 — Matrix-Based Equity Preflop (NEW, 2026-07-06)\\\n\\\nGen 4.7 replaces the NG ranking-table preflop with **pure equity decisions** from a precomputed 169×169 PEM2 distribution matrix. The strategy chain for cash games is now:\"],[0,\"\\\n\\\n| \"]],\"start1\":104,\"start2\":104,\"length1\":814,\"length2\":409},{\"diffs\":[[0,\"n | \"],[-1,\"Adaptivity\"],[1,\"Role\"],[0,\" |\\\n|\"]],\"start1\":538,\"start2\":538,\"length1\":18,\"length2\":12},{\"diffs\":[[0,\"um (\"],[-1,\"no adaptation) |\\\n| 2 | `g1_short_stack_preflop` | ≤25 BB | Push/fold chart (no adaptation) |\\\n| 3 | `g45_playability_preflop` | >25 BB | NG preflop + **flop playability-enhanced promotion** (flexible 4-6 BB call barrier) |\\\n\\\n### Adaptive Preflop (`g45_playability_preflop`)\\\nWraps `ng_preflop` with table-VPIP-aware looseness and flop-playability-enhanced fold→call promotion. The looseness factor scales open-raise and limp thresholds (clamped 0.5–2.0). Flexible call barrier: high-playability hands can call up to 6 BB, marginal hands limited to 4 BB\"],[1,\"unchanged) |\\\n| 2 | `g47_equity_preflop` | All preflop | **NEW: PEM2 matrix equity + VPIP/PFR-adaptive thresholds** |\\\n| 3 | `g4_adaptive_postflop` | Postflop | Gen 4 adaptive (unchanged) |\\\n\\\n> **Note:** `g1_short_stack_preflop` was **removed** from the cash config. Its static raise/fold ranges blocked G47 from evaluating push/fold spots (e.g. folding 55 from BTN at 10 BB). Now G47 handles all preflop decisions\"],[0,\".\\\n\\\n##\"],[1,\"#\"],[0,\" P\"],[-1,\"ostflop Maneuver\\\n\\\n### Adaptive Postflop (`g4_adaptive_postflop`)\\\n\\\n**Decision flow:**\\\n1. **External event feed** (live server): observer receives events via `on_hand_start` / `on_player_action` Strategy trait methods — no self-observe needed\\\n2. **Check observer warmth**: if `table.hands_observed < 5`, fall back to Gen 3 defaults (cold start)\\\n3. **For each opponent seat**:\\\n   - `observer.effective_range_params(seat)` → range width/strength (three-tier blend)\\\n   - `observer.effective_action_freqs(seat)` → bet/raise/call frequencies\\\n   - `observer.effective_preflop_freqs(seat)` → PFR, 3-bet frequency\\\n   - Build `NarrowContext` from observed frequencies + this hand's actions + bet sizing\\\n   - `range_builder::build_opponent_range_v2()` → weighted `HandRange`\\\n4. **Compute config overrides** from table aggregate stats (VPIP, AF)\\\n5. **Build overridden Gen3Config** with table-adaptive thresholds\\\n6. **Delegate to Gen 3**: `gen3.compute_equity_with_ranges(ctx, &ranges)` → equity against predicted ranges\\\n7. **Gen 3 decides**: `gen3.get_action_with_equity(ctx, &equity)` → final action\\\n\\\n### Range Narrowing (updated 2026-07-02)\\\n\\\nThree layers of range narrowing:\\\n\\\n**Layer 1 — Preflop action initialization**: The S-curve center is set directly from the relevant preflop stat:\\\n- PreflopRaised → center = PFR\\\n- PreflopThreeBet → center = threebet_freq × 0.7\\\n- PreflopCalledRaise → center = VPIP × 0.60\\\n- Limp/None → center = strong_hands_fraction (VPIP-based)\\\n\\\n**Layer 2 — Postflop action narrowing**: Per-street S-curve multiplier based on action type:\\\n- Bet (donk from aggressor on flop) → center capped at 0.20\\\n- Bet (normal) → center = be\"],[1,\"EM2 Distribution Matrix\\\n\\\n- **Format**: 10 metrics × 8 bins × 169×169 pairs × f32 = ~8.7 MB binary (`data/preflop_equity_matrix.bin`)\\\n- **10 metrics**: equity, hs, ppot, npot, nutpot, rpot, draw, board, status, hs_random\\\n- **Generation**: Full C(48,3)×C(45,2) enumeration per pair, rayon parallelized (~12 min on 32 cores)\\\n- **Binary**: `holdem_bots/src/bin/gen_preflop_equity.rs`\\\n- **API**: `load_or_generate()`, `equity_vs_vpip_range()`, `ppot/npot/nutpot_vs_vpip_range()`, `quartiles_vs_vpip_range()`\\\n\\\n### Decision Logic (g47_equity_preflop)\\\n\\\n**First-in (open raise):**\\\n- `open_vpip = vpip × open_vpip_factor` (tightened calling range)\\\n- `score = equity + ppot_open_bonus × ppot`\\\n- `thresh = position_threshold / looseness` (linear looseness OK for opening)\\\n\\\n**Facing a raise (reraise/call/fold):**\\\n- **Raiser range**: `PFR / (1 + sizing_narrow_strength × (raise_ratio - 1))` — bigger raises get tighter ranges\\\n- **Reraise**: `score = equity + nutpot_reraise_bonus × nutpot` vs `equity_reraise / looseness^power`\\\n  - **Hard floor**: `min_reraise_equity = 0.42` — never 3-bet with raw equity below 42%\\\n  - **Milder looseness**: `reraise_looseness_power = 0.3` — looseness^0.3 (not linear)\\\n- **Call**: uses **wider range** (`vpip × raise_range_factor`) than reraise, since calling just sees a flop\\\n  - `call_equity = equity + ppot_call_bonus × ppot - npot_call_penalty × npot` vs pot odds × call_safety_margin\\\n\\\n### Tuned Parameters (2-round sweep, 2026-07-05/06)\\\n\\\n| Parameter | Value | Source |\\\n|---|---|---|\\\n| `equity_open_sb_steal` | 0.28 | R2a (+10.87 combined) |\\\n| `nutpo\"],[0,\"t_\"],[-1,\"f\"],[0,\"re\"],[-1,\"q\\\n- RaisedBet → center = raise_freq × 0.7\\\n- CalledBet → center = call_freq\\\n- Checked → center = 0.70 (minimal narrowing)\\\n\\\n**Layer 3 — Multiplicative widening + bet sizing** (new 2026-07-02):\\\n- `center *= narrow_center_mult` (default 1.5, scales with center)\\\n- If `bet_size_ratio > 0.67` (overbet): `center /= 1 + narrow_sizing_strength * (ratio - 0.67)`\\\n\\\nThis replaces the old static `narrow_center_offset` (additive, flat). The multiplicative factor ensures tight ranges stay tight while wide ranges get proportional slack. Bet sizing awareness prevents the bot from treating a 1.76x pot overbet the same as a standard raise.\\\n\\\n### KK Overbet Bug\"],[1,\"raise_bonus` | 0.5 | R2a (+9.38) |\\\n| `raise_range_factor` | 0.40 | R2a (+9.79) |\\\n| `call_safety_margin` | 1.10 | R2a (+9.43) |\\\n| `vpip_looseness_max` | 1.3 | R2a (+8.10) |\\\n| `equity_open_mid` | 0.46 | R2a (+7.30) |\\\n| `equity_reraise` | 0.75 | R2a (+7.23) |\\\n| `equity_open_late` | 0.36 | R2a (+6.70) |\\\n| `open_vpip_factor` | 0.90 | R2a (+6.26) |\\\n| `sizing_narrow_strength` | 0.3 | R2b (+0.43) |\\\n| `reraise_looseness_power` | 0.3 | R2b (+0.98) |\\\n| `min_reraise_equity` | 0.42 | R2b (confirmed optimal) |\\\n| `ppot_call_bonus` | 0.15 | Default |\\\n| `default_pfr` | 0.15 | Default |\\\n\\\n### Performance (2026-07-06 benchmark)\\\n\\\n| Field | G47 BB/hand | G45 opponents avg |\\\n|---|---|---|\\\n| G45-heavy (3×G45 + 2×Flock) | **+10.63** | -0.78 |\\\n| Flock-heavy (1×G45 + 4×Flock) | **+10.63** | +1.30 |\\\n| Mixed (2×Chump + 2×G45 + 1×Flock) | **+248** | chumps crushed |\\\n\\\n### Live Play\"],[0,\" Fix\"],[1,\"es\"],[0,\" (20\"]],\"start1\":620,\"start2\":620,\"length1\":2860,\"length2\":2875},{\"diffs\":[[0,\"07-0\"],[-1,\"2)\\\n\\\n**Root cause**: The old additive `narrow_center_offset=0.20` widened tight raise ranges excessively (center 0.07 → 0.27), and bet sizing was completely ignored. A 1.76x pot overbet got the same range treatment as a min-raise.\\\n\\\n**Fix**: \\\n1. Replaced `narrow_center_offset` (additive) with `narrow_center_mult` (multiplicative, default 1.5)\\\n2. Added `narrow_sizing_strength` (default 0.8) for overbet-aware tightening\\\n3. Fixed Bet vs RaisedBet classification (now tracks prior bets by other players)\\\n4. Fixed replay engine `actions_this_hand` (was always empty)\\\n5. Result: KK facing 1.76x pot overbet on [2-A-2] now correctly folds (hs=0.058 vs 0.596 before fix)\\\n\\\n### The Observer\\\n\\\nTracks per-opponent statistics using **exponential moving averages** (EMA):\\\n\\\n```rust\\\nstruct PlayerStats {\\\n    vpip: EmaStat,       // Voluntary preflop entry rate\\\n    pfr: EmaStat,        // Preflop raise rate\\\n    af: EmaStat,         // Postflop aggression factor (bet+raise / call)\\\n    fold_to_bet: EmaStat,\\\n    fold_to_cbet: EmaStat,\\\n    cbet_freq: EmaStat,  // Continuation bet frequency\\\n    threebet: EmaStat,   // 3-bet frequency\\\n    wtsd: EmaStat,       // Went-to-showdown rate\\\n    bet_freq: EmaStat,   // First-in postflop bet rate\\\n    raise_freq: EmaStat, // Facing-bet raise rate\\\n    call_freq: EmaStat,  // Facing-bet call rate\\\n    hands_observed: usize,\\\n}\\\n```\\\n\\\n### Three-Tier Blend\\\n\\\n- **<5 hands observed**: pure table aggregate\\\n- **5-50 hands**: linear blend (gradually trust player-specific data)\\\n- **≥50 hands**: pure player stats\\\n\\\n### Profile Persistence\\\n\\\nProfiles saved to `profiles/{format}_{sb}_{bb}/` (table + per-player JSON). Loaded on startup via `update_table_info()` and `on_hand_start()`. Survives server restarts.\\\n\\\n## Hand Replay System (new 2026-07-02)\\\n\\\nThe `hand_replay` tool now supports **opponent model annotations** for pre-seeding the observer with synthetic stats:\\\n\\\n```\\\n# Opponent model: PlayerName vpip=0.48 pfr=0.15 af=1.0 raise_freq=0.13 hands=55\\\n# Table model: vpip=0.59 pfr=0.29 af=0.82 fold_to_bet=0.64 hands=55\\\n```\\\n\\\nThe runner writes forged profile JSON to the profiles directory (with backup/restore guard), the bot loads them on init, and adaptive range narrowing activates even for a single replayed hand. This enables:\\\n- Testing bot decisions against described opponent types (Harrington hands)\\\n- Regression testing with warm observer data\\\n- Verifying fixes against specific historical hands\\\n\\\n## Configs\\\n\\\n| Config | Chain | Purpose |\\\n|---|---|---|\\\n| `cash_nl_g45.toml` | ke_hu → short_stack → g45_playability → g4_adaptive_postflop | **Live production** (G4.5) |\\\n| `cash_nl_g4_live.toml` | ke_hu → short_stack → g4_adaptive_preflop → g4_adaptive_postflop | Previous production |\\\n| `cash_nl_g4.toml` | same chain, default thresholds (narrow disabled) | Gen 4 baseline |\\\n| `cash_nl_g4_loose.toml` | same chain, looser thresholds | Calling-station tables |\\\n| `cash_nl_g4_tight.toml` | same chain, tighter thresholds | Skilled tables |\\\n\\\n### Current Range Narrowing Parameters (2026-07-02)\\\n\\\n| Parameter | Value | Description |\\\n|---|---|---|\\\n| `narrow_center_mult` | 1.5-1.6 | Multiplicative widening (replaces old additive offset) |\\\n| `narrow_sizing_strength` | 0.8 | Overbet sensitivity (>2/3 pot → tighter) |\\\n| `scurve_steepness_mult` | 0.3 | S-curve steepness multiplier |\\\n| `narrow_strength_mult` | 1.0 | Narrowing steepness multiplier |\\\n| `narrow_floor` | 0.02 | Minimum weight for narrowed hands |\\\n\\\n## Key Files\\\n\\\n| File | Content |\\\n|---|---|\\\n| `holdem_bots/src/gen4/adaptive.rs` | AdaptivePostflopGen4 strategy (external event feed, bet_size_ratio) |\\\n| `holdem_bots/src/gen4/adaptive_preflop.rs` | AdaptivePreflopGen4 (looseness wired) |\\\n| `holdem_bots/src/gen4/playability_preflop.rs` | PlayabilityPreflop (flop-playability promotion) |\\\n| `holdem_bots/src/gen4/observer.rs` | Observer (EMA tracking, event callbacks) |\\\n| `holdem_bots/src/gen4/profile.rs` | PlayerStats, PlayerProfile, TableProfile, three-tier blend |\\\n| `holdem_bots/src/gen4/range_builder.rs` | Range construction (multiplicative center + bet sizing narrowing) |\\\n| `holdem_bots/src/gen4/storage.rs` | ProfileStore (JSON persistence) |\\\n| `hand_replay/src/parser.rs` | Hand parser with opponent model annotations |\\\n| `hand_replay/src/runner.rs` | Runner with forged profile injection |\\\n| `hand_replay/src/replay.rs` | Replay engine with actions_this_hand tracking |\\\n\\\n## Open Issues\\\n\\\n1. **Parameter sweep needed**: `narrow_center_mult` (1.5) and `narrow_sizing_strength` (0.8) are heuristic defaults — need overnight sweep validation.\\\n\\\n2. **Passive postflop play**: Bot still under-bets first-in postflop situations. Gen 5 (learned betting frequencies) will address this.\\\n\\\n3. **Range prediction accuracy**: 88% hit rate. Extreme opponents still cause misses. Gen 5 learned range model may improve this.\\\n\\\n## Where This Goes Next\\\n\\\nGen 5 replaces the heuristic decision logic with a **neural network** trained via imitation learning (DAgger) from Gen 4.5 teacher decisions, then RL fine-tuning. The observer continues to provide input features.\"],[1,\"5)\\\n\\\nFive regression tests in `hand_replay/tests/data/` — all pass:\\\n\\\n| Test | Bug (G45/G47 old) | Fix | G47 Decision |\\\n|---|---|---|---|\\\n| JJ facing 4-bet shove | Folded (G45) | G47 equity logic | **Call** |\\\n| 55 BTN 10 BB | Folded (short-stack blocked) | `consider_fold_as_not_applicable=true` | **Raise 143/214** |\\\n| AJo 10 BB facing 3-bet | Folded (G45) | G47 equity logic | **AllIn** |\\\n| 62s BTN facing raise | 3-bet (G47 loose table) | min_reraise_equity + PFR range | **Call** (not raise) |\\\n| K4o facing 170 BB 3-bet | AllIn (G47) | sizing_narrow_strength + PFR range | **Fold** |\\\n\\\n### Observer Lifecycle Fix (2026-07-06)\\\n\\\n**Bug**: `on_hand_end()` was called from `showdown_event()`, which only fires on showdown hands (minority) and multi-counts per revealed player. VPIP/PFR adaptation was effectively disabled.\\\n\\\n**Fix**: Moved to proper `on_hand_end` trait override. Now fires on every hand, once.\\\n\\\n## Postflop Maneuver\\\n\\\n### Adaptive Postflop (`g4_adaptive_postflop`) — Unchanged from G4.5\\\n\\\n**Decision flow:**\\\n1. **External event feed** (live server): observer receives events via Strategy trait methods\\\n2. **Check observer warmth**: if `table.hands_observed < 5`, fall back to Gen 3 defaults\\\n3. **For each opponent seat**: build weighted `HandRange` from observed stats + this hand's actions + bet sizing\\\n4. **Build overridden Gen3Config** with table-adaptive thresholds\\\n5. **Delegate to Gen 3**: equity against predicted ranges → final action\\\n\\\n### Range Narrowing (from G4.5)\\\n\\\nThree layers: preflop action initialization → postflop action narrowing → multiplicative widening + bet sizing.\\\n\\\n| Parameter | Value | Description |\\\n|---|---|---|\\\n| `narrow_center_mult` | 1.5 | Multiplicative widening |\\\n| `narrow_sizing_strength` | 0.8 | Overbet sensitivity (>2/3 pot → tighter) |\\\n| `scurve_steepness_mult` | 0.3 | S-curve steepness multiplier |\\\n\\\n## Engine Crash Fix (rusty-marvin, 2026-07-05)\\\n\\\n**Bug**: `complete_hand()` dropped the HandRunner (with all seats) on `run_to_completion()` error. The next `start_hand()` saw 0 active players → `NotEnoughPlayers` → **silent simulation termination**.\\\n\\\n**Fix**: \\\n1. Error path now restores seats from runner via `into_seats()` \\\n2. Wagered chips refunded before `reset_for_new_hand()` (prevents chip destruction)\\\n3. `NotEnoughPlayers` logs a warning with hand count\\\n\\\nRoot cause of the crash: a bot returning `RaiseBelowMinimum(0, 60)` (chump_bot raising to 0). Occurs ~2 per 5000 hands.\\\n\\\n## Configs\\\n\\\n| Config | Chain | Purpose |\\\n|---|---|---|\\\n| `cash_nl_g47.toml` | ke_hu → **g47_equity_preflop** → g4_adaptive_postflop | **Live production** (G4.7) |\\\n| `cash_nl_g45.toml` | ke_hu → short_stack → g45_playability → g4_adaptive_postflop | Previous production (G4.5, fallback) |\\\n| `cash_nl_g4_live.toml` | ke_hu → short_stack → g4_adaptive_preflop → g4_adaptive_postflop | Previous production |\\\n| `cash_nl_g4.toml` | same chain, default thresholds (narrow disabled) | Gen 4 baseline |\\\n\\\nLive server default: `cash_nl_g47` (in `configs/live_bots.toml`).\\\n\\\n## Key Files\\\n\\\n| File | Content |\\\n|---|---|\\\n| `holdem_bots/src/gen4/equity_preflop.rs` | **G47 strategy** — PEM2 equity decisions, PFR ranges, sizing narrowing |\\\n| `holdem_bots/src/gen4/preflop_equity_matrix.rs` | PEM2 matrix — load, generate, equity/ppot/npot lookups |\\\n| `holdem_bots/src/bin/gen_preflop_equity.rs` | Standalone matrix generation binary |\\\n| `data/preflop_equity_matrix.bin` | PEM2 binary data (~8.7 MB, gitignored) |\\\n| `holdem_bots/src/gen4/adaptive.rs` | AdaptivePostflopGen4 strategy (unchanged from G4.5) |\\\n| `holdem_bots/src/gen4/observer.rs` | Observer (EMA tracking, event callbacks) |\\\n| `holdem_bots/src/gen4/profile.rs` | PlayerStats, PlayerProfile, three-tier blend |\\\n| `holdem_bots/src/gen4/range_builder.rs` | Range construction (multiplicative center + bet sizing) |\\\n| `hand_replay/tests/data/*.txt` | 5 live-play regression test hands |\\\n| `scripts/sweep_g47_round2.sh` | Multi-field parameter sweep script |\\\n| `scripts/sweep_g47_round2b.sh` | Structural parameter sweep script |\\\n\\\n## Open Issues\\\n\\\n1. **Observer data in live play**: PreflopObserver uses table-level VPIP/PFR (needs 30+ hands). Per-opponent tracking exists but isn't wired into G47's equity lookup yet.\\\n\\\n2. **Chump field simulation crashes**: chump_bot returns `RaiseBelowMinimum(0, ...)` causing hand errors. Fixed by crash recovery, but root cause (chump_bot action generation) should be addressed.\\\n\\\n3. **Gen5 teacher update**: Overnight collection used Gen4 teacher. Need to update `gen5_collect_full` to use G47 as teacher for future data.\\\n\\\n4. **Postflop unchanged**: G47 only changes preflop. Postflop still uses Gen 4 adaptive (Gen 3 equity). Gen 5 will eventually replace postflop.\\\n\\\n## Where This Goes Next\\\n\\\n- **Gen5 training**: 3.79M transitions collected overnight. Train with G47 preflop teacher + G4 postflop teacher.\\\n- **Live monitoring**: G47 debug logging (`epf_debug`) available for per-decision equity analysis. Currently disabled in production (set in sweep configs only).\\\n- **Postflop equity matrix**: Extend PEM2 approach to postflop — precompute equity distributions for common board textures.\\\n\"]],\"start1\":3498,\"start2\":3498,\"length1\":5042,\"length2\":5110}]"
metadata_diff: {"new":{},"deleted":[]}
encryption_cipher_text: 
encryption_applied: 0
updated_time: 2026-07-06T06:18:07.436Z
created_time: 2026-07-06T06:18:07.436Z
type_: 13