Skip to content

fix(parser): thread the counter rider into the 'exile one of them face down' fusion#5701

Merged
matthewevans merged 3 commits into
phase-rs:mainfrom
andriypolanski:fix/5631-exile-one-of-them-face-down-counter-rider
Jul 13, 2026
Merged

fix(parser): thread the counter rider into the 'exile one of them face down' fusion#5701
matthewevans merged 3 commits into
phase-rs:mainfrom
andriypolanski:fix/5631-exile-one-of-them-face-down-counter-rider

Conversation

@andriypolanski

Copy link
Copy Markdown
Contributor

Closes #5631

Summary

The Dragon-Kami Reborn's chapters I–II read: "…Look at the top three cards of
your library. Exile one of them face down with a hatching counter on it,
then put the rest on the bottom of your library in any order."

Coverage reported this card as supported (zero Unimplemented nodes), but it
resolved wrong — a silent misparse of the #1146 class. The
parse_exile_one_of_them_face_down recognizer required end-of-clause immediately
after "face down", so the CR 122.1 counter rider ("with a hatching counter on it") made it decline. With the recognizer declining, the Hideaway-style
fusion never fired: the Dig short-circuited as a keep_count: 0 pure-peek and a
sibling ChangeZone { ParentTarget } exiled the trigger source itself (the
Saga, with a hatching counter) instead of one of the three looked-at cards.

Per the issue, this is a recognizer extension, not new engine work: the fix
lets the recognizer accept an optional counter rider between "face down" and the
clause end, captures those counters, and threads them onto the chosen dug card
in the fused exile.

Changes

  • Recognizer (oracle_effect/sequence.rs, parse_exile_one_of_them_face_down)
    — now returns Option<Vec<(CounterType, QuantityExpr)>> instead of bool.
    Some(entries) signals a match (empty = the bare Gonti form; non-empty = the
    rider's counters). The optional rider is parsed by delegating to the shared
    parse_counter_suffix_body_combinator, so the whole class of
    "with <count> <type> counter(s) on it" shapes (a/two/X, +1/+1, keyword,
    named) is covered — not just "a hatching counter".
  • Continuation (oracle_ir/ast.rs) — ContinuationAst::ExileOneOfThemFaceDown
    gains a enter_with_counters: Vec<(CounterType, QuantityExpr)> field
    (#[serde(default, skip_serializing_if = "Vec::is_empty")] keeps the bare-form
    serialization byte-stable).
  • Fusion (oracle_effect/sequence.rs, apply_clause_continuation) — after the
    existing append_conceal_sub_ability, a new append_counter_riders_sub_abilities
    appends a PutCounter { target: ParentTarget } chain at the deepest sub. Because
    the DigChoice resolution binds the chosen (exiled) card onto the continuation
    chain's targets and ParentTarget sub-abilities inherit the parent's target
    (mirroring lower_put_counter_list), the counters land on the one card the
    player selected
    , not the Saga.
  • Exhaustive ContinuationAst match/matches! sites (produces_sibling_def,
    the dig_continuation_* helper in oracle_effect/mod.rs) updated to the new
    struct-variant shape (no wildcard fallbacks).

Why this is the right shape

enter_with_counters was already parsed correctly onto the (buggy, discarded)
sibling ChangeZone; the only defect was that the recognizer's eof gate
rejected the rider so the fusion never ran. Dig carries no counter field and
HideawayConceal carries only a target, so the rider is applied with the existing
PutCounter building block chained onto the same ParentTarget the conceal
already uses — no new engine variant or field, and apply_counter_addition
already places counters on objects regardless of zone (exile included).

Affected cards

"exile one of them face down" matches 5 cards in the full-pool sweep; 4 already
fired the recognizer correctly, and The Dragon-Kami Reborn was the sole decline.
The generalized rider combinator also future-proofs any later choose-one exile
that attaches a counter.

Tests

  • Recognizer: exile_one_of_them_face_down_with_counter_rider_captures_counters
    (the hatching rider is captured), exile_one_of_them_face_down_bare_form_has_no_counters
    (bare form is a match with an empty rider), plus the updated
    exile_one_of_them_without_face_down_is_not_gonti_continuation /
    delver_pure_peek_is_not_gonti_continuation scope guards.
  • Fusion: exile_one_of_them_face_down_counter_rider_appends_put_counter_after_conceal
    — asserts the fused chain is Dig(keep_count:1, Exile) -> HideawayConceal(ParentTarget) -> PutCounter(hatching, ParentTarget).

Test plan

  • cargo fmt --all
  • clippy / test-engine green (Tilt)
  • card-data regenerates cleanly (The Dragon-Kami Reborn now parses to the
    choose-one exile + conceal + hatching-counter shape, not a source self-exile)

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@github-actions

github-actions Bot commented Jul 12, 2026

Copy link
Copy Markdown

Parse changes introduced by this PR · 1 card(s), 5 signature(s) (baseline: main afb6af2d940b)

1 card(s) · ability/ChangeZone · removed: ChangeZone (enter_with_counters=[(Generic("hatching"), Fixed { value: 1 })], target=parent target, to=exile)

Examples: The Dragon-Kami Reborn

1 card(s) · ability/Dig · field keep_count: 01

Examples: The Dragon-Kami Reborn

1 card(s) · ability/Dig · field rest_to: library

Examples: The Dragon-Kami Reborn

1 card(s) · ability/Dig · field to: exile

Examples: The Dragon-Kami Reborn

1 card(s) · ability/HideawayConceal · added: HideawayConceal

Examples: The Dragon-Kami Reborn

1 card(s) had Oracle-text changes (errata/reprint) — excluded as non-parser.

Match rustfmt canonical output for three spots the PR left unformatted:
- wrap the Some(A | B) or-pattern in continuation_is_exile_producer
- break the vec![AbilityDefinition::new(..)] call in the new fusion test
- collapse the !matches!(..) assert (fits within fn_call_width)

Co-authored-by: Cursor <cursoragent@cursor.com>

@matthewevans matthewevans left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Request changes — the counter-rider fusion is at the right parser seam, but no runtime regression proves the counter reaches the player-selected exiled card instead of the Saga/source.

🔴 Blocker

  • parser/oracle_effect/sequence.rs:9389-9447 constructs Dig → HideawayConceal → PutCounter { ParentTarget }, and the added coverage only inspects that shape. The reported failure is resolution behavior: “Exile one of them face down with a hatching counter on it” must select one looked-at card, exile that card face down, and put the hatching counter on that card—not on The Dragon-Kami Reborn or a discarded sibling. Add a GameScenario/resolution-path regression that drives the choice and asserts the selected exile object’s counter plus the Saga’s continued zone. This must fail when the fusion or the counter-target binding is reverted.

🟡 Non-blocking

  • The optional rider uses the shared counter-suffix combinator and correctly preserves the bare Gonti form; keep that generalization.

✅ Clean

  • parse_exile_one_of_them_face_down now rejects only nonmatching clauses and distinguishes a bare match from a match with counter entries.
  • The current parse artifact is contained to The Dragon-Kami Reborn across five expected signatures; no unrelated card changed.

Recommendation: request changes — add the discriminating real-resolution regression, then re-review this as a bug fix.

…e chosen exile

Adds a runtime resolution regression (addresses the PR review request): it
resolves chapters I–II, answers the DigChoice, and asserts the player-selected
card is exiled face down with exactly one hatching counter while the Saga stays
on the battlefield with no counter. Fails when the fusion is reverted (the peek
Dig surfaces no DigChoice) or when the counter-target binding is reverted (the
counter rides the source Saga instead of the chosen card).

Co-authored-by: Cursor <cursoragent@cursor.com>
@matthewevans matthewevans self-assigned this Jul 13, 2026

@matthewevans matthewevans left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve — the counter-rider fusion now has a discriminating real-resolution regression at the existing Dig continuation seam.

🔴 Blocker

  • None. The prior missing runtime test is addressed on dade59e: it resolves the full chapter, submits DigChoice, and proves the chosen card—not the Saga—receives the hatching counter.

🟡 Non-blocking

  • Gemini is quota-limited this session; the independent local/adversarial review was completed against the current head instead.

✅ Clean

  • The shared counter-suffix combinator keeps the bare Gonti form intact while covering the counter-rider class.
  • The current parse artifact is contained to The Dragon-Kami Reborn (one card, five expected signature changes).
  • Current CI is green, and the runtime test would fail if the fusion or ParentTarget binding were reverted.

Recommendation: approve and enqueue as bug.

@matthewevans matthewevans added the bug Bug fix label Jul 13, 2026
@matthewevans
matthewevans added this pull request to the merge queue Jul 13, 2026
@matthewevans matthewevans removed their assignment Jul 13, 2026
Merged via the queue into phase-rs:main with commit 9a156be Jul 13, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The Dragon-Kami Reborn: Saga exiles ITSELF instead of a looked-at card (silent #1146-class misparse)

3 participants