Gen 4 — Adaptive Opponent Modelling (State of the Art)

# Gen 4 — Adaptive Opponent Modelling (State of the Art)

> **Current state, 2026-07-17.** Production strategy is **Gen 4.8** — matrix-based equity preflop (G47) + formula-based postflop (G48). The strategy chain is: `g2_ke_hu_nash` (HU only) → `g47_equity_preflop` → `g48_formula_postflop`.

## Gen 4.8 — Formula-Based Postflop + G47 Preflop (PRODUCTION)

G48 replaces the 80+ parameter Gen 3/4 categorical decision tree with a **unified score formula** + threshold comparisons (~21 parameters). Same equity computation (MC rollouts), only the decision logic changes.

| Priority | Strategy | When | Role |
|---|---|---|---|
| 1 | `g2_ke_hu_nash` | HU, 15-25 BB | Nash equilibrium (unchanged) |
| 2 | `g47_equity_preflop` | All preflop | PEM2 matrix equity + VPIP/PFR-adaptive thresholds |
| 3 | `g48_formula_postflop` | Postflop | **NEW: formula-based decisions with observer ranges** |

### G48 Unified Score Formulas

**value_score** (for betting/raising):
```
value = hs × (1 − npot_w × npot) + nutredraw_w × nutpot × (1 − hs) − behind_penalty_w × (1 − hs) × (1 − ppot)
```

**draw_score** (for semi-bluffing and calling with draws):
```
draw_quality = nutpot / (nutpot + rpot + ε)
draw_score = ppot × draw_quality × ppot_weight
```

**combined = value_score + draw_score** — single threshold comparison replaces 7 first-in branches and 5 raised-pot branches.

### G48 Key Features
- **Behind-penalty** (`behind_penalty_weight=0.15`): continuous penalty for hands unlikely to improve when behind
- **Board-hit boost** (`narrow_board_hit_boost=3.0`): range narrowing boosts hands matching top board card (×3 weight), penalizes without (×1/3)
- **Draw-heavy board trap guard**: `board_is_drawy()` guards ALL trap paths
- **Per-player looseness**: `aggressor_vpip(ctx)` keys off bettor's individual VPIP
- **Observer-based range equity**: shares range computation with G45

### G48 Harrington-Aligned Preflop Sizing (2026-07-16)
- Standard open: **3BB** (was 2.5)
- Premium: 3BB or **4BB** (35% random)
- Speculative: **2BB** flat
- Limpers: **+1BB** flat all types
- Jitter: **0%** (was ±12%)
- All raise-to amounts rounded to **whole BB** via `round_to_bb()`

## Gen 4.7 — Matrix-Based Equity Preflop

G47 replaces NG ranking-table preflop with **pure equity decisions** from a precomputed 169×169 PEM2 distribution matrix.

### PEM2 Distribution Matrix

- **Format**: 10 metrics × 8 bins × 169×169 pairs × f32 = ~8.7 MB binary (`data/preflop_equity_matrix.bin`)
- **10 metrics**: equity, hs, ppot, npot, nutpot, rpot, draw, board, status, hs_random
- **Generation**: Full C(48,3)×C(45,2) enumeration per pair, rayon parallelized
- **Binary**: `holdem_bots/src/bin/gen_preflop_equity.rs`
- **API**: `load_or_generate()`, `equity_vs_vpip_range()`, `ppot/npot/nutpot_vs_vpip_range()`, `quartiles_vs_vpip_range()`

### Decision Logic (g47_equity_preflop)

**First-in (open raise):**
- `open_vpip = vpip × open_vpip_factor`
- `score = equity + ppot_open_bonus × ppot`
- `thresh = position_threshold / looseness`

**Facing a raise (reraise/call/fold):**
- **Raiser range**: `PFR / (1 + sizing_narrow_strength × (raise_ratio - 1))`
- **Reraise**: `score = equity + nutpot_reraise_bonus × nutpot` vs `equity_reraise / looseness^power`
  - **Hard floor**: `min_reraise_equity = 0.42`
  - **Milder looseness**: `reraise_looseness_power = 0.3`
- **Call**: wider range (`vpip × raise_range_factor`), pot-odds-gated
- **AJ shove fold**: call range now narrows by bet sizing (AJs eq dropped 0.606→0.391, correctly folds all-in)

## Observer Lifecycle

Observer tracks per-opponent stats (VPIP, PFR, AF, bet frequencies) via EMA. `on_hand_end()` fires on every hand (fixed from showdown-only). Profile persistence via disk loading. Three-tier blend: observed stats → table-level → defaults.

## Recent Fixes (2026-07-16/17)
- Call-path sizing narrowing, per-opponent PFR
- Board-hit boost (KK on Ace boards)
- Behind-penalty, drawy trap guard
- RaiseBelowMinimum fix (BB rounding clamp), deep-stack overbet allin fix
- Per-player looseness (`aggressor_vpip`)
- Sanity checks passed all 11 gates

## Configs

| Config | Chain | Purpose |
|---|---|---|
| `cash_nl_g48_live.toml` | ke_hu → **g47_equity_preflop** → **g48_formula_postflop** | **Live production** |
| `cash_nl_g48.toml` | same, sim settings | Sim/test |
| `cash_nl_g47.toml` | ke_hu → **g47_equity_preflop** → g4_adaptive_postflop | G47 with old postflop (fallback) |
| `cash_nl_g45.toml` | ke_hu → short_stack → g45_playability → g4_adaptive_postflop | Previous production (G4.5) |

Live server default: `cash_nl_g48_live` (in `configs/live_bots.toml`).

## Key Files

| File | Content |
|---|---|
| `holdem_bots/src/gen4/formula_postflop.rs` | **G48 strategy** — unified score formula, value_score with behind-penalty, draw guards |
| `holdem_bots/src/gen4/equity_preflop.rs` | **G47 strategy** — PEM2 equity decisions, per-opponent PFR, call-path narrowing, Harrington sizing |
| `holdem_bots/src/gen4/preflop_equity_matrix.rs` | PEM2 matrix — load, generate, equity lookups |
| `holdem_bots/src/gen4/range_builder.rs` | Range construction with board-hit boost |
| `holdem_bots/src/gen4/adaptive.rs` | AdaptivePostflopGen4 (G45 strategy) |
| `holdem_bots/src/gen4/observer.rs` | Observer (EMA tracking, per-opponent stats) |
| `holdem_bots/src/gen4/profile.rs` | PlayerStats, PlayerProfile, three-tier blend |
| `holdem_bots/src/cash/rollout_postflop_base.rs` | Shared postflop base (raise min clamps, deep-stack allin fix) |
| `hand_replay/tests/data/{harrington,regression,synthetic}/` | 40 hand replay test hands |

## Where This Goes Next

- **Gen5 training**: G48 teacher data collection running (24.1M short-handed transitions done, full-ring in progress)
- **Postflop equity matrix**: PEM3 extension to postflop deferred by user — MC rollouts fine for live play


id: de72ff3ebcbd45689c988e50038e80b0
parent_id: e13f1845de9b4b6392ad866354fbd562
created_time: 2026-06-28T05:16:57.134Z
updated_time: 2026-07-17T08:41:30.882Z
is_conflict: 0
latitude: 0.00000000
longitude: 0.00000000
altitude: 0.0000
author: 
source_url: 
is_todo: 0
todo_due: 0
todo_completed: 0
source: joplin-desktop
source_application: net.cozic.joplin-desktop
application_data: 
order: 1782623817134
user_created_time: 2026-06-28T05:16:57.134Z
user_updated_time: 2026-07-17T08:41:30.882Z
encryption_cipher_text: 
encryption_applied: 0
markup_language: 1
is_shared: 0
share_id: 
conflict_original_id: 
master_key_id: 
user_data: 
deleted_time: 0
type_: 1