Skip to content

test(engine): lock Bloodthirsty Blade opponent-equip goad (#5973) - #6605

Closed
claytonlin1110 wants to merge 1 commit into
phase-rs:mainfrom
claytonlin1110:fix/5973-bloodthirsty-blade-goad-regression
Closed

test(engine): lock Bloodthirsty Blade opponent-equip goad (#5973)#6605
claytonlin1110 wants to merge 1 commit into
phase-rs:mainfrom
claytonlin1110:fix/5973-bloodthirsty-blade-goad-regression

Conversation

@claytonlin1110

@claytonlin1110 claytonlin1110 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Test plan

  • cargo test -p engine --test integration bloodthirsty_blade_goad_5973 — 7/7 passed
  • CI green on this PR

Summary by CodeRabbit

  • Bug Fixes
    • Improved handling of Bloodthirsty Blade’s goad behavior.
    • Corrected equipment attachment, combat targeting, attack restrictions, and stat bonuses across two- and three-player games.
    • Prevented errors when activating the equipment’s ability and applying its effects.

…e-rs#5973)

The Discord report was a combat softlock when the Blade goaded an
opponent's creature in two-player — the only legal attack target is the
goading player, so the away-from clause is unsatisfiable. That class was
fixed by the CR 508.1d requirement solver (phase-rs#6164); these regression tests
pin parse shape, activate-attach, pump, must-attack, and 2p/3p attack
legality for the card.

Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds integration coverage for Bloodthirsty Blade’s oracle parsing, opponent-creature attachment, +2/+0 modification, goad attack constraints, and activated ability resolution across two- and three-player scenarios.

Changes

Bloodthirsty Blade regression coverage

Layer / File(s) Summary
Scenario setup and effect parsing
crates/engine/tests/integration/bloodthirsty_blade_goad_5973.rs
Adds reusable game setups and verifies the parsed goad static and opponent-creature attach ability.
Goad combat constraints
crates/engine/tests/integration/bloodthirsty_blade_goad_5973.rs
Tests the +2/+0 effect, required attacks, and valid attack targets in two- and three-player games.
Activated attachment resolution
crates/engine/tests/integration/bloodthirsty_blade_goad_5973.rs, crates/engine/tests/integration/main.rs
Executes the attach ability, verifies attachment state and battlefield placement, and confirms resulting goad behavior while registering the integration test module.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • phase-rs/phase#6319: Reworks goad state and attack-away-from-source requirements covered by this regression suite.

Suggested labels: bug

Suggested reviewers: matthewevans

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: a Bloodthirsty Blade goad regression test for opponent-equipment behavior.
Linked Issues check ✅ Passed The new regression tests cover opponent attachment, goad parsing, +2/+0, and attack legality in two- and three-player cases, matching #5973.
Out of Scope Changes check ✅ Passed The changes are limited to adding focused integration tests and registering the module, with no unrelated code changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@matthewevans matthewevans self-assigned this Jul 24, 2026

@matthewevans matthewevans left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review: test coverage is substantive at 60b34e06bc17fd4e9f06553c83fdc98ebfa48df1

No code findings.

  • The inline Oracle text matches the current Scryfall printing, and no existing Bloodthirsty Blade regression test covers this opponent-controlled host.
  • The 2-player pair reaches validate_attack_declaration, the CR 508.1d maximum-requirement authority: attacking the sole possible target (the goader) is legal, while declaring no attacker fails. Either a missing generic goad requirement or an over-strict away-from requirement makes one of those assertions fail.
  • The 3-player pair is a source-attribution discriminator: P0 (the Blade controller) must be rejected and P2 accepted. It exercises the actual StaticMode::Goaded scan, whose goading player is the static carrier controller.
  • The activation test uses the normal attach resolver; the parse-shape and layer assertions cover the former double-graft failure surface without relying only on direct state setup.

CI is not clear yet on this exact head: Rust lint, both Rust test shards, and CodeRabbit are still pending. I am recording this as a clean implementation review only; it is not an approval or merge-queue action.

@matthewevans matthewevans removed their assignment Jul 24, 2026

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🧹 Nitpick comments (1)
crates/engine/tests/integration/bloodthirsty_blade_goad_5973.rs (1)

55-91: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate scenario builders — parameterize instead.

setup_two_player and setup_three_player are identical except for the GameScenario::new() vs. GameScenario::new_n_player(3, 42) call. Consider collapsing into one helper taking the scenario constructor or player count as a parameter to avoid future drift between the two copies.

♻️ Proposed consolidation
-fn setup_two_player() -> (engine::game::scenario::GameRunner, ObjectId, ObjectId) {
-    let mut scenario = GameScenario::new();
-    scenario.at_phase(Phase::DeclareAttackers);
-    ...
-}
-
-fn setup_three_player() -> (engine::game::scenario::GameRunner, ObjectId, ObjectId) {
-    let mut scenario = GameScenario::new_n_player(3, 42);
-    scenario.at_phase(Phase::DeclareAttackers);
-    ...
-}
+fn setup(mut scenario: GameScenario) -> (engine::game::scenario::GameRunner, ObjectId, ObjectId) {
+    scenario.at_phase(Phase::DeclareAttackers);
+    let host = scenario.add_creature(P1, "Opponent Bear", 2, 2).id();
+    let blade = scenario
+        .add_creature(P0, "Bloodthirsty Blade", 0, 0)
+        .as_artifact()
+        .with_subtypes(vec!["Equipment"])
+        .from_oracle_text(BLOODTHIRSTY_BLADE)
+        .id();
+    let mut runner = scenario.build();
+    attach(&mut runner, blade, host);
+    refresh(&mut runner);
+    runner.state_mut().active_player = P1;
+    (runner, blade, host)
+}
+// callers: setup(GameScenario::new()) / setup(GameScenario::new_n_player(3, 42))
🤖 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/bloodthirsty_blade_goad_5973.rs` around lines
55 - 91, Consolidate setup_two_player and setup_three_player into a single
parameterized scenario builder, such as a helper accepting the desired player
count or scenario constructor. Preserve the shared creature, equipment,
attachment, refresh, and active-player setup, while selecting GameScenario::new
for two players and GameScenario::new_n_player(3, 42) for three players; update
callers to use the unified helper.
🤖 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.

Inline comments:
In `@crates/engine/tests/integration/bloodthirsty_blade_goad_5973.rs`:
- Around line 199-207: Strengthen the assertion for host in
attacker_constraints_for_active_player by destructuring
CombatRequirement::MustAttack and validating its away-from-goader payload
identifies P0 as the player to avoid, including the expected two-player
exemption behavior. Mirror the corresponding checks in
validate_attack_declaration instead of accepting any MustAttack variant.
- Around line 158-171: Strengthen the attach-target assertion in the Typed
target-filter branch to verify both opponent control and the creature-type
restriction expressed by the Oracle text. Use the existing type-related field or
predicate on tf, while preserving the current failure messages and rejection of
non-Typed filters.

---

Nitpick comments:
In `@crates/engine/tests/integration/bloodthirsty_blade_goad_5973.rs`:
- Around line 55-91: Consolidate setup_two_player and setup_three_player into a
single parameterized scenario builder, such as a helper accepting the desired
player count or scenario constructor. Preserve the shared creature, equipment,
attachment, refresh, and active-player setup, while selecting GameScenario::new
for two players and GameScenario::new_n_player(3, 42) for three players; update
callers to use the unified helper.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 69fcbbbe-e0e5-4a29-970e-9c6c92536a93

📥 Commits

Reviewing files that changed from the base of the PR and between 731cffc and 60b34e0.

📒 Files selected for processing (2)
  • crates/engine/tests/integration/bloodthirsty_blade_goad_5973.rs
  • crates/engine/tests/integration/main.rs

Comment on lines +158 to +171
let Effect::Attach { target, .. } = attach.effect.as_ref() else {
unreachable!()
};
match target {
TargetFilter::Typed(tf) => {
assert_eq!(
tf.controller,
Some(ControllerRef::Opponent),
"attach target must be opponent-controlled, got {tf:?}"
);
}
other => panic!("expected Typed opponent-creature filter, got {other:?}"),
}
}

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Attach-target assertion doesn't verify the "creature" restriction, only "opponent".

The Oracle text is "target creature an opponent controls," but the assertion only checks tf.controller == Some(ControllerRef::Opponent). If the parser dropped the creature-type restriction (letting the ability target any opponent permanent), this test would still pass — leaving a real semantic regression uncaught.

🧪 Suggested addition
     match target {
         TargetFilter::Typed(tf) => {
             assert_eq!(
                 tf.controller,
                 Some(ControllerRef::Opponent),
                 "attach target must be opponent-controlled, got {tf:?}"
             );
+            assert!(
+                tf.types.iter().any(|t| t == "Creature"),
+                "attach target must be restricted to creatures, got {tf:?}"
+            );
         }
         other => panic!("expected Typed opponent-creature filter, got {other:?}"),
     }

As per path instructions, "Test adequacy is the highest-frequency contributor finding — scrutinize it."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let Effect::Attach { target, .. } = attach.effect.as_ref() else {
unreachable!()
};
match target {
TargetFilter::Typed(tf) => {
assert_eq!(
tf.controller,
Some(ControllerRef::Opponent),
"attach target must be opponent-controlled, got {tf:?}"
);
}
other => panic!("expected Typed opponent-creature filter, got {other:?}"),
}
}
let Effect::Attach { target, .. } = attach.effect.as_ref() else {
unreachable!()
};
match target {
TargetFilter::Typed(tf) => {
assert_eq!(
tf.controller,
Some(ControllerRef::Opponent),
"attach target must be opponent-controlled, got {tf:?}"
);
assert!(
tf.types.iter().any(|t| t == "Creature"),
"attach target must be restricted to creatures, got {tf:?}"
);
}
other => panic!("expected Typed opponent-creature filter, got {other:?}"),
}
}
🤖 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/bloodthirsty_blade_goad_5973.rs` around lines
158 - 171, Strengthen the attach-target assertion in the Typed target-filter
branch to verify both opponent control and the creature-type restriction
expressed by the Oracle text. Use the existing type-related field or predicate
on tf, while preserving the current failure messages and rejection of non-Typed
filters.

Source: Path instructions

Comment on lines +199 to +207
let constraints = attacker_constraints_for_active_player(runner.state(), &valid);
assert!(
matches!(
constraints.get(&host),
Some(CombatRequirement::MustAttack { .. })
),
"display constraints must surface MustAttack for the host, got {:?}",
constraints.get(&host)
);

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

MustAttack { .. } wildcard hides the away-from-goader payload this PR is actually regression-testing.

The match only confirms some MustAttack variant is present for host, discarding its fields via { .. }. The #5973 bug was specifically about the "attack a player other than the equipment controller if able" clause — if attacker_constraints_for_active_player returns a MustAttack with the wrong or missing away-from-goader data (e.g., pointing at the wrong player, or omitting the exemption for the 2-player case), this assertion still passes.

Consider destructuring the actual field(s) of MustAttack (e.g., an away-from-player set/goader id) and asserting they identify P0 as the player to avoid, mirroring what validate_attack_declaration checks later in the file. As per path instructions, negative/behavioral test adequacy for the exact regression under test should be scrutinized closely.

🤖 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/bloodthirsty_blade_goad_5973.rs` around lines
199 - 207, Strengthen the assertion for host in
attacker_constraints_for_active_player by destructuring
CombatRequirement::MustAttack and validating its away-from-goader payload
identifies P0 as the player to avoid, including the expected two-player
exemption behavior. Mirror the corresponding checks in
validate_attack_declaration instead of accepting any MustAttack variant.

Source: Path instructions

@matthewevans matthewevans self-assigned this Jul 24, 2026
@matthewevans

Copy link
Copy Markdown
Member

Closed: required model/tier declaration missing

This PR was opened at 2026-07-24T20:27:58Z, after the Frontier-only policy landed in docs/AI-CONTRIBUTOR.md at commit 4dd28b1a4ffbf31da6eefe1d7b5d53e67f4745e7 (2026-07-24T11:39:58-07:00). That policy applies to PRs opened on or after 2026-07-24 and requires both a canonical Model: line and Tier: Frontier.

The current PR body has neither declaration. Its only model-adjacent commit evidence is Co-authored-by: Cursor <cursoragent@cursor.com>, which identifies a harness rather than a qualifying Frontier model, so eligibility cannot be assumed.

This is a policy close, not a judgment of the test change. Please resubmit from current main with the required declarations present when the new PR is created, together with the completed PR template and current-head verification evidence.

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.

Goad Crash Engine? — [[Bloodthirsty Blade]] was equipped to opponents creature.

2 participants