Skip to content

Fix Vigor prevention scope and counter follow-up - #1426

Closed
monsterdavidliu-ux wants to merge 2 commits into
phase-rs:mainfrom
monsterdavidliu-ux:fix/vigor-damage-prevention-1417
Closed

Fix Vigor prevention scope and counter follow-up#1426
monsterdavidliu-ux wants to merge 2 commits into
phase-rs:mainfrom
monsterdavidliu-ux:fix/vigor-damage-prevention-1417

Conversation

@monsterdavidliu-ux

Copy link
Copy Markdown
Contributor

Summary

Test plan

  • cargo test -p engine vigor_prevention_scopes_to_other_creatures_you_control
  • cargo test -p engine --test integration vigor

Parse "another creature you control" as valid_card on damage prevention
replacements, and route the +1/+1 counter rider to the prevented recipient.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements damage prevention replacement parsing and scoping logic to correctly handle cards like Vigor, including adding comprehensive unit and integration tests. A high-severity issue was identified in the parser where a manual string prefix check (.starts_with(',')) was used instead of idiomatic nom combinators, violating Rule R1 of the style guide.

Comment on lines +4828 to +4830
if rest.starts_with(',') {
return true;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

[HIGH] Avoid verbatim string or character checks for parsing Oracle phrases as it bypasses the robust nom-based parser.

Why it matters: Bypassing the nom-based parser with manual string/character checks creates fragile matches. Instead, decompose compound phrases into modular, reusable parsers and compose them using idiomatic combinator aggregates (like nested alt and tag sequences) to prevent combinatorial explosion and improve maintainability.

Suggested fix: Use nom combinators to parse the delimiter.

let (rest, _) = nom::character::complete::char(',')(rest)?;
References
  1. Rule R1: Every new parser dispatch under crates/engine/src/parser/ must use nom 8.0 combinators or delegate to existing helpers. Any new .contains(), .starts_with(), .ends_with(), .find(), or .split_once() used for parsing dispatch in non-test parser code is a finding. (link)
  2. Avoid verbatim string equality for parsing Oracle phrases as it bypasses the robust nom-based parser and creates fragile matches. Instead, decompose compound phrases into modular, reusable parsers for constituent parts and compose them using idiomatic combinator aggregates to prevent combinatorial explosion and improve maintainability.

Replace starts_with(',') with char(',').parse() so the trailing-comma
case stays correct for ", prevent that damage" while satisfying parser R1.
matthewevans added a commit to mike-theDude/phase that referenced this pull request May 29, 2026
…ation lens)

Graft phase-rs#1426's runtime integration tests (vigor_regression.rs) onto this PR
so the prevention scope + counter-recipient behavior is driven through the
real apply() pipeline, not just parser-AST shape. The runtime paths
(valid_card gate in replacement.rs, PostReplacementDamageTarget in
targeting.rs:636) were only exercised by sibling cards before.

Tighten vigor_event_recipient_filter_and_counter_target_rewrite to pin
count == EventContextAmount (the field try_parse_for_each_effect sets for
the suffix-form for-each) instead of the loose repeat_for-OR-count
disjunction that did not discriminate which field carries the amount.
@matthewevans matthewevans mentioned this pull request May 29, 2026
@matthewevans

Copy link
Copy Markdown
Member

Thanks for this, @monsterdavidliu-ux — your diagnosis of Vigor was correct and your integration tests are excellent. I'm leaving this PR open so you can see the reasoning, but I want to be transparent: I'm merging the Vigor fix via #1425 instead, for an architectural reason.

Both PRs make Vigor work, but they fix it at different seams:

  • This PR adds is_unscoped_creature_typed_filter, a heuristic that rewrites any unscoped Typed(Creature) in a prevention follow-up. That fixes Vigor, but it's a symptom-level patch — it doesn't address the root cause, and it leaves sibling cards (grave strength, dig deep, sacred boon — "put a counter on that creature for each …") still resolving "that creature" to an arbitrary creature.
  • The actual root cause is that try_parse_for_each_effect (oracle_effect/mod.rs) is the one SubjectApplication consumer that drops app.inherits_parent, while every other consumer (e.g. build_pump_effect) honors it. Add Vigor #1425 fixes that one site, so "that creature" lowers to ParentTarget for the whole class — Vigor and all its siblings.

Because both PRs edit the same region of oracle_replacement.rs, they can't both merge. So #1425 carries the seam fix.

Your work isn't lost: your two pipeline integration tests (vigor_regression.rs) were grafted onto #1425 with attribution — they're stronger than the parser-AST assertions #1425 had, and they're what verifies Vigor's runtime behavior end-to-end. Genuinely appreciate the contribution, and I'd love to see more — the testing instinct here is exactly right. 🙏

@monsterdavidliu-ux

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed explanation and for preserving the integration tests — I really appreciate it. The architectural reasoning makes sense, and I’m glad the tests could still contribute to the final fix. Happy to contribute more in the future 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Vigor — damage prevention applies to all creatures (missing "you control") and +1/+1 counter rider doesn't fire

2 participants