Live Integration — Server & Userscript Architecture

# Live Integration — Server & Userscript Architecture

> **Updated 2026-07-17.** Production: G48 live bot on Torn City poker via TamperMonkey userscript → live_server.

## Goal

Connect super-marvin bots to real poker sites. Current production target: **Torn City** poker page (browser → TamperMonkey). Architecture is site-agnostic: the server never contains site-specific parsing.

## Repository split (confirmed)

| Repo | IDE | Role |
|------|-----|------|
| `super_marvin` (RustRover) | Rust | `poker_protocol` + `live_server` workspace crates |
| `super-marvin-userscripts` (WebStorm) | JS/TS | npm-workspace monorepo: `shared/` + one folder per site (`torn/`) |
| `super-marvin-scrapers-native` (RustRover) | Rust | **Deferred** — native desktop scrapers |

## Transport: event stream (HTTP JSON)

The scraper streams discrete `TableEvent`s; the server keeps a per-table state machine and returns the decision inline on `waiting_for_action`.

```
TamperMonkey (browser)            native client (future)
   torn/*.user.js
        │  POST /api/event(s)           │
        ▼                               ▼
   ┌─────────────────────────────────────────────┐
   │  super_marvin / live_server (Axum)          │
   │  event → TableState (GameInfo) → bot.act()   │
   │            ▼                                │
   │   holdem_bots engine (auto-selected bot)    │
   └─────────────────────────────────────────────┘
```

## Events (poker_protocol::TableEvent)

`table_profile`, `game_start`, `deal` (hole/flop/turn/river), `player_action` (check/fold/bet/raise/call/small_blind/big_blind/ante), `waiting_for_action` (triggers decision, includes `dom_snapshot` field), `win`, `showdown`, `game_over`, `table_remove`. Cards are 2-char strings.

## DOM Snapshot Debugging (added 2026-07-17)

`waiting_for_action` carries `dom_snapshot: Option<String>` — full table HTML for debugging. `player_action` carries `raw_text: Option<String>`. Server stores snapshot on `TableState`, logs to per-hand decision log as `[DOM_SNAPSHOT]`, clears after decision. Gated by `window.__marvinDomDebug` in the userscript.

## Bot auto-detection (format × limit × seats)

`configs/live_bots.toml` maps profiles → bot config files. The most specific match wins; falls back to `default`. Bands: hu (1-2), six_max (3-6), full_ring (7+). At startup every referenced bot TOML is registered with the engine via `bot_factory::create_bot`.

## super_marvin workspace crates

```
poker_protocol/          # canonical types, site-agnostic
  src/lib.rs             # Card, GameType, BotAction, DecisionResponse
  src/events.rs          # TableEvent (with dom_snapshot, raw_text), EventResponse, GameFormat, SeatBand
live_server/
  src/main.rs            # bootstrap: config, bot selection load+register, routes, serve
  src/config.rs          # LiveServerConfig (bind_addr, bots_config_path, bot_config_path, max_tables)
  src/bot_selection.rs   # BotSelectionConfig: profile resolution + register_all
  src/handlers.rs        # /health, /api/info, /api/tables, POST /api/event, /api/events
  src/session.rs         # SessionStore: table_id → Arc<Mutex<TableSession>>
  src/table_state.rs     # event → GameInfo state machine + bot lifecycle + DOM snapshot logging
  src/adapter.rs         # engine Action → BotAction
  src/error.rs           # ApiStatusError → JSON
configs/live_server.toml # gateway config (bind 0.0.0.0:8088)
configs/live_bots.toml   # bot-selection profiles (default → cash_nl_g48_live)
```

## Endpoints

| Method | Path | Behavior |
|--------|------|----------|
| GET | `/health` | `{status,tables}` |
| GET | `/api/info` | name, version, bots_configured, default_bot, max_tables |
| GET | `/api/tables` | `{count}` |
| POST | `/api/event` | single event; decision inline for `waiting_for_action` |
| POST | `/api/events` | batch; returns envelope of last event |

Run: `cargo run -p live_server` or `./target/release/live_server`.

## super-marvin-userscripts

`shared/` mirrors the event protocol (`types.ts`) + transport client (`sendEvent`/`sendEvents` via `GM_xmlhttpRequest`, `fetch` fallback). `torn/` implements real Torn DOM scraping: `collectEvents()` parses player actions, bets, blinds, community cards from the live table; `executeAction()` clicks the correct buttons. Build: `npm run build` → `torn/dist/torn.user.js`. Current version: v0.1.11.

### Userscript fixes (2026-07-17)
- Raise regex: `[\d,.]+[kKmM]?` (handles k/M suffix)
- Corrupt blinds false positive fix
- Raise tolerance: 5% (Torn adds ~$50K chip denomination regardless of raise size)
- DOM_DEBUG runtime toggle via `window.__marvinDomDebug`

## Hand History & Decision Logging

- Per-hand logs: `/tmp/super_marvin_hands/hands/` — full hand with bot decisions inline
- Decision logs: `/tmp/super_marvin_hands/decisions/` — individual bot decision with game context
- Server log: `/tmp/live_server.log` (RUST_LOG=debug for verbose event/decision logging)

## Production Status

All 4 phases complete. The system is deployed and playing live on Torn:
- **Bot**: `cash_nl_g48_live` (G47 preflop + G48 formula postflop)
- **MC samples**: 40K (live config)
- **Key config**: `behind_penalty_weight=0.15`, `narrow_board_hit_boost=3.0`, Harrington-aligned preflop sizing


id: b7492324f4d64486ba79ea00e636d2ec
parent_id: 2c8da247905946c3aa19eb4936e16323
created_time: 2026-06-15T09:05:55.871Z
updated_time: 2026-07-17T08:40:03.387Z
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: 1781514355871
user_created_time: 2026-06-15T09:05:55.871Z
user_updated_time: 2026-07-17T08:40:03.387Z
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