fix(engine): Eriette of the Charmed Apple scoped attack restriction (#2030) - #2261
Conversation
…hase-rs#2030) Parse enchanted-by-your-aura subjects and defend-you/planeswalker scopes instead of mis-emitting SelfRef CantAttack on Eriette. Enforce scoped CantAttack at declare attackers and keep enchanted creatures eligible against other players.
There was a problem hiding this comment.
Code Review
This pull request implements support for scoped attack prohibitions (CR 508.1d), such as those on "Eriette of the Charmed Apple", by adding an attack_defended field to StaticDefinition and checking it during attacker validation and static ability resolution. It also updates the parser to handle subject-led combat restrictions and attachment-relative clauses. Feedback on this PR points out a violation of Rule R1 in evasion.rs, where string manipulation (contains and find) is used for parsing dispatch instead of nom combinators, and suggests a nom-based refactoring.
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.
Use scan_preceded for scoped cant-attack parsing (R1), run rustfmt, and add attack_defended to phase-ai StaticDefinition test literals.
🤖 Architecture Review (automated)Verdict: Seam: PASS —
Findings
Notes (verified, no action): CR 508.1d, 508.1h, 506.3, 303.4, 301.5 all confirmed against |
Resolve combat.rs test conflict: keep Eriette scoped-attack test and upstream Predatory Impetus MustBeBlocked enforcement test.
Thanks for the architecture review — agreed on the [HIGH] finding. Removed the duplicate string dispatch in parse_subject_combat_rule_static. Subject-led lines now use a single scan_preceded(&lower, parse_combat_rule_static_predicate_with_defended_nom), which threads Option from parse_cant_attack_rule_static_predicate_nom / parse_cant_attack_defended_scope_nom into .attack_defended(...). parse_rule_static_predicate_nom now composes the same combinator (dropping scope only where callers need a bare RuleStaticPredicate). Reordered the combat alt so can't attack or block precedes can't attack, fixing regressions where partial can't attack matches left or block and fell through to SelfRef (e.g. enchanted-creature auras). Runtime behavior unchanged: attack_defended still enforced at declare attackers and skipped in eligibility when no target is in context. Happy to take another look. |
Replace strip_suffix enumeration in strip_attachment_relative_clause with composed take_until/tag/value parsers so PR phase-rs#2261 passes the combinator gate.
|
Pushed maintainer follow-up in What changed:
Verification run locally with the shared target dir:
I am leaving broad validation to GitHub CI. |
|
Pushed What changed:
Re-ran focused verification:
|
matthewevans
left a comment
There was a problem hiding this comment.
Maintainer review: approved after follow-up in b633e2e and latest-main merge in 82c0386.
Evidence checked:
- Correct seam: scoped attack restriction state is parsed into
StaticDefinition.attack_defendedand enforced in combat declaration/static checks, not patched in the UI or a card-specific path. - Idiomatic at the seam: the author replaced the earlier string parser dispatch with the existing nom static-combat predicate path; runtime scope matching uses the shared restrictions helper.
- Discriminating coverage: the updated Eriette runtime test now uses three players and proves the enchanted creature can attack another player while still being blocked from attacking the restricted defender scope.
- Verification: GitHub CI is green on the latest head; local focused test
eriette_scoped_cant_attack_blocks_only_defender_scopealso passed with the shared target dir.
Summary
Fixes #2030 — Eriette of the Charmed Apple was not enforcing its combat restriction or was mis-parsed.
Root cause: Subject-led lines like
Each creature that's enchanted by an Aura you control can't attack you or planeswalkers you control.failedparse_subject_combat_rule_static(trailing defended scope left unconsumed), then fell through to the~ can't attackdispatcher and became a SelfRefCantAttackon Eriette herself. Scoped attack prohibitions also were not stored or enforced at declaration time.Changes:
that's enchanted by an Aura you controlviastrip_attachment_relative_clause+HasAttachmenton the subject filter.attack_defendedonStaticDefinitionfor flatcan't attack you [or planeswalkers you control].CantAttackindeclare_attackersviaStaticCheckContext::attack_target; skip scoped statics in attacker eligibility queries when no target is known.restrictions::attack_target_matches_defended_scopefor combat tax and flat restrictions.Test plan
cargo test -p engine --lib eriette_charmed_apple_static_and_trigger_parsecargo test -p engine --lib eriette_scoped_cant_attack_blocks_only_defender_scope