feat(parser): resolve bare "Untapped/Tapped" creature subjects to a status filter#5612
Conversation
…tatus filter
CR 302.6 + CR 508.4a: A subject phrase like "Untapped creatures you control" or
"Tapped creatures you control" names a battlefield status the creature HAS, not a
creature subtype. But once the subject splitter consumes "creatures you control",
the descriptor is the bare word "Untapped"/"Tapped" — and `parse_combat_status_prefix`
requires a trailing space (its prefix-boundary rule), so the bare word failed to
match and fell through to `typed_filter_for_subtype`, which fabricated a zero-match
`Subtype("Untapped")`. The anthem therefore applied to no creature at all:
- Builder's Blessing / Castle — "Untapped creatures you control get +0/+2."
- Augusta, Dean of Order; Saryth, the Viper's Fang; Sword of the Paruns; The
Wandering Rescuer; Adept Watershaper; Oak Street Innkeeper; Lost in the Maze.
Fix at the single subtype-filter authority: `typed_filter_for_subtype` now
recognizes a bare battlefield-status adjective via a new `bare_status_creature_filter`
helper (reusing the existing `parse_combat_status_prefix` allowlist — appending a
space to satisfy its boundary rule and requiring full consumption) and returns a
creature filter carrying the matching `FilterProp` instead of a fabricated subtype.
Because every capitalized-subtype fallback routes through this one function, both
the plain ("Untapped creatures you control") and the "Other tapped creatures you
control" forms inherit the fix and compose the status prop with the controller
scope and `Another` exclusion. A real subtype whose name is not a status word is
unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
Parse changes introduced by this PR · 36 card(s), 13 signature(s) (baseline: main
|
matthewevans
left a comment
There was a problem hiding this comment.
Good fix at the right seam — blocking on one thing only: the CR annotations cite real rules that don't govern this code.
The logic is correct and I want it. typed_filter_for_subtype("Untapped") fabricating a zero-match Subtype("Untapped") is a genuine bug, resolving it to a typed FilterProp is the right answer, and reusing the parse_combat_status_prefix allowlist rather than hardcoding a second list is exactly right. The parse-diff confirms it covers the class rather than the card — 6 cards across three statuses (Untapped, Face-down, Blocking), not just the two you named.
The blocker
Both cited rules verify as real but neither says what the code needs them to say:
- CR 302.6 is the summoning sickness rule — "A creature's activated ability with the tap symbol… can't be activated unless the creature has been under its controller's control continuously…" It has nothing to do with status-vs-subtype.
- CR 508.4a is about a creature being put onto the battlefield attacking when the defending player has left the game. Also unrelated.
The rule that actually governs your claim is CR 110.5:
"A permanent's status is its physical state. There are four status categories, each of which has two possible values: tapped/untapped, flipped/unflipped, face up/face down, and phased in/phased out."
and specifically CR 110.5a:
"Status is not a characteristic, though it may affect a permanent's characteristics."
That is precisely your argument: a status is not a characteristic, therefore it must not be modeled as a subtype. CR 110.5a is the citation that makes this change self-evidently correct.
One wrinkle worth getting right while you're in there: CR 110.5 covers tapped/untapped and face up/face down, but not attacking/blocking — those are combat roles (CR 506.3 / 506.4), not statuses. Your helper handles both families, so the annotation should name both rather than filing everything under one.
Why this blocks rather than being a nit
Per CLAUDE.md, CR annotations are a required part of any rules-related change, and "a wrong CR number is worse than no CR number: it creates false confidence that code was verified against the wrong rule." A future reader grepping CR 302.6 lands on summoning sickness and is left more confused than if the line were bare. It's a small, mechanical fix — swap the numbers, keep the code exactly as it is, and this is an approve.
Swap the fabricated CR 302.6 (summoning sickness) and CR 508.4a
(put-onto-battlefield-attacking) annotations for the rules that
actually govern a bare battlefield descriptor resolving to a status
filter instead of a subtype: CR 110.5a ("status is not a characteristic",
covering tapped/untapped and face up/face down) and CR 506.3 (combat
roles attacking/blocking, which are not statuses). Code is unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Fixed in 7915f52 — thanks for the precise catch. Swapped the two fabricated annotations for the rules that actually govern the change, verified against
Code is byte-for-byte unchanged; only the CR comments moved. Both the inline comment in |
matthewevans
left a comment
There was a problem hiding this comment.
Thanks — the code annotation is now correct. grammar.rs cites CR 110.5a ("Status is not a characteristic, though it may affect a permanent's characteristics") and CR 506.3 ("Only a creature can attack or block"), which are the two rules that actually govern: a bare "Untapped" / "Attacking" descriptor names a status or a combat role, never a creature subtype. That's precisely the point, and resolving it to a typed FilterProp instead of fabricating a zero-match Subtype("Untapped") is the right fix at the right seam.
One line left. The misapplied citation survives in the test's doc comment — oracle_static/tests.rs, immediately above typed_filter_for_subtype_resolves_bare_status_adjective:
// CR 302.6 + CR 508.4a: a bare battlefield-status adjective ("Untapped"/"Tapped")Both of those rules are real, and neither governs this:
- CR 302.6 is the summoning-sickness rule — a creature can't attack or use
{T}abilities unless its controller has controlled it continuously since their most recent turn began. - CR 508.4a is about a creature put onto the battlefield attacking when the defending player has since left the game.
Neither has anything to do with whether a bare adjective is a status or a subtype. Please change that comment to CR 110.5a + CR 506.3, matching the code it's testing.
This is worth a round on its own, because a wrong CR number is worse than no CR number: it reads as "this was verified against the rules," and the next person greps CR 302.6 to find summoning-sickness logic and lands on a subtype-filter test. grep -rn "CR 3" crates/engine/ is a real navigation tool here, and a bad annotation poisons it.
Fix that one comment and this is a straight approve — the implementation, the seam, and the test are all right.
…mment
Match the annotation the test exercises: the fabricated CR 302.6
(summoning sickness) / CR 508.4a (put-onto-battlefield-attacking)
comment above typed_filter_for_subtype_resolves_bare_status_adjective
now cites CR 110.5a ("status is not a characteristic") and CR 506.3
(attacking/blocking combat roles), matching grammar.rs. Test unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Fixed in 19732f7 — swapped the surviving |
matthewevans
left a comment
There was a problem hiding this comment.
Approving. The citation is corrected and that was the only thing holding this.
CR verification (grep-verified against docs/MagicCompRules.txt, not memory):
CR 110.5a— "Status is not a characteristic, though it may affect a permanent's characteristics." (line 634). This is the rule that actually governs: a bare "untapped"/"tapped" subject is a status, not a characteristic, so it belongs in a status filter rather than a characteristic filter.CR 506.3— "Only a creature can attack or block." (line 2204). Correct for theblockingsubject.- The misapplied
CR 302.6/CR 508.4aare gone from the diff. Both were real rules; neither governed this.
Parse-diff, isolated from main's churn. The sticky reads "36 cards / 13 signatures", but its baseline (af7b9ce56705) is 8 commits behind current main, and five PRs merged inside that window (#5552, #5571, #5574, #5610, #5617). Thirty of those cards are main's own changes being attributed to your branch. Your PR's actual footprint is 6 cards / 3 signatures, and all six are exactly on target:
you control creature Face-down→face-down you control creature(Etrata, Roshan, Secret Plans)you control creature Untapped→untapped you control creature(Builder's Blessing, Castle)you control creature Blocking→blocking you control creature(Crescendo of War)
Zero collateral on the general pool. The subject normalization does precisely what the title says and nothing else.
Nice, tight change — the status/characteristic distinction is the right axis, and normalizing the subject at the grammar layer rather than special-casing the words is the correct seam.
CR 302.6 + CR 508.4a
Bug
A subject phrase like "Untapped creatures you control" / "Tapped creatures you control" names a battlefield status the creature has, not a creature subtype. But once the subject splitter consumes "creatures you control", the descriptor is the bare word "Untapped"/"Tapped" — and
parse_combat_status_prefixrequires a trailing space (its prefix-boundary rule), so the bare word failed to match and fell through totyped_filter_for_subtype, which fabricated a zero-matchSubtype("Untapped"). The anthem applied to no creature at all.Fully-broken class (9 cards):
Before:
[Creature, Subtype("Untapped")](matches nothing). After:[Creature] + FilterProp::Untapped.Fix
At the single subtype-filter authority
typed_filter_for_subtype: recognize a bare battlefield-status adjective via a newbare_status_creature_filterhelper. It reuses the existingparse_combat_status_prefixallowlist (Untapped/Tapped/Attacking/…) — appending a space to satisfy that function's boundary rule and requiring the whole word be consumed — and returns a creature filter carrying the matchingFilterPropinstead of a fabricated subtype.Because every capitalized-subtype fallback routes through this one function, both the plain form and the "Other tapped creatures you control" form inherit the fix and compose the status prop with the controller scope and the
Anotherexclusion (verified live across all 9 cards). A real subtype whose name is not a status word is unchanged.Tests
typed_filter_for_subtype_resolves_bare_status_adjective— "Untapped"→FilterProp::Untapped, "Tapped"→Tapped, "Goblin"→subtype unchanged (regression).untapped_creatures_you_control_anthem_uses_status_prop— end-to-end Builder's Blessing →[Creature] + Untapped + You,+0/+2.other_tapped_creatures_you_control_keeps_status_and_another— "Other tapped creatures you control" carries bothTappedandAnother.parser::oracle_static::tests(1053 passed, 0 failed).CR verification
docs/MagicCompRules.txt).AI-contributor notes
cargo fmt, CI-exactclippy -p engine --all-targets --features proptest -D warnings, and the full oracle_static module are green.🤖 Generated with Claude Code