Skip to content

fix(parser): count "the number of <type> you control with <keyword>" (Axebane Guardian, Doorkeeper)#5059

Merged
matthewevans merged 2 commits into
phase-rs:mainfrom
luciferlive112116:fix-number-of-controlled-type-with-keyword
Jul 5, 2026
Merged

fix(parser): count "the number of <type> you control with <keyword>" (Axebane Guardian, Doorkeeper)#5059
matthewevans merged 2 commits into
phase-rs:mainfrom
luciferlive112116:fix-number-of-controlled-type-with-keyword

Conversation

@luciferlive112116

Copy link
Copy Markdown
Contributor

Problem (#5058)

The the number of quantity grammar could not parse a controller-scoped count gated on a keyword predicatethe number of <type> you control with <keyword>:

  • Axebane Guardian — "Add X mana …, where X is the number of creatures you control with defender."
  • Doorkeeper / Coral Colony — "Target player mills X cards, where X is the number of creatures you control with defender."
  • Vent Sentinel — "deals damage … equal to the number of creatures you control with defender."

The dispatch already had the battlefield-wide parse_number_of_type_on_battlefield_with_keyword (<type> on the battlefield with <keyword>, backing Dauthi Warlord) and the bare parse_number_of_controlled_type (<type> you control), but no controller-scoped keyword-qualified arm. The bare arm matched creatures you control and stranded with defender, so the whole the number of quantity failed and the dependent value (X mana / mill / damage) was silently dropped.

Fix (oracle_nom/quantity.rs)

  • Add parse_number_of_controlled_type_with_keyword, registered in the the number of alt() immediately before parse_number_of_controlled_type so the longer keyword-qualified form is tried first.
  • The controller axis is generalized via the existing parse_quantity_controller_suffix (you control / your opponents control / the chosen player controls, CR 109.4) and the keyword axis over the full KEYWORDS table via parse_keyword_name + FilterProp::WithKeyword.
  • The keyword name is mapped through Keyword's FromStr with map_res, so an unconvertible name fails the parse as a graceful nom error rather than panicking.

No new AST/variant — it composes existing building blocks (parse_type_filter_word + parse_quantity_controller_suffix + parse_keyword_name), producing the same QuantityRef::ObjectCount shape the resolver already evaluates.

Before / after

the number of creatures you control with defender
  before: (clause fails to consume → whole quantity dropped → X = 0 / value lost)
  after:  ObjectCount{ Typed{ Creature, controller: You, [WithKeyword(Defender)] } }

Result

Axebane Guardian's mana, Doorkeeper's/Coral Colony's mill, and Vent Sentinel's damage now scale with the count of Defender creatures the controller has. The combinator covers the whole <type> <controller> with <keyword> the number of class, not just these cards, and is the controller-scoped counterpart of the existing battlefield-wide keyword count.

Implementation method (required)

  • Produced via the /engine-implementer pipeline
  • Not /engine-implementer — explain why below

A pure-parser, additive combinator composed from three existing building blocks, producing an already-evaluated QuantityRef::ObjectCount/FilterProp::WithKeyword shape. No engine game-logic, AST, or variant change. A /review-impl self-check was run against the two mandatory gates (correct seam: the the number of combinator dispatch; most idiomatic form: map_res + shared controller/keyword combinators rather than a bespoke re-parse or unwrap).

CR references

  • CR 109.4 — only objects with a controller are counted, so a "you control" count is over the resolved controller's permanents.

Testing

Local toolchain has no MSVC/mingw linker, so the test binaries can't be linked/run here — only the non-linking checks were run:

  • cargo fmt --all -- --check — clean.
  • cargo check -p engine --lib --tests — clean.

New tests (written to the conventions of the existing suite; please let CI's nextest be the gate):

  • parse_number_of_controlled_type_with_keyword_scoped_count (oracle_nom/quantity.rs) — asserts the controller scope (you control / your opponents control) and the FilterProp::WithKeyword predicate for defender/flying.
  • parse_number_of_controlled_type_bare_no_keyword_still_parses — guards that the new arm does not shadow the bare you control count.

Model: claude-opus-4-8[1m] (via Claude Code)

Closes #5058

…(Axebane Guardian, Doorkeeper)

The "the number of" quantity grammar had a combinator for the battlefield-wide
"<type> on the battlefield with <keyword>" form
(parse_number_of_type_on_battlefield_with_keyword, backing Dauthi Warlord) but
none for the controller-scoped "<type> you control with <keyword>" form. The
bare parse_number_of_controlled_type arm matched "creatures you control" and
stranded " with defender" as an unconsumed remainder, so the whole "the number
of" quantity failed to fully consume and the dependent value (X mana / mill /
damage / P-T) was silently dropped.

Add parse_number_of_controlled_type_with_keyword, registered ahead of the bare
controlled-type arm so the longer keyword-qualified form is tried first. The
controller axis is generalized via parse_quantity_controller_suffix (you
control / your opponents control / the chosen player controls, CR 109.4) and
the keyword axis over the full KEYWORDS table via parse_keyword_name +
FilterProp::WithKeyword. The keyword name is mapped through Keyword's FromStr
with map_res so an unconvertible name fails the parse gracefully rather than
panicking.

Backs the "the number of creatures you control with defender" cycle: Axebane
Guardian, Doorkeeper, Coral Colony, and Vent Sentinel.

Model: claude-opus-4-8[1m] (via Claude Code)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@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!

@matthewevans

Copy link
Copy Markdown
Member

Holding current head 7a4b10c532b8d54fe717175a7f1736b3cd51d271 for required coverage-parse-diff evidence.

This changes parser quantity dispatch in crates/engine/src/parser/oracle_nom/quantity.rs, but I do not see a parse-diff sticky for this PR/head yet. Please refresh or post the current-head parse-diff evidence so we can verify the card-level parser blast radius before approval/enqueue.

@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown

Parse changes introduced by this PR

✓ No card-parse changes detected.

@matthewevans matthewevans self-assigned this Jul 5, 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.

Reviewed current head 469bf68. This is safe as parser architecture coverage for the distinct 'the number of you control with ' grammar path: it composes existing type/controller/keyword parser building blocks, is not redundant with #5019/#5075's 'for each' path, and has focused tests for controller/opponent scope plus the non-shadowing bare form. Parse-diff reports no card-output changes, so I am approving this as canonical parser-path cleanup rather than a new card-data unlock.

@matthewevans matthewevans added the bug Bug fix label Jul 5, 2026
@matthewevans matthewevans added this pull request to the merge queue Jul 5, 2026
@matthewevans matthewevans removed their assignment Jul 5, 2026
Merged via the queue into phase-rs:main with commit 7e63294 Jul 5, 2026
11 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.

Parser drops "the number of <type> you control with <keyword>" counts (Axebane Guardian, Doorkeeper, Vent Sentinel)

2 participants