Skip to content

Emit synthetic SHOW actions for summary-only card reveals#107

Merged
ggratte merged 1 commit intoItalyToast:masterfrom
ggratte:fix-summary-reveal-actions
Apr 26, 2026
Merged

Emit synthetic SHOW actions for summary-only card reveals#107
ggratte merged 1 commit intoItalyToast:masterfrom
ggratte:fix-summary-reveal-actions

Conversation

@ggratte
Copy link
Copy Markdown
Collaborator

@ggratte ggratte commented Apr 26, 2026

Summary

  • Restores ShowdownAnalyzer's contract from Add showdown status and card-reveal action to parsed hands #105 (the action stream is the single source of truth for WentToShowdown and Player.RevealAction) by closing two gaps via the action stream itself instead of a side channel on Player.
  • New EmitSummaryShowdownActions(handLines, hand) virtual hook on HandHistoryParserFastImpl, called between FinalizeHandHistory and ShowdownAnalyzer.Populate. Default no-op.
  • PokerStars override appends a synthetic HandAction(name, SHOW, Street.Showdown) for two cases the action stream otherwise misses:
    • PokerStars uncontested-winner flash — summary Seat X: name (button) showed [..] with no *** SHOW DOWN *** section. Analyzer derives ShownVoluntarily (because WentToShowdown == false).
    • PPPoker showdown*** SHOW DOWN *** contains only the collection line; cards appear exclusively in summary Seat X: id showed [..]. Analyzer derives ShownAtShowdown (because WentToShowdown == true).
  • HashSet guard prevents double-emission when a real SHOW already exists.
  • Mucked-summary case is intentionally not handled here — every Seat X: name mucked [..] is already paired with a name: mucks hand line inside SHOW DOWN that emits a real MUCKS via ParseMiscShowdownLine.

Why not pre-set RevealAction on Player directly?

That alternative was attempted locally and discarded. It (a) breaks #105's "analyzer derives everything from the action stream" contract, (b) labels PPPoker reveals as ShownVoluntarily because the analyzer's WentToShowdown ? ShownAtShowdown : ShownVoluntarily mapping never runs when RevealAction is already set, and (c) requires a new else: keep branch on the analyzer. Emitting the missing actions instead lets the analyzer keep its single-source-of-truth model and gets the PPPoker label right for free.

Test plan

  • New integration test PPPokerShowdownHand_RevealActionsDerivedFromSummary — exercises the existing ShowdownHand.txt fixture; asserts both showing players get ShownAtShowdown (the value the discarded approach got wrong).
  • New integration test UncontestedWinnerFlash_RevealsAsShownVoluntarily — uses a new minimal fixture (ExtraHands/UncontestedWinnerFlash.txt); asserts the flasher gets ShownVoluntarily and WentToShowdown is false.
  • Full HandHistories.Parser.UnitTests suite on macOS: 1023 passed / 0 failed / 386 skipped (skips are pre-existing platform-conditional). Up from the post-Add showdown status and card-reveal action to parsed hands #105 baseline of 994 / 27.
  • HandHistories.Objects.UnitTests: 51 / 51 pass.

🤖 Generated with Claude Code

What
----
Add a `EmitSummaryShowdownActions(handLines, hand)` virtual hook on
`HandHistoryParserFastImpl`, called between `FinalizeHandHistory` and
`ShowdownAnalyzer.Populate`. PokerStars overrides it to append a synthetic
`HandAction(name, SHOW, Street.Showdown)` for two cases the action stream
otherwise misses:

  * PokerStars uncontested-winner flash — summary "Seat X: name (button)
    showed [..]" with no *** SHOW DOWN *** section.
  * PPPoker showdown — *** SHOW DOWN *** contains only the collection
    line; cards appear exclusively in summary "Seat X: id showed [..]".

A `HashSet` guard prevents double-emission when a real SHOW already
exists. The mucked-summary case is intentionally skipped: every
"Seat X: name mucked [..]" line is already paired with a "name: mucks
hand" inside SHOW DOWN that emits a real MUCKS via
`ParseMiscShowdownLine`.

Why
---
Commit 1fb5309 framed `ShowdownAnalyzer` as deriving `WentToShowdown`
and `Player.RevealAction` purely from the action stream. The earlier
attempt at this fix pre-set `RevealAction` on `Player` from inside
`ParsePlayers` and added an `else: keep` branch to the analyzer, which
breaks that contract and — for PPPoker — labels at-showdown reveals as
`ShownVoluntarily` because the analyzer's `WentToShowdown ?
ShownAtShowdown : ShownVoluntarily` mapping never runs.

Emitting the missing actions in the parser instead lets the analyzer
keep its single-source-of-truth model: `SHOW + WentToShowdown ⇒
ShownAtShowdown` (PPPoker), `SHOW + !WentToShowdown ⇒ ShownVoluntarily`
(uncontested flash) — both fall out automatically.

Tests
-----
Two new integration tests in `PokerStarsFastParserActionTests`:

  * `PPPokerShowdownHand_RevealActionsDerivedFromSummary` — exercises
    the existing PPPoker `ShowdownHand.txt` fixture; asserts both
    showing players get `ShownAtShowdown` (the value the prior approach
    got wrong).
  * `UncontestedWinnerFlash_RevealsAsShownVoluntarily` — uses a new
    minimal fixture (`ExtraHands/UncontestedWinnerFlash.txt`); asserts
    the flasher gets `ShownVoluntarily` and `WentToShowdown` is false.

Full `HandHistories.Parser.UnitTests` suite: 1023 passed / 0 failed /
386 skipped on macOS, up from the post-1fb5309 baseline of 994 / 27.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ggratte ggratte merged commit a0e5212 into ItalyToast:master Apr 26, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant