When Brian narrates a hand he watched between OTHER players, the parser was still
filling hero_pos/hero_cards — pinning someone else's cards, position, and result
to him. Now:
- parser prompt adds hero_involved detection: fill hero_pos/hero_cards ONLY if he
was actually in the hand; a hand he only watched has hero_involved=false and
null hero fields, with the other players recorded normally.
- normalize_structured enforces it as a safety net (hero_involved=false → null
hero_pos/cards, hero_net) even if the model slips.
- record_hand tool confirms an observed hand as "not yours" instead of implying
it was his.
2 tests. Restarting web for the live session.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Solidify hand histories into one versioned shape that gets stored, replayed, and
exported — the foundation the tap recorder will emit into and RTO consumes.
- normalize_structured(): single guarantee of the contract shape — canonical cards
(unicode/10/case -> RankSuit tokens, unknown 'Ax'/'x' preserved), hero synced into
players[] (RTO finds hero via pos==hero_pos), schema_version stamp, and a
completeness summary so consumers skip suit-dependent math on partial hands.
Idempotent; runs on store AND read (legacy rows conform on the way out).
- list_recent_hands: has_structured flag so the export/RTO knows which hands have a
replayable body worth fetching.
- docs/HAND_HISTORY.md: the shared contract both repos cite (schema, conventions,
ownership rule, one-way HTTP coupling, transport endpoints).
- replaces the narrow _normalize_parsed (unicode-only) everywhere.
Card format chosen: lists of 2-char tokens (unambiguous, matches what Lyra already
stores + the viewer reads). Unknowns kept + flagged rather than dropped.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>