Files
project-lyra/docs/HAND_HISTORY.md
serversdown b6cdf799dc feat: straddle support in recorder
Add a straddle from any non-blind seat (default 2×BB) via a preflop control.
Recorded as a preflop post (fits the contract — order carries 'acts last'), tagged
straddle for the log label + removability; the UI-only flag is dropped from the
emitted structured hand. Blinds stay non-removable. Documented in HAND_HISTORY.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 04:49:50 +00:00

4.2 KiB
Raw Permalink Blame History

Hand-history contract (Lyra → RTO)

The canonical structured shape for a poker hand. Lyra owns hands — it produces this shape (LLM parser today; the tap recorder natively, going forward), stores it, replays it in the viewer, and exports it. RTO consumes it over HTTP and never reaches into Lyra.

Ownership rule: whoever owns the data owns the tools that produce it. Lyra owns the hand DB, the viewer, and the copilot loop, so hand capture lives here. RTO is a pure engine.

Coupling: one arrow, Lyra → RTO, HTTP only. RTO is a standalone service (solve / exploit / estimate); Lyra POSTs to it when it wants analysis. No shared package, no shared DB, no shared UI components. If RTO is down, Lyra skips analysis and nothing breaks.

Schema (schema_version: 1)

{
  "schema_version": 1,
  "game": "NLH",                     // NLH | PLO | ...
  "stakes": "1/3",                   // or null
  "hero_pos": "BTN",                 // one of POSITIONS
  "hero_cards": ["Ah", "Kh"],        // convenience mirror of the hero's players[].cards
  "players": [                       // every player in the hand, incl. hero
    {"pos": "BTN", "stack": 300, "name": "Hero", "cards": ["Ah","Kh"], "hero": true},
    {"pos": "BB",  "stack": 250, "name": "Sal",  "cards": null}        // cards: null unless shown
  ],
  "actions": [                       // one flat chronological list across all streets
    {"street": "preflop", "pos": "BTN", "action": "raise", "amount": 15},
    {"street": "flop", "board": ["7d","2c","5h"]},   // a street begins with its board reveal
    {"street": "flop", "pos": "BB", "action": "check"}
  ],
  "board": ["7d","2c","5h"],         // full final board, 05 cards
  "result": {"pot": 40, "hero_net": 25, "summary": "one line"},
  "completeness": {"cards": true, "board": true, "actions": true}
}

Conventions (load-bearing)

  • Cards are lists of 2-char tokens, RankSuit: rank in 23456789TJQKA (ten = T), suit in c d h s (lowercase). E.g. ["As","5d","2c"]. RTO maps each token via pokercore.parse_card. (Chosen over space-joined strings: unambiguous, no re-splitting, and it's what Lyra already stores + what the viewer reads.)
  • Unknown cards are kept, not dropped: "Ax" = known rank / unknown suit, "x" = fully unknown card. The LLM parser emits these when Brian didn't state suits. The tap recorder won't — it captures complete cards by construction — so "x" is an import/parser-only concern.
  • completeness tells a consumer what's safe to use: cards/board are true only when every relevant card is fully specified (no "x"). RTO uses false-card hands for positions/frequencies/pairs and skips suit-dependent math (flushes).
  • Hero appears in players[] with "hero": true and is findable via pos == hero_pos. hero_cards is a mirror for the viewer; players[].cards is the source of truth.
  • Positions: UTG UTG1 UTG2 MP LJ HJ CO BTN SB BB.
  • Actions: post fold check call bet raise allin. amount is a plain number (no $), null for non-sized actions (fold/check). Street boards appear as {street, board} entries. A straddle is recorded as a preflop post at a non-blind position (typically 2×BB, voluntary); preflop action starts left of it and it acts last, but that's reflected by action order, not a distinct verb.
  • Streets: preflop flop turn river.

lyra/poker.py:normalize_structured() is the single function that guarantees this shape. It runs on store and on read, and is idempotent.

Transport (HTTP, Lyra serves on :7078)

  • GET /hands/data?limit=N{ "hands": [ {id, position, hole_cards, board, result, tag, at, lesson, venue, stakes, has_structured}, ... ] } — flat list for browsing. Use has_structured to pick which hands have a replayable body worth fetching.
  • GET /hand/{id}/data → the full hand row; structured is the object above (or null for a flat quick-log that hasn't been reconstructed).

RTO's "Lyra bridge" (its docs/estimator-design.md, Phase B) walks structured.actions to classify each villain decision into checked_to / facing_bet / facing_raise, and uses shown cards + that street's board for board-relative categories. Everything that walk needs is in the schema above.