fix(parser): scope "can't attack you or block creatures you control" instead of collapsing to self (#5607)#5878
Conversation
…reatures you control" (phase-rs#5607) Storm, Windrider ("Creatures with flying can't attack you or block creatures you control") could not attack: the whole line parsed to a self-scoped blanket `CantAttack` on Storm herself. The single-clause "<subject> can't attack you" already parses correctly (defender-scoped `CantAttack`, affected = the subject). The trailing "or block creatures you control" made that subject parser reject (unconsumed remainder), and the generic dispatch arm then fell back to `CantAttack { affected: SelfRef }` — a blanket restriction that stopped the source creature from attacking at all. Add a dedicated compound parser in the multi-static dispatch that emits the two correctly-scoped statics the line actually means (CR 508.1c + CR 509.1b): 1. a defender-scoped `CantAttack` (attack_defended = "you") — the subject can't attack the source's controller but may still attack anyone else; 2. a `BlockRestriction` whose filter is the NEGATION of the block target — "can block only things that are NOT creatures you control" is exactly "can't block creatures you control" (the CR 509.1b enforcement in combat.rs allows a block only when the attacker matches the filter). Both are scoped to the subject filter and the block target is parsed by the full type-phrase grammar, so this is a building block for the defensive-flyer class, not a single card. The enforcement side already handles defender-scoped attack restrictions and negated block filters, so no runtime change is required. Verified: cargo test -p engine --lib -> 16675 passed, 0 failed. New tests assert the compound yields two scoped statics (never SelfRef) and that the plain single-clause "can't attack you" is unchanged.
There was a problem hiding this comment.
Code Review
This pull request introduces parser support for the compound combat restriction "can't attack you or block creatures you control" (CR 508.1c + CR 509.1b) by adding the parse_cant_attack_you_or_block_predicate and parse_subject_cant_attack_you_or_block_static functions, and integrating them into the multi-dispatch pipeline. The review feedback suggests adding a defensive check to explicitly reject TargetFilter::Any when parsing the block filter to prevent constructing invalid block restrictions.
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.
Parse changes introduced by this PR · 1 card(s), 2 signature(s) (baseline: main
|
matthewevans
left a comment
There was a problem hiding this comment.
Approved after current-head implementation review.
matthewevans
left a comment
There was a problem hiding this comment.
Approved after current-head implementation review.
Maintainer port preserves the approved Storm parser while retaining main's independent static-parser additions. Co-authored-by: philluiz2323 <philluiz2323@users.noreply.github.com>
matthewevans
left a comment
There was a problem hiding this comment.
Approved after rechecking the maintainer conflict-resolution merge at current head de0145bb29faf8c9f795e0b397bcd9f881580317.
Summary
Fixes #5607. Storm, Windrider — "Creatures with flying can't attack you or block creatures you control" — could not attack. The line collapsed to a self-scoped blanket
CantAttackon Storm herself.Implementation method (required)
/engine-implementerpipeline/engine-implementer— explain why belowRoot cause
The single-clause "
<subject>can't attack you" already parses correctly — a defender-scopedCantAttack(attack_defended = you) affecting the subject. Storm's trailing "or block creatures you control" left an unconsumed remainder, so that subject parser rejected the line, and the generic dispatch arm fell back to:attack_defended: Noneis a blanket "can't attack" (combat.rscreature_cant_attack_gated), andaffected: SelfRefpoints it at Storm — so the source creature itself couldn't attack.Fix
A dedicated compound parser in the multi-static dispatch emits the two statics the line actually means (CR 508.1c + CR 509.1b):
CantAttack(attack_defended = you) — the subject can't attack the source's controller, but may still attack anyone else; andBlockRestrictionwhose filter is the negation of the block target.BlockRestriction { filter }means "can block only creatures matchingfilter" (combat.rs:1482), sofilter: Not(creatures you control)is exactly "can't block creatures you control".Both are scoped to the subject filter, and the block target is parsed by the full
parse_type_phrasegrammar, so this is a building block for the defensive-flyer class rather than one card. The enforcement side already handles defender-scoped attack restrictions andBlockRestriction, so no combat-code change is needed.CR references
CR 508.1c— a defender-scoped attack restriction leaves the creature able to attack another target.CR 509.1b— blocker-side "can block only<filter>" restrictions.Verification
cargo test -p engine --lib→ 16675 passed, 0 failed (static-parser changes can affect many cards).CantAttackand a negatedBlockRestriction), neverSelfRef; and the plain single-clause "can't attack you" is unchanged.cargo fmt --allclean.