Skip to content

fix(engine): reject stale object targets after zone changes - #7235

Merged
matthewevans merged 1 commit into
phase-rs:mainfrom
traemyn:fix/strip-mine-stale-target
Aug 12, 2026
Merged

fix(engine): reject stale object targets after zone changes#7235
matthewevans merged 1 commit into
phase-rs:mainfrom
traemyn:fix/strip-mine-stale-target

Conversation

@traemyn

@traemyn traemyn commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes stale ordinary object targets after a permanent leaves and returns to the battlefield. The engine now captures selected-target incarnations at announcement/selection, rechecks them on resolution, and refreshes only targets actually changed by retargeting. The regression reproduces the supplied /home/traemyn/Downloads/earthbend-stripmine.zip state shape through the real activation and resolution pipeline.

Files changed

  • crates/engine/src/game/ability_rw.rs
  • crates/engine/src/game/ability_scan.rs
  • crates/engine/src/game/ability_utils.rs
  • crates/engine/src/game/effects/additional_phase.rs
  • crates/engine/src/game/effects/change_targets.rs
  • crates/engine/src/game/effects/double.rs
  • crates/engine/src/game/effects/extra_turn.rs
  • crates/engine/src/game/effects/grant_extra_loyalty_activations.rs
  • crates/engine/src/game/effects/player_counter.rs
  • crates/engine/src/game/effects/reverse_turn_order.rs
  • crates/engine/src/game/effects/skip_next_step.rs
  • crates/engine/src/game/effects/skip_next_turn.rs
  • crates/engine/src/game/effects/vote.rs
  • crates/engine/src/game/engine.rs
  • crates/engine/src/game/resolution_prompt.rs
  • crates/engine/src/game/stack.rs
  • crates/engine/src/types/ability.rs
  • crates/engine/tests/integration/main.rs
  • crates/engine/tests/integration/target_incarnation_revalidation.rs
  • crates/engine/tests/integration/the_chain_veil_loyalty_grants.rs

Track

Developer

LLM

Model: GitHub Copilot (traemyn edit: GPT-5.6 Luna)
Tier: Frontier
Thinking: high

Implementation method (required)

Method: /engine-implementer

CR references

  • CR 400.7: a zone change creates a new object with no relation to its prior existence.
  • CR 601.2c: announced objects and players become targets of a spell or ability.
  • CR 608.2b: targets are rechecked on resolution; all-illegal-target spells and abilities do not resolve.
  • CR 115.7a-d: retargeting changes only legal replacement targets and may leave unchanged targets intact.

The references were verified against docs/MagicCompRules.txt fetched from the repository's authoritative rules source.

Verification

  • Required checks ran clean, or the exact CI-owned alternative is stated below.

  • Gate A output below is for the current committed head.

  • Final review-impl below is clean for the current committed head.

  • Both anchors cite existing analogous code at the same seam.

  • cargo fmt --all -- --check — passed.

  • cargo check -p phase-engine — passed.

  • cargo test -p phase-engine ability_utils::tests:: --lib — 145 passed, 0 failed.

  • cargo test -p phase-engine --test integration target_incarnation_revalidation — 1 passed, 0 failed.

  • cargo test -p phase-engine --test integration --quiet — 4,801 passed, 2 ignored, 0 failed.

  • git diff --check — passed.

  • ./scripts/check-parser-combinators.sh upstream/main — Gate G PASS; Gate A PASS for head 31ce252b7fc72d95a83a2c036b127efadaf46e06, base b8185d37bb6a1d49ae5b7fe8e955e2ce54a4357c.

  • Tilt was unavailable in the isolated worktree, so the repository-prescribed direct Cargo checks were used.

Gate A

Gate A PASS head=31ce252b7fc72d95a83a2c036b127efadaf46e06 base=b8185d37bb6a1d49ae5b7fe8e955e2ce54a4357c

Anchored on

  • crates/engine/src/game/ability_utils.rs:1799assign_selected_slots_in_chain is the existing interactive target-assignment seam used by GameAction::ChooseTarget.
  • crates/engine/src/game/ability_utils.rs:1891validate_targets_in_chain is the existing resolution-time target legality seam used by stack resolution.

Final review-impl

Final review-impl PASS head=31ce252b7fc72d95a83a2c036b127efadaf46e06

Claimed parse impact

None.

Scope Expansion

None. The supplied Strip Mine/Earthbend reproduction is addressed as a general ordinary selected-object target identity fix across bulk, interactive, automatic/random, activated, triggered, modal/chain, and retargeting paths.

Validation Failures

None. The full implementation-review and validation workflow completed on the immutable candidate 31ce252b7fc72d95a83a2c036b127efadaf46e06. Legacy serialized abilities without the new optional field intentionally remain fail-open for backward compatibility.

CI Failures

None. CI has not yet run on the pushed branch; the listed local checks passed and this section will reflect any CI result if one fails.

Pipeline handoff

Pipeline-reviewed head: 31ce252b7fc72d95a83a2c036b127efadaf46e06
Current branch head: 31ce252b7fc72d95a83a2c036b127efadaf46e06
Pipeline status: current
Current-head review: clean at 31ce252b7fc72d95a83a2c036b127efadaf46e06

Checkpoint base: b8185d37bb6a1d49ae5b7fe8e955e2ce54a4357c

Summary by CodeRabbit

  • Bug Fixes

    • Improved target validation when objects leave and return to play, preventing abilities from applying to a new incarnation of the same object.
    • Updated interactive, forced, and copied-ability retargeting to keep selected object targets accurately tracked.
    • Applied consistent target checks across combat, attachments, counters, damage prevention, and other targeted effects.
  • Tests

    • Added regression coverage for target revalidation after zone changes, returns to play, copied abilities, and same-object retargeting.

@traemyn
traemyn requested a review from matthewevans as a code owner August 11, 2026 13:23
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8f5b5268-e3c9-41b8-8ff0-3c3ed68e1069

📥 Commits

Reviewing files that changed from the base of the PR and between c149607 and 006e144.

📒 Files selected for processing (1)
  • crates/engine/src/game/effects/copy_spell.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/engine/src/game/effects/copy_spell.rs

📝 Walkthrough

Walkthrough

The engine now tracks incarnation pins for ordinary selected object targets. Target assignment captures pins, retargeting updates them, and resolution validates them across target paths. Tests cover stale targets, retargeting, and copied abilities.

Changes

Selected target incarnation tracking

Layer / File(s) Summary
Pin model and ability handling
crates/engine/src/types/ability.rs, crates/engine/src/game/ability_rw.rs, crates/engine/src/game/ability_scan.rs, crates/engine/src/game/stack.rs, crates/engine/src/game/resolution_prompt.rs
ResolvedAbility stores, clears, captures, validates, and compares selected-target incarnation pins.
Target assignment and resolution validation
crates/engine/src/game/ability_utils.rs
Target assignment captures object incarnations. Specialized and generic resolution paths validate target pins, filters, zones, and current incarnations.
Retargeting and copy updates
crates/engine/src/game/engine.rs, crates/engine/src/game/effects/change_targets.rs, crates/engine/src/game/effects/copy_spell.rs
Object-target replacement refreshes selected-target incarnation pins. Copied target replacements propagate pins through nested abilities.
Fixtures and regression coverage
crates/engine/src/game/effects/*.rs, crates/engine/tests/integration/*, crates/engine/src/game/engine_keyword_action_stack_tests.rs
Fixtures initialize the new field. Tests cover zone changes, interactive retargeting, forced retargeting, and copied abilities.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Player
  participant ResolvedAbility
  participant GameState
  participant AbilityResolution
  Player->>ResolvedAbility: select object target
  ResolvedAbility->>GameState: capture current incarnation
  GameState-->>ResolvedAbility: store incarnation pin
  GameState->>GameState: move object away and return it
  AbilityResolution->>ResolvedAbility: validate selected-target pin
  ResolvedAbility->>GameState: compare pinned and current incarnations
  GameState-->>AbilityResolution: invalidate stale target
Loading
🚥 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 and concisely describes the main change: rejecting stale object targets after zone changes.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@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: 3

🤖 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/src/game/engine.rs`:
- Around line 11755-11770: Update crates/engine/src/game/engine.rs:11755-11770
to compare target incarnations, not only TargetRef equality, and refresh the
selected-target pin when the existing pin is stale; apply the same
incarnation-aware comparison in crates/engine/src/game/engine.rs:6745-6770 when
finalizing copy retargeting. In
crates/engine/src/game/effects/change_targets.rs:88-103, ensure forced
retargeting rebinds a same-ObjectId target after it leaves and returns, and add
a regression covering that sequence.

In `@crates/engine/src/types/ability.rs`:
- Around line 24713-24715: Update the documentation annotation at the shared
target-selection method to cite CR 115.1, CR 601.2c, CR 602.2b, and CR 603.3d,
reflecting spell, activated-ability, and triggered-ability target selection; do
not leave CR 601.2c as the sole citation unless the method is restricted to
spell targets.

In `@crates/engine/tests/integration/target_incarnation_revalidation.rs`:
- Around line 48-49: Replace the direct zones::move_to_zone calls in the test
with the production ProposedEvent::ZoneChange scenario and priority pipeline,
resolving the intervening leave-and-return ability before the activated ability.
Ensure replacement handling runs and the returned permanent is treated as a new
object, making the original target illegal per the expected rules behavior.
🪄 Autofix

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: d0655fed-2e7b-4c20-9caf-090be447963d

📥 Commits

Reviewing files that changed from the base of the PR and between 448e986 and 31ce252.

📒 Files selected for processing (20)
  • crates/engine/src/game/ability_rw.rs
  • crates/engine/src/game/ability_scan.rs
  • crates/engine/src/game/ability_utils.rs
  • crates/engine/src/game/effects/additional_phase.rs
  • crates/engine/src/game/effects/change_targets.rs
  • crates/engine/src/game/effects/double.rs
  • crates/engine/src/game/effects/extra_turn.rs
  • crates/engine/src/game/effects/grant_extra_loyalty_activations.rs
  • crates/engine/src/game/effects/player_counter.rs
  • crates/engine/src/game/effects/reverse_turn_order.rs
  • crates/engine/src/game/effects/skip_next_step.rs
  • crates/engine/src/game/effects/skip_next_turn.rs
  • crates/engine/src/game/effects/vote.rs
  • crates/engine/src/game/engine.rs
  • crates/engine/src/game/resolution_prompt.rs
  • crates/engine/src/game/stack.rs
  • crates/engine/src/types/ability.rs
  • crates/engine/tests/integration/main.rs
  • crates/engine/tests/integration/target_incarnation_revalidation.rs
  • crates/engine/tests/integration/the_chain_veil_loyalty_grants.rs

Comment thread crates/engine/src/game/engine.rs Outdated
Comment thread crates/engine/src/types/ability.rs Outdated
Comment on lines +48 to +49
move_to_zone(runner.state_mut(), target, Zone::Graveyard, &mut events);
move_to_zone(runner.state_mut(), target, Zone::Battlefield, &mut events);

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 | 🏗️ Heavy lift

Use the production zone-change pipeline.

Lines 48-49 call zones::move_to_zone directly. This bypasses ProposedEvent::ZoneChange and replacement handling. The test can pass while a live leave-and-return sequence produces different state or target pins.

Resolve an intervening zone-change ability through the scenario and priority pipeline before resolving the activated ability. CR 400.7 makes the returned permanent a new object, and CR 608.2b requires the original target to be illegal. (media.wizards.com)

🤖 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/target_incarnation_revalidation.rs` around
lines 48 - 49, Replace the direct zones::move_to_zone calls in the test with the
production ProposedEvent::ZoneChange scenario and priority pipeline, resolving
the intervening leave-and-return ability before the activated ability. Ensure
replacement handling runs and the returned permanent is treated as a new object,
making the original target illegal per the expected rules behavior.

Source: Path instructions

@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown

Generated for head 006e1442a16efa1279dd3f18046fa5eb11a102d7.

Parse changes introduced by this PR

✓ No card-parse changes detected.

@matthewevans matthewevans self-assigned this Aug 11, 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.

[HIGH] Same-ObjectId retargeting leaves the selected-target incarnation pin stale. Evidence: crates/engine/src/types/ability.rs:24022-24025 represents object targets by ObjectId only, while crates/engine/src/types/identifiers.rs:143-180 defines the distinct ID-plus-epoch identity; all three refresh paths use only old != newcrates/engine/src/game/engine.rs:6745-6770, crates/engine/src/game/engine.rs:11755-11770, and crates/engine/src/game/effects/change_targets.rs:88-103. Why it matters: a legal retarget to an object that left and returned with the same storage ID retains the old epoch pin and then incorrectly fizzles on resolution. Suggested fix: make the changed-target comparison incarnation-aware (including a stale existing pin) and cover copy, interactive, and forced retarget routes.

[HIGH] The regression bypasses the production zone-change pipeline. Evidence: crates/engine/tests/integration/target_incarnation_revalidation.rs:4,48-49 calls zones::move_to_zone directly, whereas production delivery captures PendingZoneChangeDelivery with ProposedEvent::zone_change at crates/engine/src/game/effects/change_zone.rs:289-299 and routes a move through the replacement-aware terminal path at crates/engine/src/game/effects/mod.rs:1260-1276. Why it matters: the test can pass without exercising replacement/delivery state that live leave-and-return events use. Suggested fix: reproduce the intervening leave/return through the scenario and priority pipeline, resolve it before the original activated ability, and retain a reach assertion proving the real delivery path ran.

[MED] The shared selected-target capture method cites spell casting as its only target-selection source. Evidence: crates/engine/src/types/ability.rs:24713-24716 labels a shared announcement/selection method solely with CR 601.2c, while the method serves spell, activated, and triggered ability target selection. Why it matters: the rule annotation misstates the authority for two of its production callers. Suggested fix: retain the CR 400.7 identity citation and add the applicable target-selection sources from CR 115, 601.2c, 602.2b, and 603.3d.

[MED] The PR leaves the Rust producer-census test red from a stale expected coordinate. Evidence: the current head's failing shard reports actual game/engine.rs:11979 versus expected game/engine.rs:11942; the producer is still the intended WaitingFor::OptionalEffectChoice return at crates/engine/src/game/engine.rs:11979, while the census expectation remains at crates/engine/src/game/engine.rs:16395. Why it matters: required CI cannot pass, even though the census producer set has not changed. Suggested fix: mechanically update only that expected location after re-verifying the producer identity.

@matthewevans matthewevans added the bug Bug fix label Aug 11, 2026
@matthewevans matthewevans removed their assignment Aug 11, 2026
@traemyn
traemyn force-pushed the fix/strip-mine-stale-target branch from 31ce252 to 7b8b92a Compare August 11, 2026 15:19
@traemyn

traemyn commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

Addressed all review findings in amended commit 7b8b92a54:

  • Added retarget_target_requires_pin_refresh, so interactive, copy, and forced retargeting refresh stale same-ObjectId pins while preserving unchanged targets.
  • Added copy-retarget unit coverage for a leave/return same-ID target.
  • Reworked the integration regression to use a real blink ability through the scenario and priority pipeline, including replacement-aware zone delivery.
  • Expanded the shared target-capture CR annotation for spell, activated, and triggered abilities.
  • Updated the producer-census expectation after verifying the producer location.

Validation: cargo fmt --all -- --check, cargo test -p phase-engine --lib, cargo test -p phase-engine --test integration, git diff --check, and touched-file diagnostics all pass.

@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: 1

🤖 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/src/game/engine_keyword_action_stack_tests.rs`:
- Around line 1007-1055: Update the test fixture around ResolvedAbility::new and
finalize_copy_retarget to use an effect whose TargetFilter legally accepts the
TargetRef::Object target, such as a creature- or permanent-targeting effect.
Construct the copied spell and drive retargeting through the normal
WaitingFor::CopyRetarget action flow rather than calling finalize_copy_retarget
directly, while preserving the zone-change setup needed to exercise the
pin-refresh failure path.
🪄 Autofix

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: 9d1fac48-629f-4b1b-9012-28a68550b9c7

📥 Commits

Reviewing files that changed from the base of the PR and between 31ce252 and 7b8b92a.

📒 Files selected for processing (5)
  • crates/engine/src/game/effects/change_targets.rs
  • crates/engine/src/game/engine.rs
  • crates/engine/src/game/engine_keyword_action_stack_tests.rs
  • crates/engine/src/types/ability.rs
  • crates/engine/tests/integration/target_incarnation_revalidation.rs
🚧 Files skipped from review as they are similar to previous changes (3)
  • crates/engine/src/game/engine.rs
  • crates/engine/src/game/effects/change_targets.rs
  • crates/engine/src/types/ability.rs

Comment thread crates/engine/src/game/engine_keyword_action_stack_tests.rs Outdated
@matthewevans matthewevans self-assigned this Aug 11, 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.

Changes requested — the current head has one blocking test-evidence gap.

🔴 Blocker

[MED] The copy-retarget regression constructs an impossible slot and bypasses the production selection action. Evidence: crates/engine/src/game/engine_keyword_action_stack_tests.rs:1007-1055 pairs Effect::Draw { target: TargetFilter::Controller } with TargetRef::Object, writes that object into legal_alternatives, and directly calls finalize_copy_retarget; the object matcher explicitly rejects TargetFilter::Controller at crates/engine/src/game/filter.rs:2620-2626. Production instead accepts GameAction::ChooseTarget only when the choice is already in an engine-built legal-alternatives slot before it calls finalize_copy_retarget (crates/engine/src/game/engine.rs:11010-11066). Why it matters: this passes even if copied spells that can legally target objects fail to refresh their incarnation pin, so it does not prove the advertised copy-retarget behavior. Suggested fix: exercise a legally object-targeting copied spell through WaitingFor::CopyRetarget and the normal GameAction::ChooseTarget pipeline, retaining the leave/return setup and asserting the refreshed pin after the action completes.

✅ Clean

The current full integration blink regression replaces the earlier direct zone-move fixture, so that previous concern is resolved. CI is green and the current-head parse-diff reports no card-parse change; neither substitutes for a discriminating copy-retarget test.

Recommendation: request changes for the production-flow regression above, then re-review the new head.

@matthewevans matthewevans removed their assignment Aug 11, 2026
@traemyn
traemyn force-pushed the fix/strip-mine-stale-target branch from 7b8b92a to 00ca01b Compare August 11, 2026 17:17
@traemyn

traemyn commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the copy-test evidence blocker in amended commit 00ca01b6b.

The regression now:

  • uses Effect::Destroy with a typed creature TargetFilter, so the object target is legal;
  • calls production copy_spell::open_copy_retarget_choice, which builds legal_alternatives through build_target_slots;
  • submits GameAction::ChooseTarget through the normal WaitingFor::CopyRetarget action handler;
  • preserves the leave/return setup and asserts the stale same-ObjectId pin is refreshed.

Validation passed: the focused regression, all 22 keyword_action_stack_tests, all 3 target_incarnation_revalidation tests, formatting, whitespace, and diagnostics.

@matthewevans matthewevans self-assigned this Aug 11, 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.

Changes requested — the manual copy-retarget regression is fixed, but the automatic copy path still bypasses the new incarnation authority.

🔴 Blocker

[HIGH] Zada-style automatic copies overwrite object targets without capturing the replacement incarnation. Evidence: crates/engine/src/game/effects/copy_spell.rs:191-212 calls rewrite_copy_spell_object_targets after pushing the copy, while crates/engine/src/game/effects/copy_spell.rs:886-898 only rewrites targets; it never refreshes selected_target_incarnations. Resolution checks a new target only through selected_target_pin_is_current (crates/engine/src/game/ability_utils.rs:1869-1888), which intentionally passes when there is no pin (crates/engine/src/types/ability.rs:24764-24775). Why it matters: if an iteration member leaves and returns after its copy is put on the stack, the copy can affect that new incarnation instead of treating its target as illegal. Suggested fix: make the shared Zada rewrite capture the post-rewrite target incarnations from GameState, then add a regression that lets a retargeted copy's member leave and return before that copy resolves.

✅ Clean

The prior copy-retarget blocker is resolved at this head: the test uses a creature-targeting Effect::Destroy, obtains its slots from open_copy_retarget_choice, and submits GameAction::ChooseTarget through WaitingFor::CopyRetarget (crates/engine/src/game/engine_keyword_action_stack_tests.rs:1007-1071).

Recommendation: retain the new interactive-path test and extend the same incarnation-pinning authority to the automatic RetargetEachCopyToIterationMember path before approval.

@matthewevans matthewevans removed their assignment Aug 11, 2026
@traemyn
traemyn force-pushed the fix/strip-mine-stale-target branch from 00ca01b to c149607 Compare August 12, 2026 04:03
@traemyn

traemyn commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the automatic-copy blocker in amended commit c14960716.

The Zada-style RetargetEachCopyToIterationMember path now captures the selected-target incarnation after rewriting object targets, including recursive sub-abilities. The regression covers a retargeted creature copy whose iteration member leaves and returns before resolution, and resolves it through stack::resolve_top so the normal stale-target fizzle validation runs.

Validation passed before push:

  • all 39 copy_spell unit tests;
  • full phase-engine unit suite: 18,838 passed, 6 ignored;
  • full integration suite: 4,803 passed, 2 ignored;
  • target-incarnation integration regressions: 3 passed;
  • cargo check -p phase-engine; formatting, whitespace, and diagnostics.

@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

🤖 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/src/game/effects/copy_spell.rs`:
- Around line 3350-3362: Replace the direct zones::move_to_zone calls in the
regression setup with the replacement-aware ProposedEvent::ZoneChange pipeline,
routing both the Graveyard and Battlefield transitions through normal event
resolution before resolving the copy. Preserve the existing iteration_member
movement sequence and zone targets.
- Around line 911-916: Update rewrite_copy_spell_object_targets to also iterate
through ability.mode_abilities and recursively apply the same target and
incarnation-pin rewrite to each mode ability. Preserve the existing sub_ability
and else_ability traversal, and add a regression test covering automatic copying
of a modal spell with selected object targets.
🪄 Autofix

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: 6b1c298b-c832-49bc-89bf-3586ecdb42df

📥 Commits

Reviewing files that changed from the base of the PR and between 00ca01b and c149607.

📒 Files selected for processing (1)
  • crates/engine/src/game/effects/copy_spell.rs

Comment on lines 911 to 916
if let Some(sub) = ability.sub_ability.as_mut() {
rewrite_copy_spell_object_targets(sub, new_target);
rewrite_copy_spell_object_targets(sub, new_target, new_target_pin);
}
if let Some(else_ab) = ability.else_ability.as_mut() {
rewrite_copy_spell_object_targets(else_ab, new_target);
rewrite_copy_spell_object_targets(else_ab, new_target, new_target_pin);
}

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 | 🏗️ Heavy lift

Rewrite selected modal ability targets.

ResolvedAbility also stores mode_abilities. This helper only traverses sub_ability and else_ability.

When an automatic copy retargets a modal spell, a selected mode can retain its old object target and old incarnation pin. Traverse every mode_abilities entry with the same rewrite function. Add a modal-copy regression.

As per path instructions, CLAUDE.md requires target-incarnation behavior across modal paths.

🤖 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/src/game/effects/copy_spell.rs` around lines 911 - 916, Update
rewrite_copy_spell_object_targets to also iterate through ability.mode_abilities
and recursively apply the same target and incarnation-pin rewrite to each mode
ability. Preserve the existing sub_ability and else_ability traversal, and add a
regression test covering automatic copying of a modal spell with selected object
targets.

Source: Path instructions

Comment thread crates/engine/src/game/effects/copy_spell.rs Outdated
@matthewevans matthewevans self-assigned this Aug 12, 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.

Changes requested — the automatic-copy production regression is still not discriminating enough for this rules-sensitive target-incarnation change.

🔴 Blocker

[MED] The new Zada-style regression bypasses the production zone-change delivery path. Evidence: crates/engine/src/game/effects/copy_spell.rs:3351-3362 calls zones::move_to_zone directly for the iteration member, while production ChangeZone resolves through PendingZoneChangeDelivery::new(ProposedEvent::zone_change(...)) at crates/engine/src/game/effects/mod.rs:1260-1268, where replacements and delivery state are applied. Why it matters: the test proves only that a direct mutation bumps the incarnation; it can remain green while the actual leave/return path that the copy will encounter changes delivery/replacement state or target pins differently. Suggested fix: make the regression move the iteration member out and back through the existing ProposedEvent::ZoneChange/resolution pipeline, then resolve the copy and retain the asserted fizzle of the returned incarnation.

✅ Clean

The automatic retarget itself now captures the current iteration-member incarnation before rewriting targets (copy_spell.rs:204-215, 892-916), and the earlier interactive-copy evidence gap is resolved. The current CodeRabbit modal-recursion suggestion does not apply: mode_abilities are AbilityDefinitions; modal casting/selection constructs selected modes as a ResolvedAbility sub-chain before this copy path (game/casting_targets.rs:142-152, game/engine_modes.rs:40-46), which the rewrite already recurses.

Recommendation: request changes for the production-path regression, then re-review the new head.

@traemyn
traemyn force-pushed the fix/strip-mine-stale-target branch from c149607 to 006e144 Compare August 12, 2026 13:36
@traemyn

traemyn commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the valid production-path regression blocker in amended commit 006e1442a.

The automatic-copy regression now moves the iteration member out and back by resolving real Effect::ChangeZone abilities. That path reaches the replacement-aware ProposedEvent::ZoneChange delivery machinery before stack::resolve_top verifies the copied spell fizzles against the returned incarnation.

I also verified the modal-recursion suggestion is not applicable: ResolvedAbility::mode_abilities stores AbilityDefinitions, while selected modal modes are constructed into the resolved sub-chain before this copy path and are already recursively rewritten.

Validation: focused automatic-copy regression; all 39 copy_spell unit tests; all 3 target-incarnation integrations; full engine units (18,838 passed, 6 ignored); full integrations (4,803 passed, 2 ignored); cargo check -p phase-engine; formatting, diagnostics, and whitespace checks.

@matthewevans matthewevans self-assigned this Aug 12, 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.

Approved after current-head review: selected-target incarnation pins are captured, revalidated, and refreshed through interactive, forced, and automatic-copy retargeting; the regressions exercise the production resolution path.

@matthewevans
matthewevans added this pull request to the merge queue Aug 12, 2026
@matthewevans matthewevans removed their assignment Aug 12, 2026
Merged via the queue into phase-rs:main with commit 8b7e06d Aug 12, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants