Live Integration — Server & Scrapers Architecture

# Live Integration — Server & Scrapers Architecture

**Status:** Phase 2 (server core) complete — 2026-06-15
**Design plan:** `super_marvin/.kilo/plans/live-integration-torn.md`
**Related:** [Vision — Poker AI Testbed & Live Server](joplin://b7bf3a077c064d8493373a3661adc702), [Prototype: Web Traffic Interception](joplin://6794e0bb60ae4da68720868546ddf628), [Browser Extension](joplin://c994139aac9245c79da7923dbe8579b3)

---

## Goal

Connect super-marvin bots to real poker sites/applications. First 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 (chosen)

The Java JointBot `BotServer` was event-driven (UDP multicast, XML). We modernize to **JSON over
HTTP** but keep the model: the scraper streams discrete `TableEvent`s; the server keeps a per-table
state machine and returns the decision inline on `waiting_for_action`. (A snapshot-per-decision
model was considered and rejected as less robust.)

```
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` (format/limit/seats — drives auto-detect), `game_start`, `deal` (hole/flop/turn/
river), `player_action` (check/fold/bet/raise/call/small_blind/big_blind/ante), `waiting_for_action`
(triggers decision), `win`, `showdown`, `game_over`, `table_remove`. Cards are 2-char strings.

## 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; `live_server` instantiates the selected bot per table via
`holdem_core::game::bot_factory::create_bot`.

## super_marvin additions

```
poker_protocol/          # canonical types, site-agnostic
  src/lib.rs             # Card, GameType, BotAction, DecisionResponse
  src/events.rs          # TableEvent, 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 + read_bot_name
  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 (decide on waiting_for_action)
  src/adapter.rs         # engine Action → BotAction
  src/error.rs           # ApiStatusError → JSON
configs/live_server.toml # gateway config
configs/live_bots.toml   # bot-selection profiles (default cash_nl, sng_gen2, mtt→sng_gen2)
```

`table_state.rs` is the Rust analogue of JointBot `TableManager`: it accumulates events into
running table state (seats, stacks, wagers, board, pot, current_bet, blinds, street, button/sb/bb),
drives the bot's `HoldemPlayer` lifecycle (game_start_event, hole_cards_dealt, stage_event,
action_event, act, game_over_event), and builds a `GameInfo` on each step.

## 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`.

## super-marvin-userscripts

`shared/` mirrors the event protocol (`types.ts`) + transport client (`sendEvent`/`sendEvents` via
`GM_xmlhttpRequest`, `fetch` fallback). `torn/` ships a stub `collectEvents()` + `executeAction()`
loop; Phase 3 fills in real Torn DOM scraping. Build: `npm run build` → `torn/dist/torn.user.js`.

## Verification (Phase 2)

- 18 unit tests pass (poker_protocol + live_server: bot-selection resolution, session store,
  table-state state machine incl. blinds/pot GameInfo, fold/call via built-in bots).
- End-to-end smoke: posted `table_profile` (cash NL 2-seat) → resolved `cash_nl`; full event stream
  (game_start → deal hole → SB/BB → waiting_for_action) → real engine decision returned inline.

## Phases

1. **Phase 1 — Scaffolding** ✅ crates, build pipeline, stubs.
2. **Phase 2 — Server core** ✅ event protocol, bot auto-detect, state machine → engine decisions.
3. **Phase 3 — Torn scraping** — inspect Torn poker DOM/network; implement `collectEvents()` +
   `executeAction()`; live single-table loop.
4. **Phase 4 — Hardening** — GameInfo fidelity tuning vs engine, session/hand lifecycle edge cases,
   reconnect/retry, throttling, hand-history logging, multi-table.

## Open items / known caveats

- **GameInfo fidelity:** the server-built `GameInfo` approximates engine state from events. Move
  quality depends on this matching exactly what the engine produces; needs validation against real
  Torn event streams (Phase 3) and possibly unit tests against the engine's own `GameInfo` snapshots.
- **`actions_this_hand`** is left empty in the server-built `GameInfo`; the `StrategyBot` derives its
  action history from its own `ActionRecorder` (fed via `action_event`), so this is not blocking, but
  any strategy reading `actions_this_hand` directly would see empty data.
- Server stays site-agnostic; `poker_protocol` stays free of network/runtime deps.

id: b7492324f4d64486ba79ea00e636d2ec
parent_id: 2c8da247905946c3aa19eb4936e16323
created_time: 2026-06-15T09:05:55.871Z
updated_time: 2026-06-15T10:28:09.849Z
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-06-15T10:28:09.849Z
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