fix(engine): scope "attacks the monarch" triggers + bind destroy target to the monarch (The Spear of Bashenga #5249)#6162
Conversation
|
🚨 Contributor flagged. Click here for more info: Superagent Dashboard |
There was a problem hiding this comment.
Code Review
This pull request implements support for the 'attacks the monarch' trigger condition (Issue #5249, e.g., for 'The Spear of Bashenga'). It introduces the AttackTargetFilter::Monarch enum variant, updates the parser to recognize 'the monarch' as an attack target and reference player, implements the stateful runtime check in attack_target_matches against state.monarch, and adds comprehensive unit and integration tests. I have no feedback to provide as the implementation is highly idiomatic, conforms to the MTG Comprehensive Rules, and includes thorough test coverage.
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.
|
Authorized maintainer handling attempted to bring The only textual conflict is the rustfmt-sorted module registry at |
…target to the monarch (The Spear of Bashenga phase-rs#5249) The Spear of Bashenga: "Whenever equipped creature attacks the monarch, destroy target tapped nonland permanent that player controls." The parser's `parse_attack_target` had no " the monarch" arm, so the trigger degraded to a bare `TriggerMode::Attacks` with no attack-target scope — it fired on EVERY attack and prompted for a destroy target regardless of who was the monarch. Two-part fix (both class-level building blocks, not one-offs): 1. New `AttackTargetFilter::Monarch` — a Player-type attack whose defending player must currently hold the monarch designation. Because the monarch is a dynamic single-player identity (CR 725.1) the pure type matcher can't see it, so `attack_target_type_matches` treats `Monarch` as a Player attack and the identity check is applied statefully in `attack_target_matches` against `state.monarch` (both `Some` and equal; no monarch → never fires). Parser arm `tag(" the monarch")` in `parse_attack_target`. 2. The "that player controls" destroy target was mis-bound. "attacks the monarch ... that player controls" parsed with `controller: You` (the Spear's controller = the ATTACKER), so the destroy would have hit the attacker's own tapped permanents — a wrong-player-target bug worse than the original. `parse_referenced_player_phrase` (the antecedent binder feeding `DefendingPlayer`) had no "the monarch" arm, so "that player" never bound to the defender. Added `tag("the monarch")` there so "that player" binds to `ControllerRef::DefendingPlayer` (the monarch IS the attacked player, CR 508.1a + CR 725.1) — also covers "deals combat damage to the monarch ... that player" for the whole class. `AttackTargetFilter::Monarch` is additive; all exhaustive matches carry explicit arms (no wildcards). CR annotations (508.1a, 725.1) grep-verified. Tests (each revert-probed): - parser shape: "attacks the monarch" → `attack_target_filter = Monarch`, `valid_card` = the equipped-creature subject, and the destroy target is controlled by `DefendingPlayer` (guards the wrong-target fix), zero Unimplemented; - matcher unit test: monarch defender → matches; non-monarch → no; no monarch → no; - integration (real combat): attacking the monarch fires the trigger; attacking a non-monarch does NOT fire (the reported bug — disabling the stateful check makes this fire, confirmed); no monarch → no fire. Curse "attacks enchanted player" + Curse of Predation regressions confirm no antecedent-binding collateral. Full lib: 17065 passed. Integration: 3412 passed. Clippy clean. Fixes phase-rs#5249. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
19c78a7 to
f58408a
Compare
Parse changes introduced by this PR · 1 card(s), 1 signature(s) (baseline: main
|
matthewevans
left a comment
There was a problem hiding this comment.
Request changes — the current head does not compile, and the required current-head engine parse-diff evidence is absent.
🔴 Blockers
-
[HIGH] The new matcher unit test passes the wrong argument type.
attack_target_matchesrequires&TriggerSourceContext, but all three calls passsource_id: ObjectId(crates/engine/src/game/trigger_matchers.rs:5020-5025, 5032-5037, 5046-5051; CIE0308). Use the existingtest_trigger_source_context(&state, source_id)helper at:4924-4945and pass a reference to that context. The Rust lint and both Rust test shards fail on this exact error. -
[HIGH] This engine/parser PR has no current parse-diff artifact. The PR has no
<!-- coverage-parse-diff -->comment despite changed trigger/parser sources and CI having run. Regenerate the artifact for this head and account for its card-level changes before the PR can be approved.
Gemini's clean review applies only to the superseded 19c78a7 head; it does not cover this failing head. I did not run local builds.
Recommendation: fix the compile error, regenerate current-head parse evidence, and request re-review.
Fixes #5249 (claimed there). Verified The Spear of Bashenga's Oracle text on Scryfall and reproduced end-to-end before building.
The bug
"Whenever equipped creature attacks the monarch, destroy target tapped nonland permanent that player controls." The parser's
parse_attack_targethad no" the monarch"arm, so the trigger degraded to a bareTriggerMode::Attackswith no attack-target scope — it fired on every attack and prompted for a destroy target regardless of who was the monarch.Two-part fix (both class-level building blocks)
1.
AttackTargetFilter::Monarch— a Player-type attack whose defending player must currently hold the monarch designation. The monarch is a dynamic single-player identity (CR 725.1) the pure type matcher can't see, soattack_target_type_matchestreatsMonarchas a Player attack and the identity check is applied statefully inattack_target_matchesagainststate.monarch(bothSomeand equal; no monarch → never fires). Parser armtag(" the monarch").2. The destroy target was mis-bound — a wrong-player-target bug the fix caught. "attacks the monarch ... that player controls" parsed with
controller: You— the Spear's controller, i.e. the attacker — so the destroy would have hit the attacker's own tapped permanents, strictly worse than the original bug. Root cause:parse_referenced_player_phrase(the antecedent binder feedingDefendingPlayer) had no "the monarch" arm, so "that player" never bound to the defender. Addedtag("the monarch")there so "that player" binds toControllerRef::DefendingPlayer(the monarch is the attacked player, CR 508.1a + CR 725.1) — also covers "deals combat damage to the monarch ... that player" for the whole class.AttackTargetFilter::Monarchis additive; every exhaustive match carries an explicit arm (no wildcards). CR annotations (508.1a, 725.1) grep-verified.Tests (each revert-probed)
attack_target_filter = Monarch,valid_card= the equipped-creature subject, and the destroy target is controlled byDefendingPlayer(guards the wrong-target fix), zeroUnimplemented.parse_referenced_player_phrasechange causes no antecedent-binding collateral.Verification
Full engine lib: 17065 passed, 0 failed. Integration: 3412 passed, 0 failed. Clippy clean; fmt clean; parser combinator gate PASS.
🤖 Generated with Claude Code