fix(parser): support filtered card counts in the chosen player's zone (Haunting Apparition)#5426
Conversation
…phase-rs#5425) Haunting Apparition — "power is equal to 1 plus the number of green creature cards in the chosen player's graveyard" — failed to parse. The unfiltered form ("cards in the chosen player's graveyard", Sewer Nemesis) and filtered forms scoped to your/their/opponents' graveyards already worked; only a type/color filter combined with the chosen-player scope was unsupported, because two count-parsing paths never learned the "the chosen player's <zone>" possessive: - `parse_scoped_zone_ref` (the `ZoneCardCount` scoped-zone path) gains a "the chosen player's <zone>" arm → `CountScope::SourceChosenPlayer`, alongside your/opponents'/all. This unifies the type-filtered and unfiltered cases, so the previously separate unfiltered-only `parse_number_of_cards_in_chosen_player_zone` special case is removed. - `parse_zone_qual` (the general typed-phrase zone-suffix path, used for color/property filters that lower to `ObjectCount`) gains a `ZoneQual::ChosenPlayer` arm → `ControllerRef::SourceChosenPlayer`, mirroring how "your <zone>" sets `ControllerRef::You`. Ordered before the bare "the " article so it isn't mis-consumed. Both resolve against the source's persisted chosen player, matching the existing your/their/opponents' scopes. No runtime changes. CR 613.1 + CR 404.2 + CR 108.3.
There was a problem hiding this comment.
Code Review
This pull request refactors the parsing of "the chosen player's " phrases by integrating them into the shared scoped-zone path and introducing the ZoneQual::ChosenPlayer qualifier. This change allows card-type and color filters to compose uniformly. Reviewer feedback correctly identifies that several code comments and test annotations incorrectly reference CR 613.1 (continuous effect layers) instead of CR 607.2d (linked abilities referring to a chosen player), violating the repository's strict rule on accurate CR annotations.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| // CR 613.1: "the chosen player's <zone>" — the player persisted on the | ||
| // source via an earlier "choose a player" (Haunting Apparition: | ||
| // "green creature cards in the chosen player's graveyard"). Placed on the | ||
| // shared scoped-zone path so card-type/color filters compose uniformly, | ||
| // rather than a separate unfiltered-only arm. |
There was a problem hiding this comment.
[MED] Incorrect CR annotation for chosen player. Evidence: crates/engine/src/parser/oracle_nom/quantity.rs:2108. Why it matters: CR 613.1 describes continuous effect layers, whereas CR 607.2d governs linked abilities referring to a chosen player. Suggested fix: Change CR 613.1 to CR 607.2d.
| // CR 613.1: "the chosen player's <zone>" — the player persisted on the | |
| // source via an earlier "choose a player" (Haunting Apparition: | |
| // "green creature cards in the chosen player's graveyard"). Placed on the | |
| // shared scoped-zone path so card-type/color filters compose uniformly, | |
| // rather than a separate unfiltered-only arm. | |
| // CR 607.2d: "the chosen player's <zone>" — the player persisted on the | |
| // source via an earlier "choose a player" (Haunting Apparition: | |
| // "green creature cards in the chosen player's graveyard"). Placed on the | |
| // shared scoped-zone path so card-type/color filters compose uniformly, | |
| // rather than a separate unfiltered-only arm. |
References
- Every rules-touching line of engine code must carry a comment of the form CR : verified against docs/MagicCompRules.txt. (link)
| // CR 613.1: "creature cards in the chosen player's graveyard" — a type- | ||
| // filtered card count scoped to the source's persisted chosen player. | ||
| // Recognizing "the chosen player's <zone>" on the shared scoped-zone path | ||
| // lets the type filter compose, mirroring "your"/"opponents'" graveyards. |
There was a problem hiding this comment.
[MED] Incorrect CR annotation for chosen player. Evidence: crates/engine/src/parser/oracle_quantity.rs:3337. Why it matters: CR 613.1 describes continuous effect layers, whereas CR 607.2d governs linked abilities referring to a chosen player. Suggested fix: Change CR 613.1 to CR 607.2d.
| // CR 613.1: "creature cards in the chosen player's graveyard" — a type- | |
| // filtered card count scoped to the source's persisted chosen player. | |
| // Recognizing "the chosen player's <zone>" on the shared scoped-zone path | |
| // lets the type filter compose, mirroring "your"/"opponents'" graveyards. | |
| // CR 607.2d: "creature cards in the chosen player's graveyard" — a type- | |
| // filtered card count scoped to the source's persisted chosen player. | |
| // Recognizing "the chosen player's <zone>" on the shared scoped-zone path | |
| // lets the type filter compose, mirroring "your"/"opponents'" graveyards. |
References
- Every rules-touching line of engine code must carry a comment of the form CR : verified against docs/MagicCompRules.txt. (link)
| // CR 613.1: Haunting Apparition — "green creature cards in the chosen | ||
| // player's graveyard". A color (property) filter routes through the | ||
| // general typed-phrase path as an `ObjectCount`; the "the chosen player's" | ||
| // zone qualifier must set `ControllerRef::SourceChosenPlayer`, mirroring | ||
| // how "your graveyard" sets `ControllerRef::You`. |
There was a problem hiding this comment.
[MED] Incorrect CR annotation for chosen player. Evidence: crates/engine/src/parser/oracle_quantity.rs:3359. Why it matters: CR 613.1 describes continuous effect layers, whereas CR 607.2d governs linked abilities referring to a chosen player. Suggested fix: Change CR 613.1 to CR 607.2d.
| // CR 613.1: Haunting Apparition — "green creature cards in the chosen | |
| // player's graveyard". A color (property) filter routes through the | |
| // general typed-phrase path as an `ObjectCount`; the "the chosen player's" | |
| // zone qualifier must set `ControllerRef::SourceChosenPlayer`, mirroring | |
| // how "your graveyard" sets `ControllerRef::You`. | |
| // CR 607.2d: Haunting Apparition — "green creature cards in the chosen | |
| // player's graveyard". A color (property) filter routes through the | |
| // general typed-phrase path as an ObjectCount; the "the chosen player's" | |
| // zone qualifier must set ControllerRef::SourceChosenPlayer, mirroring | |
| // how "your graveyard" sets ControllerRef::You. |
References
- Every rules-touching line of engine code must carry a comment of the form CR : verified against docs/MagicCompRules.txt. (link)
| /// "the chosen player's " — the player persisted on the source via an earlier | ||
| /// "choose a player" (Haunting Apparition: "green creature cards in the chosen | ||
| /// player's graveyard"). Sets `ControllerRef::SourceChosenPlayer`, mirroring | ||
| /// how `You` sets `ControllerRef::You`; CR 613.1 resolves it against the | ||
| /// source's persisted choice. |
There was a problem hiding this comment.
[MED] Incorrect CR annotation for chosen player. Evidence: crates/engine/src/parser/oracle_target.rs:6892. Why it matters: CR 613.1 describes continuous effect layers, whereas CR 607.2d governs linked abilities referring to a chosen player. Suggested fix: Change CR 613.1 to CR 607.2d.
| /// "the chosen player's " — the player persisted on the source via an earlier | |
| /// "choose a player" (Haunting Apparition: "green creature cards in the chosen | |
| /// player's graveyard"). Sets `ControllerRef::SourceChosenPlayer`, mirroring | |
| /// how `You` sets `ControllerRef::You`; CR 613.1 resolves it against the | |
| /// source's persisted choice. | |
| /// "the chosen player's " — the player persisted on the source via an earlier | |
| /// "choose a player" (Haunting Apparition: "green creature cards in the chosen | |
| /// player's graveyard"). Sets ControllerRef::SourceChosenPlayer, mirroring | |
| /// how You sets ControllerRef::You; CR 607.2d resolves it against the | |
| /// source's persisted choice. |
References
- Every rules-touching line of engine code must carry a comment of the form CR : verified against docs/MagicCompRules.txt. (link)
| // CR 613.1: must precede the `Plain` "the " arm so "the chosen player's " | ||
| // isn't consumed as a bare "the " article. |
There was a problem hiding this comment.
[MED] Incorrect CR annotation for chosen player. Evidence: crates/engine/src/parser/oracle_target.rs:7141. Why it matters: CR 613.1 describes continuous effect layers, whereas CR 607.2d governs linked abilities referring to a chosen player. Suggested fix: Change CR 613.1 to CR 607.2d.
| // CR 613.1: must precede the `Plain` "the " arm so "the chosen player's " | |
| // isn't consumed as a bare "the " article. | |
| // CR 607.2d: must precede the Plain "the " arm so "the chosen player's " | |
| // isn't consumed as a bare "the " article. |
References
- Every rules-touching line of engine code must carry a comment of the form CR : verified against docs/MagicCompRules.txt. (link)
Parse changes introduced by this PR · 2 card(s), 3 signature(s) (baseline: main
|
matthewevans
left a comment
There was a problem hiding this comment.
Reviewed the parser change at 099a4a7e22ad33958ce213b03881da8c670b1265.
Evidence checked:
- The diff is limited to parser quantity/target handling plus focused parser tests.
the chosen player's <zone>now composes through the existing scoped-zone building block instead of an unfiltered one-off arm.- The parse-diff sticky is narrow: Haunting Apparition plus the related Nyxathid dynamic-count shape.
- GitHub CI is green, including Rust lint/tests, card-data, WASM, and frontend checks.
No blocking issues found.
Closes #5425.
What
Makes Haunting Apparition work — "power is equal to 1 plus the number of green creature cards in the chosen player's graveyard." A card-count quantity that's filtered (by card type or color) and scoped to "the chosen player's" graveyard failed to parse. The unfiltered chosen-player form (Sewer Nemesis) and the filtered your/their/opponents' forms already worked — only the filter × chosen-player combination was missing.
How (routes to existing runtime, no engine changes)
Two count-parsing paths never learned the
"the chosen player's <zone>"possessive they already recognize foryour/their/opponents':parse_scoped_zone_ref(theZoneCardCountscoped-zone path, type-filtered counts) gains a"the chosen player's <zone>"arm →CountScope::SourceChosenPlayer. This unifies the type-filtered and unfiltered cases, so the previously separate unfiltered-onlyparse_number_of_cards_in_chosen_player_zonespecial case is removed (single authority).parse_zone_qual(the general typed-phrase zone-suffix path, color/property filters that lower toObjectCount) gains aZoneQual::ChosenPlayerarm →ControllerRef::SourceChosenPlayer, mirroring how"your <zone>"setsControllerRef::You. Ordered before the bare"the "article so it isn't mis-consumed.Both resolve against the source's persisted chosen player (
SourceChosenPlayer, CR 613.1) via the existing filter/count runtime.Haunting Apparition now parses to
SetDynamicPower(Offset { ObjectCount { Creature, controller: SourceChosenPlayer, [HasColor(Green), InZone(Graveyard)] }, offset: 1 }).Tests
cda_type_filtered_cards_in_chosen_players_graveyard→ZoneCardCount { scope: SourceChosenPlayer, card_types: [Creature] }.cda_color_filtered_cards_in_chosen_players_graveyard→ObjectCountwithcontroller: SourceChosenPlayer+HasColor(Green)+InZone(Graveyard).Verification
cargo fmt --all✓ · engine lib suite 15,871 pass / 0 fail ✓ ·clippy -p engine --all-targets -D warningsclean ✓