fix(parser): scope "that player" to triggering player for Aura/Equipment damage triggers - #6540
Conversation
…ent damage triggers
Article-less damage-source subjects ("enchanted creature" / "equipped
creature") failed the DamageDone-pattern check in
`parse_damage_source_subject` (it required a leading article), so a later
"that player controls/'s" anaphor mis-bound to `ControllerRef::TargetPlayer`
— surfacing a phantom companion Player target slot that froze the game
(Sigil of Sleep). Recognize the two determiner subjects as
`TargetFilter::AttachedTo` so the DamageDone pattern matches and the
anaphor binds to `TriggeringPlayer`.
Covers Sigil of Sleep, Hammer of Ruin, Fishing Gear, Whispersteel Dagger.
Komainu Battle Armor's compound "this creature or equipped creature"
subject is a separate follow-up.
CR 303.4b + CR 301.5a + CR 120.3a.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThe parser now recognizes article-less “enchanted creature” and “equipped creature” damage subjects. Unit and integration tests verify that “that player” resolves to the damaged player across target selection, equipment destruction, life changes, and hand-size calculations. ChangesOracle trigger parsing
Estimated code review effort: 3 (Moderate) | ~25 minutes Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Parse changes introduced by this PR · 6 card(s), 4 signature(s) (baseline: main
|
matthewevans
left a comment
There was a problem hiding this comment.
Requested change
P1 — Explain or constrain the parser blast radius
The fresh coverage-parse-diff shows parser-output changes for four cards outside this PR’s claimed Sigil of Sleep / Hammer of Ruin scope: Sword of Fire and Ice, Sword of War and Peace, Quietus Spike, and Scytheclaw.
Please either narrow the grammar so these changes do not occur, or document why each change is intended and add card-level runtime coverage for the affected behavior. Re-run the parse diff on the updated head.
This is blocking because parse_damage_source_subject is shared damage-trigger grammar, and the current PR does not provide evidence that those additional behavior changes are correct.
…on-only parse changes The parser fix changed six cards' parse output. This adds runtime coverage that classifies each one by re-running the suite with the fix reverted: * Behavioral (fails when the fix is reverted) — Hammer of Ruin's "target Equipment that player controls" produced TWO target slots (the phantom companion player slot behind the issue phase-rs#4240 softlock). * Representation-only (passes either way) — Quietus Spike, Scytheclaw and Sword of War and Peace (and Sword of Fire and Ice and War and Peace, which prints the same clause). During trigger resolution QuantityContext::scoped_player is already bound to the damaged player, so the pre-fix PlayerScope::ScopedPlayer and the post-fix PlayerScope::Target resolve to the same player. These are kept as invariance tests: standing evidence that the shared-grammar edit changed no behavior for them. Every test is discriminating by construction — controller and damaged player get different life totals / hand sizes, so a controller-bound anaphor would assert-fail. CR 120.3a + CR 301.5a + CR 603.2. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Thanks — that was the right call to block on. I reproduced the parse diff locally and it matches CI exactly (6 cards, 4 signatures), then wrote runtime coverage that separates the changes into two classes. Short version: 2 of the 6 are behavioral (the fix), and the other 4 are representation-only with provably identical runtime behavior. Evidence: run the new tests against the fix, then against the fix revertedNew file:
Why the 4 are inert at runtime: during trigger resolution I kept those three tests deliberately even though they pass either way, and labelled them invariance tests in the module docs — they are the standing evidence that this shared-grammar edit did not alter what those cards do. Each is discriminating by construction (controller and damaged player are given different life totals / hand sizes, so a controller-bound reading would assert-fail):
On narrowingI looked at narrowing the grammar so the 4 don't move. It isn't reachable without giving up the fix: the same Also verified the card Oracle text against Scryfall for all six (the "their life" / "their hand" readings above), per the verify-the-card rule. Parse diff has been re-run on the updated head — unchanged at 6 cards / 4 signatures. |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
crates/engine/tests/integration/issue_4240_damaged_player_anaphor_runtime.rs (1)
96-105: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueWildcard fallback in the
WaitingFormatch.
other => panic!(...)at Line 104 sidesteps exhaustive matching onWaitingFor, so a newly added variant wouldn't be caught at compile time. It fails loudly at runtime instead, which is acceptable for a test helper, but an exhaustive match with an explicit panic arm per remaining variant would align with the crate's exhaustive-match convention.As per coding guidelines, "Use exhaustive matches without wildcard fallbacks for known enums so the compiler detects missing variants."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/engine/tests/integration/issue_4240_damaged_player_anaphor_runtime.rs` around lines 96 - 105, Update the WaitingFor match in the trigger-resolution helper to remove the wildcard other arm and enumerate every remaining WaitingFor variant with explicit panic arms. Preserve the existing Priority handling and panic behavior, allowing future enum variants to produce compile-time exhaustiveness errors.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In
`@crates/engine/tests/integration/issue_4240_damaged_player_anaphor_runtime.rs`:
- Around line 96-105: Update the WaitingFor match in the trigger-resolution
helper to remove the wildcard other arm and enumerate every remaining WaitingFor
variant with explicit panic arms. Preserve the existing Priority handling and
panic behavior, allowing future enum variants to produce compile-time
exhaustiveness errors.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 150d7fff-f8ac-44d6-903f-34703a3340e5
📒 Files selected for processing (2)
crates/engine/tests/integration/issue_4240_damaged_player_anaphor_runtime.rscrates/engine/tests/integration/main.rs
🚧 Files skipped from review as they are similar to previous changes (1)
- crates/engine/tests/integration/main.rs
…h target selection The test added in 17a1018 could not run: it failed to compile, and once compiling it did not reach the window it asserts on. Three minimal changes, all preserving the test's original intent: 1. E0308 — the `Priority` match arm evaluated to the `ActionResult` returned by `.expect(...)`, but the match sits in statement position and must be `()`. The sibling `break` / `panic!` arms are `!` and coerce to anything, so they never reconciled it. Wrapped the arm in a block with a trailing semicolon to discard the value. 2. With a single legal target the engine auto-selects and never opens `WaitingFor::TriggerTargetSelection`, so the loop passed priority until the damaged player decked out and the test saw `GameOver`. Gave the damaged player a second creature so a genuine choice exists and the window opens. 3. Widened the legal-target assertion to both of the damaged player's creatures, which the previous hardcoded single-target expectation missed. The load-bearing assertions are unchanged and still pass: exactly ONE target slot (no phantom companion player slot, the issue phase-rs#4240 softlock) and the Aura controller's own creature excluded from "that player controls". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Pushed The test couldn't run as written: it failed to compile, and once compiling it didn't reach the window it asserts on. Three minimal changes, all preserving the original intent. 1. The CI error (E0308). The WaitingFor::Priority { .. } => {
runner
.act(GameAction::PassPriority)
.expect("passing priority must advance Sigil's trigger");
}2. It never reached target selection. With only one legal target the engine auto-selects and never opens 3. Widened the legal-target assertion to both of the damaged player's creatures — the hardcoded single-target expectation was the last failure once the window opened. The load-bearing assertions are untouched and still pass: exactly one target slot (no phantom companion player slot — the #4240 softlock), and the Aura controller's own creature still excluded from All five One process note on my side: my earlier "full suite green" claim was run in a checkout that didn't contain your test file, so I never compiled it — CI caught what I should have. I've since been building the branch itself, which is how both defects above surfaced. |
matthewevans
left a comment
There was a problem hiding this comment.
Maintainer approval: current head bcfac8f is clean at the parser seam; its runtime regression coverage and required CI are green.
Fixes #4240.
Problem
Sigil of Sleep froze the game (p0 softlock — the "set resolve order" prompt could not be actioned). Its trigger — "Whenever enchanted creature deals damage to a player, return target creature that player controls to its owner's hand" — surfaced two target slots (a Player and a creature) instead of one. The phantom Player slot has no meaningful prompt, so the client hung waiting on it.
Root cause
parse_damage_source_subject(inparser/oracle_trigger.rs) required a leading article, so the article-less Aura/Equipment subjects"enchanted creature"/"equipped creature"failed the strictis_damage_done_trigger_patterncheck and fell through tocondition_introduces_target_player. That mis-bound the effect's"that player controls"anaphor toControllerRef::TargetPlayer— whose documented behavior is to auto-surface a companionPlayertarget slot — instead ofControllerRef::TriggeringPlayer(the damaged player identified by the triggering event).Fix
Recognize the two article-less determiner subjects at the top of
parse_damage_source_subject, resolving them toTargetFilter::AttachedTo(which is also the correctvalid_sourcefor the source-led trigger path — notTyped(Enchanted/EquippedBy), which would match any attached creature). TheDamageDonepattern now matches, sorelative_player_scope_for_conditionreturnsTriggeringPlayerand the anaphor binds correctly — a single creature target scoped to the damaged player.Scope (built for the class, not the card)
Fixes Sigil of Sleep, Hammer of Ruin, Fishing Gear, Whispersteel Dagger. Komainu Battle Armor's compound
"this creature or equipped creature"subject is a disjunctive variant left for a follow-up (unchanged here — no regression).Tests
relative_player_scope_for_conditionasserting both determiner subjects →TriggeringPlayer(covers the class).TriggeringPlayer-scoped creature target (was red before the fix).clippy -D warningsclean,cargo fmtapplied.CR 303.4b + CR 301.5a + CR 120.3a.
🤖 Generated with Claude Code
Summary by CodeRabbit