feat(parser): dynamic damage-modification replacements (Fated Firepower, Neriv)#6066
Merged
Merged
Conversation
…er, Neriv)
Add two damage-replacement parser arms (CR 614.1a — "instead"
replacement effects) emitting typed DamageModification values:
- "plus an amount of damage equal to <quantity> instead" ->
DamageModification::Plus { value } via parse_cda_quantity
(Fated Firepower: +N where N = fire counters on the source).
- "twice that much damage" -> DamageModification::Double
(CR 701.10g double-damage; Neriv, Heart of the Storm).
Both compose existing combinators (preceded/alt/take_until +
parse_cda_quantity) — no verbatim Oracle-string matching.
Extend the DynamicQty swallow classifier so a damage Double/Triple
carrier is no longer flagged as an unsupported dynamic clause: a
new suppression clause plus the shared OTHER_DYNAMIC_MARKERS const
(deduplicated from three prior copies). Ojer Axonil
(SetToSourcePower) is covered by an unchanged-behavior regression
test.
Coverage delta vs base: exactly {Fated Firepower, Neriv, Heart of
the Storm} False->True, zero other transitions.
Assisted-by: ClaudeCode:claude-opus-4.8
Contributor
|
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 PRBaseline pending for |
matthewevans
approved these changes
Jul 17, 2026
matthewevans
left a comment
Member
There was a problem hiding this comment.
Maintainer sign-off: current head independently reviewed; required checks green. Runtime coverage discriminates the dynamic offset and entered-this-turn damage-doubling paths.
This was referenced Jul 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🤖 AI text below 🤖
Summary
Adds two composable damage-modification replacement parser arms (CR 614.1a — "instead" replacement effects) so damage-doubling and dynamic additive-offset cards parse to typed
DamageModificationvalues. Fixes aDynamicQtycoverage-classifier false-positive that flagged damage-Double/Triplecarriers as unsupported. Standard-legal cards unlocked: Fated Firepower and Neriv, Heart of the Storm; Ojer Axonil, Deepest Might (SetToSourcePower, already handled) gains an unchanged-behavior regression test.DynQty subgroup A of the Standard rollout.
Implementation method (required)
Method: /engine-implementer
CR references
CR 614.1a— a replacement effect that reads "instead" replaces the damage event with the modified amount.CR 701.10g— to double an amount of damage, the source instead deals twice that much damage.What changed
parser/oracle_replacement.rs— two arms, both composed from existing combinators (no verbatim Oracle-string match):parse_that_much_damage_offset— new arm (inserted before the numeric arm so"an"→1 does not shadow it):preceded(tag("plus an amount of damage equal to "), terminated(take_until(" instead"), tag(" instead")))→
map_opt→parse_cda_quantity(q)→DamageModification::Plus { value }. Fail-closed: an unrecognized quantity makesmap_optfail and the arm yields.parse_damage_modification_phrase— new leaftag("twice that much damage")added to the pre-existing Doublealt(siblings"double that damage","deals double that damage") →DamageModification::Double.Before → after AST (Fated Firepower):
it deals that much damage plus an amount of damage equal to the number of fire counters on ~ instead→ReplacementDefinition { damage_modification: None, .. }(dropped / unsupported).damage_modification: Some(DamageModification::Plus { value: <fire-counters-on-source ref> }).parser/swallow_check.rs— thedetect_dynamic_qtycoverage classifier was flagging"twice that much damage"(a static-typedDouble, not a runtimeQuantityExpr) as an unsupported dynamic clause. Added a suppression clause: when the only dynamic marker is the damage-double phrase and the parsed replacement carriesDamageModification::Double | Triple, do not warn (CR 614.1a + CR 701.10g). Deduplicated the triplicated 8-phrase marker list into a single module-scopeOTHER_DYNAMIC_MARKERSconst.Verification
All commands run at the committed head
f62acde5f(rebased onto upstream/mainb387f9c66)../scripts/check-parser-combinators.shcargo test -p engine --lib -- fated_firepower neriv ojer_axonil dynamic_qty_…_damage_double twice_is_only_dynamic_markercargo test -p engine --test integration -- neriv_entered_this_turn_source_doubles neriv_prior_turn_source_not_doubled fated_firepower_offset_scales_with_fire_counterscargo clippy -p engine --all-targetsRuntime discriminators (the honesty arbiter):
neriv_entered_this_turn_source_doubles— source that entered this turn deals 2 → 4 marked damage (Doubleapplied).neriv_prior_turn_source_not_doubled— source present since a prior turn deals 2 → 2 (replacement'sEnteredThisTurnsource filter correctly excludes it; CR 400.7).fated_firepower_offset_scales_with_fire_counters— base 2 + 3 fire counters → 5 (Plus { value = fire-counters-on-source }resolves dynamically).Coverage delta — isolated measurement. Measured at base
d59d1c3e2where base and tip differ only by this 4-file diff (perfect isolation of this change): exactly{Fated Firepower, Neriv, Heart of the Storm}False→True,0True→False,0appeared/disappeared.supported_cards31394 → 31396.Rebase-invariance of that delta (base advanced 10 commits to
b387f9c66): established by measurement + causal orthogonality — (1) no upstream commit in the range touchesparser/oracle_replacement.rsorparser/swallow_check.rs(this change's only production edits); (2) the one upstream commit that touches a dependency of this change, #6045 (parse_cda_quantity), is loyalty-counter-only (~'s loyalty→CountersOn{Source, Loyalty}), disjoint from Fated Firepower's fire-counter quantity — and the post-rebase lib testfated_firepower_assembled_replacementconfirms FF still parses toPlus{fire-counters}. The card-data coverage-regression CI check runs the authoritative clean diff against upstream/main.Gate A
Gate A PASS head=f62acde5fbf46516f08db6cddea0e4387d480da2 base=be22e078bb1b05661d1ce34a21f3fbc07a136cab
Anchored on
crates/engine/src/parser/oracle_replacement.rs—parse_damage_modification_phrasealready maps"double that damage"/"deals double that damage"→DamageModification::Double; the new"twice that much damage"leaf is a sibling in the samealt.crates/engine/src/parser/oracle_replacement.rs—parse_that_much_damage_offsetalready maps numeric offsets toDamageModification::Plus; the new dynamic arm mirrors it, delegating the quantity toparse_cda_quantity.Final review-impl
Final review-impl PASS head=f62acde5fbf46516f08db6cddea0e4387d480da2
Claimed parse impact