Bot Framework Architecture

# Bot Framework Architecture

> Updated 2026-07-17.

## Strategy Composition

All bots use the **modular strategy pattern** (ported from Java Supersonic). Strategies are composed in a tree with voting mechanisms:
- **FirstApplicable**: first strategy that's applicable wins (most common)
- **Democratic**: weighted majority vote
- **Random**: weighted random selection

A strategy is `is_applicable()` → `get_action()`. If it returns Fold/Check and `consider_fold_as_not_applicable()` is true, the chain continues to the next strategy.

## Production Config Chain (2026-07-17)

**G48 Live** (`configs/bots/cash_nl_g48_live.toml`):
```
voting_mode = "first_applicable"

1. g47_equity_preflop     → preflop: PEM2 matrix equity (zero MC, pure lookup)
                            unified score: equity + ppot_w×ppot + nutpot_w×nutpot - npot_w×npot
                            position-based thresholds, stack-depth sigmoid
                            Harrington-aligned sizing: 3BB open, 4BB premium (35%), 2BB speculative

2. g48_formula_postflop   → postflop: formula score + observer-based range equity
                            value_score = hs×(1-npot_w×npot) + nutredraw_w×nutpot×(1-hs) - behind_penalty_w×(1-hs)×(1-ppot)
                            call_threshold with multiway pot-odds cap
                            board-hit boost, drawy trap guard, per-player looseness
                            MC budget: 40,000 (divided by opp count, floor 8K)
```

**G47 Live** (`configs/bots/cash_nl_g47_live.toml`) — fallback:
```
1. g47_equity_preflop     → preflop: PEM2 matrix equity
2. g4_adaptive_postflop   → postflop: observer-driven range narrowing → Gen 3 equity
```

## G48 Key Parameters (live)

| Parameter | Value | Description |
|-----------|-------|-------------|
| `sizing_base` | 0.33 | Base bet/raise fraction of pot |
| `bet_base` | 0.08 | Minimum bet fraction (unraised pot) |
| `ppot_weight` | 0.75 | Draw bonus weight in combined score |
| `npot_weight` | 0.10 | Negative potential penalty |
| `behind_penalty_weight` | 0.15 | Continuous penalty for hands unlikely to improve when behind |
| `narrow_board_hit_boost` | 3.0 | Range narrowing: ×3 weight for board-matching hands |
| `call_base` | 0.42 | Base call threshold |
| `call_bs_weight` | 0.12 | Board-strength adjustment to call threshold |
| `raise_base_bb` | 3.0 | Preflop standard open (Harrington) |
| `raise_strong_freq` | 0.35 | 35% chance of 4BB with premiums |
| `raise_jitter` | 0.0 | No preflop jitter (avoid sizing tells) |
| MC samples | 40,000 | Divided by opponent count, floor 8,000 |

## Multiway Call Margin

```
cap = required_equity × (1 + margin)
  margin = 0.28/√opp  (non-river)   [was 0.35, relaxed 2026-07-08]
  margin = 0.15/√opp  (river)
```

Also bounded by: `required_equity × 0.5` (floor) and `required_equity + 0.15` (ceiling).

## Shared Postflop Base

Gen 2-4 postflop strategies all inherit from `rollout_postflop_base.rs` (27 methods):
- `compute_equity()` — Monte Carlo equity
- `should_call()`, `should_raise()`, `should_bet()` — threshold-based decisions
- `push_when_calling()` — AllIn guard when facing bet with deep stack
- `remaining_stack_share()` — fraction of bankroll committed

Key functions for G48:
- `build_ranges_and_equity()` in `gen4/mod.rs` — shared range-building (G45 + G48)
- `compute_equity_with_ranges()` in `rollout_postflop_gen3.rs` — MC equity vs predicted ranges

## Equity Computation

| Situation | Method |
|---|---|
| **Preflop (G47)** | PEM2 matrix lookup (zero MC) |
| **Postflop (G48)** | Monte Carlo vs observer-predicted ranges |
| Heads-up exact | Exhaustive enumeration (trie-optimized) |
| Multi-way MC | Monte Carlo with partial Fisher-Yates dealing |

### PotentialResult fields
- `hs`: hand strength (P(best hand now))
- `ppot`: positive potential (P(improve))
- `npot`: negative potential (P(decline))
- `nutpot`: nut potential
- `rpot`: robustness
- `win_prob`: EHS = hs*(1-npot) + (1-hs)*ppot

## Observer & Range Building

### Cold-start defaults (new players)
- VPIP: 0.30, PFR: 0.15, AF: 1.0, threebet: 0.05
- fold_to_bet: 0.50, cbet_freq: 0.45, wtsd: 0.10
- call_raise_freq: 0.15 (#[serde(default)] for backward compat)

### Three-tier blend
1. **Player stats** (per-seat EMA with half-life)
2. **Table stats** (aggregate EMA)
3. **Cold-start defaults** (for unknown players)

Weight transitions from table → player as `hands_observed` grows (10-hand blend start, 100-hand full weight).

### Range narrowing
`build_opponent_range_v2()` uses:
- VPIP/PFR/threebet/call_raise_freq for preflop range width
- In-hand actions (bet, raise, call, check) for postflop narrowing
- Bet-sizing ratio for additional tightening
- Position for range width adjustment
- **Board-hit boost** (added 2026-07-16): hands matching top board card get ×3 weight

## Config System

Bot configs are TOML files in `configs/bots/`:
```toml
[bot]
name = "cash_nl_g48_live"
voting_mode = "first_applicable"

[[strategies]]
type = "g47_equity_preflop"

[[strategies]]
type = "g48_formula_postflop"

[strategies.config]
mc_samples = 40000
sizing_base = 0.33
behind_penalty_weight = 0.15
narrow_board_hit_boost = 3.0
raise_base_bb = 3.0
raise_strong_freq = 0.35
# ... more parameters
```

## Workspace Structure

```
super_marvin/
├── holdem_bots/          # Bot strategies (Gen 1-5)
├── holdem_gui/           # Web-based sweep tool
├── live_server/          # Axum gateway for live play
├── poker_protocol/       # Wire types (BotAction, TableEvent with dom_snapshot)
├── hand_replay/          # Replay engine + regression tests (40+ test hands)
├── configs/bots/         # Bot configuration files
├── scripts/              # Sweep/tournament/sanity/gen5 collection scripts
├── profiles/             # Persisted opponent profiles (gitignored)
└── data/                 # PEM2 matrix binary (gitignored)
```

## Userscript (separate project)

```
/home/jan/WebstormProjects/super-marvin-userscripts/
├── torn/                 # Torn-specific script
│   ├── src/              # TypeScript source
│   ├── dist/             # Built output (torn.user.js)
│   └── meta.json         # Version (v0.1.11)
└── shared/               # Shared HTTP client
```


id: 659da65d964344fc89e6b4eafac0098c
parent_id: 2c8da247905946c3aa19eb4936e16323
created_time: 2026-05-31T10:37:02.827Z
updated_time: 2026-07-17T08:43:42.574Z
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: 1780223822827
user_created_time: 2026-05-31T10:37:02.827Z
user_updated_time: 2026-07-17T08:43:42.574Z
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