Skip to content

fix(engine): rebind DamageAll recipient-filter source to the resolved damage target#5873

Merged
matthewevans merged 1 commit into
phase-rs:mainfrom
tryeverything24:fix/issue-4759
Jul 16, 2026
Merged

fix(engine): rebind DamageAll recipient-filter source to the resolved damage target#5873
matthewevans merged 1 commit into
phase-rs:mainfrom
tryeverything24:fix/issue-4759

Conversation

@tryeverything24

Copy link
Copy Markdown
Contributor

Fixes #4759.

Chandra's Ignition: "Target creature you control deals damage equal to its power to each other creature and each opponent."

Root cause & fix

The reported "prompts for two targets" symptom is already fixed on main via an earlier, unrelated maintainer commit that added a generalized single-target suppression for Effect::DamageAll with damage_source: Some(Target).

While building the required end-to-end regression test, found a real, separate bug: DamageAll::resolve_all built its recipient-matching FilterContext from ability.source_id (the spell object) instead of the resolved DamageSource::Target creature, so FilterProp::Another ("each OTHER creature") never actually excluded the damage source itself — the targeted/boosted creature took its own sweep damage.

Fixed by rebinding ctx.source_id/source_controller to the resolved target creature before the recipient filter runs, scoped strictly to the DamageSource::Target case (mirrors the equivalent fix already applied to the single-recipient DealDamage path in #5834/issue #4960).

Testing

New end-to-end test: cast Chandra's Ignition with multiple creatures on the battlefield, confirm only one target prompt occurs, and confirm the source creature takes zero damage from its own sweep while other creatures and opponents take the full amount.

Verified locally (rebased onto main): fmt clean, clippy clean, 16673 lib tests pass, 3133 integration tests pass.

… damage target (phase-rs#4759)

Issue phase-rs#4759 "bug 1" reported Chandra's Ignition ("Target creature you
control deals damage equal to its power to each other creature and each
opponent") prompting for a second target when the controller had multiple
creatures. That specific symptom does NOT reproduce on current main: the
generalized one-sided-damage target-slot suppression (added for phase-rs#4234 Bite
Down, extended to DamageAll's damage_source: Target shape by the maintainer's
own "phase-rs#607 followup" commit) already collapses the recipient's "each other
creature"/"each opponent" clause into a non-targeted DamageAll sub-ability,
so casting only ever prompts for the one real target (the source creature).

While building the end-to-end regression test the dispatch asked for (cast
with >=2 own creatures + an opponent creature, drive the real pipeline), a
distinct, real bug surfaced: the chosen source creature damaged ITSELF.
DamageAll::resolve_all built its recipient-matching FilterContext via
FilterContext::from_ability(ability), which sets source_id to the ability's
nominal source (the spell object). FilterProp::Another ("each OTHER
creature") compares each battlefield candidate's id against that source_id —
but the spell is never itself a battlefield creature, so the comparison
never excludes anything, and the resolved damage-source creature (bound
separately, only for keyword/protection purposes, via damage_source:
Some(DamageSource::Target)) was never excluded from its own "each other
creature" recipient set.

Fix: when damage_source is Some(DamageSource::Target), rebind the recipient
FilterContext's source_id/source_controller to the resolved target creature
before evaluating the recipient filter, mirroring the existing damage-source
lookup used later in the same function for keyword/replacement purposes.
Scoped to the DamageSource::Target case only (the shape Chandra's Ignition
and Nova Flame use); other DamageAll damage-source modes are untouched.

Added issue_4759_chandras_ignition_single_target.rs: drives the real cast/
target-selection pipeline with the controller's chosen source creature, a
second creature the controller also controls, and an opponent creature.
Asserts exactly one target prompt occurs (pinning bug 1's already-fixed
behavior) and that only the two OTHER creatures + the opponent player take
damage, never the source itself (pinning the newly-fixed self-damage bug).
Failed pre-fix (source took damage_marked == 4 instead of 0); passes after.

Full suite green: fmt clean, clippy -D warnings clean, lib 16673 passed/0
failed, integration 3133 passed/0 failed.

Closes phase-rs#4759

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request resolves an issue where "other"-relative recipient filters (such as Chandra's Ignition) were incorrectly evaluated against the ability's nominal source instead of the resolved damage source creature. This is fixed by updating the filter context's source ID and controller when the damage source is a target. An integration test has been added to verify this behavior. The reviewer suggests refactoring the target resolution logic in deal_damage.rs to avoid duplicate iterations over ability.targets.

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.

Comment on lines +1564 to +1572
if matches!(damage_source, Some(DamageSource::Target)) {
if let Some(resolved_source_id) = ability.targets.iter().find_map(|t| match t {
TargetRef::Object(id) => Some(*id),
_ => None,
}) {
ctx.source_id = resolved_source_id;
ctx.source_controller = state.objects.get(&resolved_source_id).map(|o| o.controller);
}
}

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.

medium

This logic correctly fixes the issue. However, it introduces a small code duplication. The logic to find the resolved_source_id by iterating over ability.targets is repeated later in this function (around line 1597) to create the DamageContext.

To improve maintainability and avoid iterating over ability.targets twice, consider resolving the source ID once at the beginning of the function when damage_source is Some(DamageSource::Target), storing it in a variable, and then using it in both places.

@github-actions

Copy link
Copy Markdown

Parse changes introduced by this PR

✓ No card-parse changes detected.

@matthewevans matthewevans self-assigned this Jul 16, 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 implementation review.

@matthewevans matthewevans added the bug Bug fix label Jul 16, 2026
@matthewevans matthewevans added this pull request to the merge queue Jul 16, 2026
@matthewevans matthewevans removed their assignment Jul 16, 2026
Merged via the queue into phase-rs:main with commit 5325614 Jul 16, 2026
13 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.

Chandra's Ignition — bug 1 When you control multiple creatures, Chandra's Ignition prompts for two targets.

2 participants