fix(parser): strip ability-word prefix on subject-anchored statics (CR 207.2c)#5198
Conversation
…R 207.2c)
Ability words are italic flavor with no rules meaning (CR 207.2c). The
subject-anchored static parsers match their subject at the start of the
line, so a leading ability-word label ('Chroma - ', 'Fateful hour - ',
'Protector - ') blocked them and the whole static silently dropped.
parse_static_line_multi_inner now retries through the dispatch after
stripping a *recognized* ability-word label (whitelist-gated via the shared
is_known_ability_word authority, exactly as the token-grant path does). It
is a strict fallback: any line the dispatch already parses is returned
untouched, so no existing coverage can regress, and the stripped body
carries no further label so the retry recurses at most once.
Also adds the two Warhammer 40K flavor ability words 'proclamator hailer'
and 'protector' to ABILITY_WORD_NAMES. Recovers Light from Within, Gavony
Ironwright, Echo of Dusk, Cryptothrall, Clamavus and the whole class of
known-ability-word subject-anchored statics.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request introduces a fallback mechanism in the static parser to strip recognized, whitelisted ability words (such as "Chroma", "Fateful hour", and newly added Warhammer 40k flavor words "protector" and "proclamator hailer") from subject-anchored static lines before parsing, preventing them from being silently dropped. It also adds corresponding unit tests. The review feedback points out a violation of Rule R6, noting that the comment added in oracle_modal.rs lacks the mandatory CR 207.2c annotation.
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.
| "pack tactics", | ||
| "paradox", | ||
| "parley", | ||
| // Warhammer 40,000 Commander (40K) flavor ability words — no rules meaning. |
There was a problem hiding this comment.
[MEDIUM] Missing mandatory CR annotation on rules-touching comment. Evidence: crates/engine/src/parser/oracle_modal.rs:1645.
Why it matters: Rule R6 requires every rules-touching line of engine code to carry a comment of the form CR <number>: <description> to ensure strict fidelity to the MTG Comprehensive Rules.
Suggested fix: Update the comment to include the verified CR reference (CR 207.2c).
| // Warhammer 40,000 Commander (40K) flavor ability words — no rules meaning. | |
| // CR 207.2c: Warhammer 40,000 Commander (40K) flavor ability words — no rules meaning. |
References
- Every rules-touching line of engine code must carry a comment of the form
CR <number>: <description>(regexCR \d{3}(\.\d+[a-z]?)?). (link)
matthewevans
left a comment
There was a problem hiding this comment.
Thanks for the scoped parser fix. The fallback seam looks reasonable, but the regression proof needs to be stronger before this lands.
The new positive cases in crates/engine/src/parser/oracle_static/tests.rs only assert parse_static_line_multi(...).len() == 1 for lines 28-57. That would still pass if the stripped body parsed into the wrong static shape, affected filter, condition, or modification, so it does not prove the ability-word fallback preserves the intended semantics. Please assert the resulting StaticDefinition shape for representative cases, for example: Chroma's dynamic P/T quantity, Fateful hour's condition plus "other creatures you control" pump, Protector's Other artifact creatures hexproof grant, and keep the unknown-label negative case.
Parse changes introduced by this PR · 21 card(s), 21 signature(s) (baseline: main
|
…tics; CR-annotate 40K words (review)
matthewevans[CHANGES_REQUESTED]: the positive cases only asserted len()==1,
which would pass even if the stripped body parsed into the wrong shape. Assert
the resulting StaticDefinition by field for representative cases:
- Chroma (Light from Within): creatures-you-control affected + dynamic P/T =
white pips in each recipient's mana cost (AddDynamicPower/Toughness ==
ManaSymbolsInManaCost{White}).
- Fateful hour (Gavony Ironwright): OTHER creatures you control, gated on
life <= 5 (QuantityComparison LE Fixed 5), +1/+4 pump.
- Protector (Cryptothrall): OTHER artifact creatures you control grant Hexproof.
Keeps the unknown-em-dash-label negative case (whitelist gate holds).
gemini[MED]: add the mandatory CR 207.2c annotation to the 40K ability-word
comment in ABILITY_WORD_NAMES.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Addressed both in @matthewevans — stronger regression proof. The positive cases now assert the resulting
The unknown-em-dash-label negative case ( @gemini — CR annotation. Added oracle_static suite 1009 pass / 0 fail; parser gate + fmt clean. |
|
Thank you for the update and the thorough regression testing. The implementation of the whitelist-gated fallback in |
matthewevans
left a comment
There was a problem hiding this comment.
Thanks for tightening the regression proof. One cleanup still needs to happen before this lands: the new test accidentally splits the existing Serra's Emissary rules doc comment.
At current head, crates/engine/src/parser/oracle_static/tests.rs:18-19 starts the Serra's Emissary CR 702.16 / CR 609.6 comment immediately above ability_word_prefix_is_stripped_from_subject_anchored_statics, and line 130 has the orphaned tail (/// type.) above the original Serra test. Please move the full Serra's Emissary doc comment back above compound_subject_keyword_static_splits_serras_emissary and leave only the CR 207.2c ability-word comment above the new test.
I don't have a behavior/code-path objection to the parser seam after this comment-layout fix.
The ability-word regression test was inserted into the middle of the existing Serra's Emissary CR 702.16 / CR 609.6 doc comment, stranding its head above the new test and orphaning its tail above the original test. Move the full Serra comment back above `compound_subject_keyword_static_splits_serras_emissary` and leave only the CR 207.2c ability-word comment above the new test. Comment-only; no behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@matthewevans — comment-layout fix pushed in The Serra's Emissary |
matthewevans
left a comment
There was a problem hiding this comment.
Reviewed current head after the follow-up. The prior regression-proof request is addressed with shape assertions for Chroma, Fateful hour, and Protector, and the Serra's Emissary CR doc comment is whole again above its original test. The implementation stays at the existing ability-word/static-parser seam, CR 207.2c is verified, and the source diff remains scoped to the parser/test files. Approving and enqueueing; the merge queue will wait for the in-progress checks on this head.
Summary
Fixes a class of static abilities that silently drop when their Oracle line carries a leading ability-word label (CR 207.2c — ability words are italic flavor with no rules meaning).
The subject-anchored static parsers match their subject at the start of the line, so a label like
Chroma —/Fateful hour —/Protector —prevents the subject match and the whole static is lost — leaving the permanent strictly off its printed rules. (Threshold —/Metalcraft —self-pumps slipped through only because their scanner matches mid-line.)parse_static_line_multi_innernow retries the dispatch after stripping a recognized ability-word label — whitelist-gated through the sharedis_known_ability_wordauthority, exactly as the token-grant path inkeyword_grant.rsalready does. It is a strict fallback: any line the ordinary dispatch already parses is returned untouched (zero regression), and the stripped body carries no further label so the retry recurses at most once.Recovered cards (and every future known-ability-word subject-anchored static):
Chroma — Each creature you control gets +1/+1 for each white mana symbol in its mana cost.Fateful hour — As long as you have 5 or less life, other creatures you control get +1/+4.Descend 4 — As long as there are four or more permanent cards in your graveyard, ~ gets +1/+1 and has lifelink.Protector — Other artifact creatures you control have hexproof.Proclamator Hailer — Each creature you control gets +1/+1 for each +1/+1 counter on it.Files changed
crates/engine/src/parser/oracle_static/shared.rscrates/engine/src/parser/oracle_modal.rscrates/engine/src/parser/oracle_static/tests.rsAnchored on
crates/engine/src/parser/oracle_static/keyword_grant.rs:1768— the token-granted-ability path already strips an em-dash label withstrip_ability_word_with_nameand gates it onis_known_ability_wordbefore re-dispatching the body; this change applies the identical, whitelist-gated pattern to the static-line dispatch.crates/engine/src/parser/oracle_modal.rs:1602—ABILITY_WORD_NAMES(CR 207.2c) is the single source of recognized ability words; the two 40K flavor words are added here so the gate accepts them.Gate A
(exit 0)
CR references
CR 207.2c— ability words are italicized flavor text with no rules meaning (verified againstdocs/MagicCompRules.txt).Track
Developer
LLM
Model: claude-opus-4-8
Thinking: high
Verification
cargo fmt --all— clean./scripts/check-parser-combinators.sh— clean (Gate A above)cargo test -p engine --lib parser::oracle_static— 1008 pass / 0 failcargo test -p engine --lib parser::oracle_modal— 53 pass / 0 failability_word_prefix_is_stripped_from_subject_anchored_statics— passes, incl. a safety case asserting an unknown em-dash label is NOT stripped (whitelist gate holds).Scope Expansion
None.
Validation Failures
None.
CI Failures
None.
🤖 Generated with Claude Code