Gen 3 Pre-Sweep Analysis — Strategy, Structure, Tests, Performance, Multi-Way

# Gen 3 Pre-Sweep Comprehensive Analysis

## 1. Strategy Issues

### 1a. Threshold Scale Mismatch (PARTIALLY ADDRESSED)
All thresholds calibrated for full-deck hs (0.3–0.9 scale) but Gen 3 uses range-weighted hs (0.2–0.6 scale).

**Status:** The OR-chain refactoring to weighted scoring (round 2) partially addresses this — `should_call`/`should_raise` now use dynamic threshold adjustments rather than fixed thresholds. However, the base thresholds (`call_base=0.75`, `raise_base=0.93`) are still calibrated for full-deck hs and may need downward adjustment. The round 2 sweep tests `call_base` at 0.60/0.75/0.85 and `raise_base` at 0.86/0.93/0.97.

### 1b. Non-deterministic RNG ✅ FIXED
Check-raise and rope-a-dope now use `decision_rng()` — deterministic `StdRng` seeded from `mc_seed + win_prob.to_bits() + pot_size + num_active`. The `sweep_mode` config flag (default `true`) controls this: `true` = fully deterministic for sweeps, `false` = mixes in system entropy for live play.

### 1c. Raise OR-chain has too many bypass paths ✅ FIXED
Replaced with `should_raise()` weighted decision. Base threshold from `raise_base` is dynamically lowered by draw quality (`raise_w_draw`) and bluff equity (`raise_w_bluff`). Made hands can now raise (previously only draws raised through the ppot bypass).

### 1d. No board texture awareness
Still open. No connected/monotone/paired board detection. Only crude npot threshold for "dangerous board."

### 1e. Missing Java tactics
Still open. backAlleyMug, luringBet, fakeSemiBluff, scareCardHit not ported.

## 2. Structure
- Clean module separation (range, model, potential, strategy)
- ~30 private threshold methods
- Gen3Config now has 29 sweepable parameters (was 3)
- `apply_model_modifier` extracted (was inline `/mm` bug at bet site, now `*mm` everywhere)

## 3. Test Coverage
| Component | Tests |
|-----------|-------|
| hand_range.rs | 14 |
| player_model.rs | 6 |
| hand_potential.rs (range) | 24 |
| rollout_postflop_gen3.rs | 13 |
| **Total** | **340** |

## 4. Performance

### Current single-opponent (flop)
Exhaustive enumeration with trie. ~5ms/action.

### Monte Carlo multi-way (implemented, optimized)
Uses sparse precomputed pair lists (`nonzero_pairs_by_cards` + `sample_sparse`) and presence mask (`present[]`) instead of per-sample card removal.

| Opponents | M=20K | M=100K |
|-----------|-------|--------|
| 1 | 0.3ms (exhaustive used instead) | — |
| 5 | ~1.5ms | ~15ms |
| 9 | ~2.5ms | ~25ms |

### Optimizations Applied (round 2)
- `sample_sparse`: iterates precomputed sparse pair list O(k) instead of O(n²) per sample
- `nonzero_pairs_by_cards`: precomputed once per equity computation, shared across all MC samples
- `present[]` mask: O(1) card removal vs old O(n) swap_remove
- `select_nth_unstable_by`: O(n) partial sort replaces O(n log n) full sort in `init_strong_hands`
- `is_oesd`: fixed `[u8; 6]` stack array replaces heap `Vec` allocation
- `compute_vs_range_multiway_shared`: avoids cloning 2704-entry HandRange once per opponent
- `PotentialResult::fallback()`: shared fallback instead of duplicated struct literals
- `board_completion_count`: extracted `const fn` (was triplicated match block)

## 5. Parameter Defaults Analysis

### strong_hands_fraction = 0.20
Defensible as population average. Range weighting via MC importance sampling.

### bluff_factor = 2.0
Sound. Near-balanced for pot-sized bet.

### draw_call_ppot_thresh = 0.22
Appropriate. Sweep tests 0.15/0.22/0.32.

### New weighted-decision defaults
| Parameter | Default | Role |
|-----------|---------|------|
| `call_w_draw` | 0.8 | How much strong draws lower call threshold |
| `call_w_pot_odds` | 0.5 | How much favorable pot odds lower call threshold |
| `call_w_commitment` | 0.3 | How much stack commitment lowers call threshold |
| `raise_w_draw` | 0.5 | How much strong draws lower raise threshold |
| `raise_w_bluff` | 0.3 | How much bluff equity lowers raise threshold |
| `range_draw_weight` | 1.0 | Draw weight in opponent range (1.0 = equal to made hands) |

## 6. Range Estimation from Opponent Actions

**Infrastructure exists** but **not yet implemented**:
- HandRange supports `weighted_union`, `nonzero_pairs_by_cards`
- StrategyContext has action recorder
- Currently uses static range (same for all opponents)

**Next:** RangePredictor — per-seat HandRange[], lazily initialized from action patterns.

## 7. Multi-Way Rollout — Implemented

- **Exhaustive (HU only):** `compute_vs_range` — exact, trie-optimized
- **Monte Carlo (multi-way):** `compute_vs_range_multiway_shared` — range-weighted importance sampling
- Range weighting = importance weights (zero extra cost)
- ppot/npot extend naturally: classify vs strongest opponent at each point

## 8. Gen 2 Multi-Way Applicability
Gen 2 still uses exhaustive `hp.compute()` (uniform opponent hands, no range). Could benefit from MC for multi-way pots. Not yet migrated.

## Round 2 Sweep Status (2026-06-14)
- 16 params × 3 values × 4 seeds = 192 runs
- ~82s per run, ~4.5h total
- Early results: most parameters show small but non-zero sensitivity
- `call_w_pot_odds` at 0.75 shows strongest positive swing
- Gen3 currently underperforms Gen2 in 5v5 10-seat format — may need table composition adjustment

## Recommended Priority (updated)
1. ✅ ~~Add strategy tests~~ — 13 tests added
2. ✅ ~~Parameter sweep~~ — round 2 running
3. ✅ ~~Monte Carlo equity engine~~ — implemented + optimized
4. RangePredictor (per-opponent range tracking)
5. Board texture awareness
6. Port missing tactics

id: 3bd32147b8e0490f98ebb0e6cb400c2b
parent_id: 1246bbc3bb4948fc8329079b84b4ae3d
created_time: 2026-06-12T17:37:51.732Z
updated_time: 2026-06-22T15:54:32.737Z
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: 1781285871732
user_created_time: 2026-06-12T17:37:51.732Z
user_updated_time: 2026-06-14T18:26:57.523Z
encryption_cipher_text: 
encryption_applied: 0
markup_language: 1
is_shared: 0
share_id: 
conflict_original_id: 
master_key_id: 
user_data: 
deleted_time: 1782143672737
type_: 1