id: 681e830910c94cb8ad852497ac082b14
parent_id: 
item_type: 1
item_id: f7657197c0e74a5cb5f6761eee89ff38
item_updated_time: 1784277466697
title_diff: "[{\"diffs\":[[1,\"Gen 4.8 — Formula-Based Postflop (Design)\"]],\"start1\":0,\"start2\":0,\"length1\":0,\"length2\":41}]"
body_diff: "[{\"diffs\":[[1,\"# Gen 4.8 — Formula-Based Postflop\\\n\\\n## Motivation\\\n\\\nGen 3/4 postflop uses **80+ parameters** in a categorical decision tree with 7 first-in branches and 5 raised-pot branches. Each metric (hs, ppot, npot, nutpot, rpot) gets its own side-channel through boolean checks (`is_nut_draw`, `draw_quality > thresh`) and separate OR-chain logic. The primary signal is `hs` for bets/raises and `win_prob` for calls, with ppot handled as categorical adjustments.\\\n\\\nGen 4.8 replaces this with a **unified score formula** + **threshold comparisons**, mirroring the approach that works for G47 preflop. Target: ~16 parameters, no categorical branches, same mathematical foundation.\\\n\\\n## Current Architecture (Gen 3/4)\\\n\\\n### First-in decision tree (7 branches, priority order)\\\n\\\n| # | Branch | Signal | Key threshold |\\\n|---|--------|--------|--------------|\\\n| 1 | Check-raise trap | `hs` | cr_flop=0.95, cr_turn=0.93 |\\\n| 2 | Raise-bluff CR | ppot, model | (flop HU only) |\\\n| 3 | Conti-bet | fold_eq, ppot | conti_fold_thresh |\\\n| 4 | Semi-bluff | `hs`, ppot | semi_bluff_hs, semi_bluff_ppot |\\\n| 5 | Value bet | `hs` | bet_flop=0.15, bet_turn=0.40, bet_river=0.47 |\\\n| 6 | Bluff bet | `win_prob` | bluff_range from fold equity |\\\n| 7 | Check | — | default |\\\n\\\n### Raised-pot decision tree (5 branches)\\\n\\\n| # | Branch | Signal | Key threshold |\\\n|---|--------|--------|--------------|\\\n| 1 | Rope-a-dope | `hs` | rd_flop=0.95, rd_turn=0.93 |\\\n| 2 | Semi-bluff raise | `hs`, ppot | same as first-in |\\\n| 3 | Raise | `hs` | raise_base=0.80 + adjustments |\\\n| 4 | Call | `win_prob` | call_base=0.40 + adjustments |\\\n| 5 | Fold | — | default |\\\n\\\n### Key observation\\\n\\\nThe system already uses **two different signals**:\\\n- `hs` (raw hand strength) for **bets and raises** — asserting \\\"I'm ahead NOW\\\"\\\n- `win_prob` (EHS = `hs×(1-npot) + (1-hs)×ppot`) for **calls** — \\\"I have equity to continue\\\"\\\n\\\nThis is sound: betting asserts current strength, calling allows for future improvement. But ppot, nutpot, and rpot are handled through categorical side-channels instead of continuous formulas.\\\n\\\n## Gen 4.8 Design\\\n\\\n### Composite scores\\\n\\\nThree scores replace the categorical metrics:\\\n\\\n#### 1. value_score — for betting/raising (asserting strength)\\\n\\\n```\\\nvalue_score = hs × (1 − npot × npot_weight) + nutpot × nutredraw_weight × (1 − hs)\\\n```\\\n\\\n- **hs × (1 − npot × w)**: current strength, discounted by vulnerability\\\n- **nutpot × w × (1 − hs)**: small bonus for nut redraws even when behind\\\n- High for: top pair on dry board, sets, straights\\\n- Moderate for: vulnerable top pair on wet board (npot discount)\\\n- Low for: air, weak draws\\\n\\\n#### 2. draw_score — for semi-bluffing and calling with draws\\\n\\\n```\\\ndraw_quality = nutpot / (nutpot + rpot + ε)\\\ndraw_score = ppot × draw_quality × ppot_weight\\\n```\\\n\\\n- **ppot × quality**: draw value, weighted by how likely the draw is to the nuts\\\n- High for: nut flush draw (high ppot, high nutpot, low rpot)\\\n- Moderate for: non-nut flush draw (high ppot, moderate rpot)\\\n- Low for: gutshot, backdoor draws\\\n- Zero for: made hands with no improvement potential\\\n\\\n#### 3. fold_equity — from opponent model (already computed)\\\n\\\nUnchanged from current implementation: `opp_max_hs`, `opp_max_ppot`, `all_fold_to_bet`.\\\n\\\n### Unified decision: first-in\\\n\\\n```\\\ncombined = value_score + draw_score\\\n\\\n// 1. Trap? (mixed strategy with randomness)\\\ntrap_prob = clamp(\\\n    (value_score − trap_gate)         // only with monsters\\\n    × opp_aggression                   // aggressive opponents bet for us\\\n    × (1 − street_progress²)          // trap more on flop/turn\\\n    × trap_random_scale               // tunable mixing\\\n)\\\nif value_score >= trap_gate && rng() < trap_prob → CHECK (trap)\\\n\\\n// 2. Value bet or semi-bluff?\\\nbet_thresh = bet_base + (bet_river_base − bet_base) × street_progress²\\\nbet_thresh = bet_thresh / opp_looseness\\\n\\\nif combined >= bet_thresh → BET\\\n    sizing = sizing_base + (value_score − bet_thresh) × sizing_strength_mult\\\n    sizing = clamp(sizing, sizing_min, sizing_max)\\\n\\\n// 3. Pure bluff?\\\nbluff_fold_eq = all_fold_to_bet × fold_freq_mult\\\nif value_score <= bluff_hs_max && bluff_fold_eq >= bluff_fold_min → BET (bluff sizing)\\\n\\\n// 4. Check\\\n→ CHECK\\\n```\\\n\\\n**What disappears:**\\\n- No separate CONTI_BET logic — emerges from bet_thresh + fold_equity\\\n- No separate SEMI_BLUFF logic — draw_score in combined handles it\\\n- No separate VALUE_BET vs BLUFF_BET — score threshold handles it\\\n- No separate check_raise vs rope_a_dope — unified trap formula\\\n\\\n### Unified decision: facing bet\\\n\\\n```\\\ncombined = value_score + draw_score\\\n\\\n// 1. Raise?\\\nraise_thresh = raise_base / opp_looseness\\\nraise_thresh += aggression_penalty(num_raises, opp_wagerers, npot)\\\n\\\nif combined >= raise_thresh → RAISE\\\n    sizing = raise_sizing_base + (combined − raise_thresh) × raise_sizing_mult\\\n\\\n// 2. Call?\\\ncall_thresh = call_base + call_bs_weight × bet_size_ratio\\\ncall_thresh = call_thresh / opp_looseness\\\ncall_thresh = min(call_thresh, pot_odds × (1 + multiway_margin))  // multiway cap\\\n\\\nif combined >= call_thresh → CALL\\\n\\\n// 3. Fold\\\n→ FOLD\\\n```\\\n\\\n**What disappears:**\\\n- No separate should_call vs should_raise — unified threshold comparison\\\n- No separate call_w_draw, call_w_pot_odds, call_w_commitment — all in the score\\\n- No separate raise_w_draw, raise_w_bluff, raise_w_protection — all in the score\\\n- No HU vs full-ring split — opp_looseness and multiway_margin handle it\\\n\\\n### The check-call / check-call / check-raise line\\\n\\\nNo explicit trap state tracking needed:\\\n\\\n| Street | Situation | What happens |\\\n|--------|-----------|-------------|\\\n| Flop | First-in, monster | `trap_prob > rng()` → CHECK (trap) |\\\n| Turn | Facing bet | `combined` very high → CALL (above call_thresh, raise suppressed by slow_play_mult if trapping) |\\\n| River | Facing bet | `combined` still very high, slow_play_mult drops to 1.0 → RAISE (check-raise!) |\\\n\\\nThe **slow_play_mult** parameter controls check-call vs check-raise on earlier streets:\\\n\\\n```\\\nif is_trapping && street < River:\\\n    raise_thresh *= slow_play_mult    // e.g. 1.4 → harder to raise, more calls\\\n```\\\n\\\nOn the river, `slow_play_mult = 1.0` → raises fire normally → natural check-raise.\\\n\\\n### Bet sizing (formula replaces 5+ parameters)\\\n\\\n```\\\nbet_frac = clamp(\\\n    sizing_base + (value_score − bet_thresh) × sizing_strength_mult,\\\n    sizing_min,    // minimum (protection)\\\n    sizing_max     // maximum (pot-sized)\\\n)\\\n```\\\n\\\n- Strong hands: high value_score → bigger bet (value extraction)\\\n- Draws: high draw_score but low value_score → smaller bet (semi-bluff sizing)\\\n- Bluffs: very low value_score → small bet (risk minimization)\\\n\\\n## Parameter Mapping\\\n\\\n### Score parameters (5)\\\n\\\n| Gen 4.8 parameter | Default | Replaces |\\\n|---|---|---|\\\n| `npot_weight` | 0.5 | npot_trap_ceiling, bet_flop_npot_mult, VOLATILE_NPOT_GATE |\\\n| `nutredraw_weight` | 0.3 | (new — nut redraw bonus when behind) |\\\n| `ppot_weight` | 0.5 | adj_ppot_base_weight, adj_ppot_quality_weight |\\\n| `draw_quality_epsilon` | 0.01 | draw_quality_epsilon |\\\n| `rpot` | (in draw_quality) | rpot handling via nutpot/(nutpot+rpot) |\\\n\\\n### First-in threshold parameters (6)\\\n\\\n| Gen 4.8 parameter | Default | Current equivalent |\\\n|---|---|---|\\\n| `bet_base` | 0.15 | bet_flop_base |\\\n| `bet_river_base` | 0.47 | bet_turn_base + 0.07 |\\\n| `trap_gate` | 0.85 | check_raise_flop=0.95, check_raise_turn=0.93 |\\\n| `trap_random_scale` | 1.0 | cr_chance (fold_freq × all_fold_to_bet) |\\\n| `bluff_hs_max` | 0.05 | bluff_hs_max=0.05 |\\\n| `bluff_fold_min` | 0.30 | bluff_success_floor=0.30 |\\\n\\\n### Facing-bet threshold parameters (4)\\\n\\\n| Gen 4.8 parameter | Default | Current equivalent |\\\n|---|---|---|\\\n| `raise_base` | 0.80 | raise_base=0.80 (live), raise_default=0.925 (default) |\\\n| `call_base` | 0.40 | call_base=0.40 (live) |\\\n| `call_bs_weight` | 0.25 | 0.25×bs coefficient in call formula |\\\n| `call_safety_margin` | 1.10 | (already in preflop) |\\\n\\\n### Sizing parameters (3)\\\n\\\n| Gen 4.8 parameter | Default | Current equivalent |\\\n|---|---|---|\\\n| `sizing_base` | 0.50 | bet_value_fraction=0.67, bet_bluff_fraction=0.40 |\\\n| `sizing_strength_mult` | 0.50 | (new — scales with score) |\\\n| `sizing_min` | 0.25 | semi_bluff sizing floor |\\\n\\\n### Opponent parameters (3, already exist)\\\n\\\n| Parameter | Source |\\\n|---|---|\\\n| `opp_looseness` | VPIP/PFR (same as preflop) |\\\n| `opp_aggression` | Postflop bet/raise frequency from observer |\\\n| `fold_freq_mult` | fold_freq_multiplier |\\\n\\\n**Total: ~21 parameters** (vs 80+ in Gen 3/4)\\\n\\\n## Score Calibration\\\n\\\n### Target: reproduce current behavior\\\n\\\nThe formula scores must produce decisions close to the current thresholds. Mapping:\\\n\\\n**value_score examples (with npot_weight=0.5, nutredraw_weight=0.3):**\\\n\\\n| Hand | Board | hs | ppot | npot | nutpot | rpot | value_score | draw_score | Current action |\\\n|------|-------|-----|------|------|--------|------|-------------|------------|----------------|\\\n| Top pair AK | K72 rainbow | 0.70 | 0.05 | 0.05 | 0.02 | 0.01 | 0.683 | 0.024 | BET (hs>0.15) |\\\n| Set 55 | K52 rainbow | 0.93 | 0.02 | 0.02 | 0.95 | 0.00 | 0.921 | 0.010 | TRAP (hs>0.95) |\\\n| Nut flush draw | Q♥7♥2♥ + A♥T♥ | 0.35 | 0.35 | 0.10 | 0.35 | 0.00 | 0.358 | 0.175 | SEMI_BLUFF |\\\n| Non-nut flush draw | Same + K♥T♥ | 0.35 | 0.35 | 0.10 | 0.20 | 0.15 | 0.358 | 0.097 | SEMI_BLUFF (tighter) |\\\n| Vulnerable top pair | AK on T98 | 0.60 | 0.08 | 0.25 | 0.05 | 0.10 | 0.525 | 0.020 | BET (protection) |\\\n| Complete air | 72o on K53 | 0.08 | 0.03 | 0.05 | 0.01 | 0.01 | 0.078 | 0.014 | CHECK or BLUFF |\\\n\\\n### Score-to-threshold mapping\\\n\\\n**Bet decision (first-in, flop, M=6):**\\\n\\\n| Hand type | value_score | draw_score | combined | bet_thresh | Decision |\\\n|-----------|------------|------------|----------|------------|----------|\\\n| Top pair dry | 0.683 | 0.024 | 0.707 | 0.15/1.07=0.14 | BET ✓ |\\\n| Set dry | 0.921 | 0.010 | 0.931 | 0.14 | TRAP (if opp aggressive) or BET |\\\n| Nut flush draw | 0.358 | 0.175 | 0.533 | 0.14 | BET (semi-bluff) ✓ |\\\n| Weak air | 0.078 | 0.014 | 0.092 | 0.14 | CHECK or BLUFF ✓ |\\\n\\\n**Call decision (facing half-pot bet, turn):**\\\n\\\n| Hand type | combined | call_thresh | Decision |\\\n|-----------|----------|-------------|----------|\\\n| Top pair | 0.707 | 0.40+0.25×0.5=0.525 | CALL ✓ |\\\n| Nut flush draw | 0.533 | 0.525 | CALL (marginal) ✓ |\\\n| Weak air | 0.092 | 0.525 | FOLD ✓ |\\\n\\\n**Raise decision (facing half-pot bet, turn):**\\\n\\\n| Hand type | combined | raise_thresh | Decision |\\\n|-----------|----------|-------------|----------|\\\n| Set | 0.931 | 0.80/1.07=0.748 | RAISE ✓ |\\\n| Top pair | 0.707 | 0.748 | CALL (not raise) ✓ |\\\n| Nut flush draw | 0.533 | 0.748 | CALL (not raise) ✓ |\\\n\\\n## Street Gradient\\\n\\\nLike preflop's position² gradient, postflop uses a street gradient:\\\n\\\n```\\\nstreet_progress: flop=0.0, turn=0.5, river=1.0\\\n\\\nbet_thresh = bet_base + (bet_river_base − bet_base) × street_progress²\\\n```\\\n\\\n- Flop: bet_thresh ≈ 0.15 (loose — many streets to realize equity)\\\n- Turn: bet_thresh ≈ 0.31 (tighter — one card left)\\\n- River: bet_thresh ≈ 0.47 (tightest — equity fully realized)\\\n\\\nThe `street_progress²` makes the curve convex — thresholds rise slowly through the flop, steeply approaching the river. This matches reality: the jump from flop to turn is smaller than turn to river because the turn still offers one more card.\\\n\\\n## Implementation Plan\\\n\\\n### Phase 1: Parallel implementation (feature-gated)\\\n\\\n1. New strategy type: `g48_formula_postflop`\\\n2. Register alongside `g4_adaptive_postflop`\\\n3. Shares equity computation (Gen3Equity) — only the decision logic changes\\\n4. Config: `cash_nl_g48.toml` with formula parameters\\\n5. A/B testing: run G48 vs G47 in self-play and vs G45 field\\\n\\\n### Phase 2: Test cases\\\n\\\nBuild 30-50 postflop hand replay tests:\\\n- Value betting (top pair, sets, flushes) × 3 streets\\\n- Bluffing (air on dry board, missed draws) × 2 streets\\\n- Semi-bluffing (nut flush draw, OESD) × 2 streets\\\n- Trapping (flopped set → check-call → river raise) — full multi-street sequence\\\n- Facing aggression (fold medium pair to 3-bet, call with draw + odds)\\\n- Protection betting (vulnerable top pair on wet board)\\\n- Multi-street lines (check-call flop, lead turn, bet river)\\\n\\\n### Phase 3: Parameter sweep\\\n\\\nUse the existing GUI sweep tool to tune the ~21 parameters.\\\n\\\n### Phase 4: Replacement\\\n\\\nIf G48 outperforms G47 in self-play and vs field, replace `g4_adaptive_postflop` as the production postflop.\\\n\\\n## Risk Assessment\\\n\\\n**Low risk:** Equity computation unchanged (same MC rollouts, same ranges). Only the decision logic changes.\\\n\\\n**Medium risk:** The categorical tree has been battle-tested in live play. Some edge cases (e.g., multi-way raise wars, short-stack dynamics) may not reproduce perfectly in the formula. The test suite and A/B testing will catch these.\\\n\\\n**Key advantage:** 21 parameters vs 80+ means the sweep space is tractable. The current 80-parameter system is effectively un-sweepable — many parameters interact non-linearly and can't be explored independently.\\\n\\\n## Open Questions\\\n\\\n1. **Should the call score use win_prob (EHS) instead of combined?**\\\n   - Current code uses win_prob for calls, hs for bets/raises\\\n   - The combined score includes draw_score, which may overvalue draws for calling\\\n   - Alternative: use value_score + draw_score for bets/raises, win_prob for calls\\\n   - This preserves the \\\"assert strength vs continue with equity\\\" distinction\\\n\\\n2. **How to handle multi-way adjustment?**\\\n   - Current code uses exponential power adjustments (strength^adj)\\\n   - Formula could use: `bet_thresh *= 1 + 0.1 × (num_active - 2)`\\\n   - Simpler and more predictable\\\n\\\n3. **Aggression penalty formula?**\\\n   - Current: +0.12 for raise-over-bet, +0.06 per extra raise, +0.08 volatile board\\\n   - Formula: `raise_thresh += agg_penalty_base × (1 + num_raises × 0.5) × (1 + npot × npot_weight)`\\\n\\\n4. **Short-stack handling?**\\\n   - Current code has separate OTB (short-handed) thresholds\\\n   - Formula could use: `bet_thresh /= max(1.0, 15.0 / stack_bb)` — tighter when deep, looser when short\\\n\"]],\"start1\":0,\"start2\":0,\"length1\":0,\"length2\":13781}]"
metadata_diff: {"new":{"id":"f7657197c0e74a5cb5f6761eee89ff38","parent_id":"e13f1845de9b4b6392ad866354fbd562","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":1783366244870,"user_updated_time":1783366244870,"markup_language":1,"is_shared":0,"share_id":"","conflict_original_id":"","master_key_id":"","user_data":"","deleted_time":1784277466697},"deleted":[]}
encryption_cipher_text: 
encryption_applied: 0
updated_time: 2026-07-17T08:46:47.561Z
created_time: 2026-07-17T08:46:47.561Z
type_: 13