Skip to content
7 changes: 7 additions & 0 deletions crates/engine/src/game/effects/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11205,6 +11205,13 @@ fn resolve_chain_body(
);
resolve_ability_chain(state, &trailing_resolved, events, depth + 1)?;
}
} else if ability.forward_result && forwarded_objects.is_empty() {
// CR 608.2c: A forward-result continuation is anchored to the object
// moved by the preceding instruction. If no object moved, that
// instruction has no referent for dependent riders such as "it gains
// haste" or "sacrifice it"; do not let ParentTarget fall back to the
// original ability source.
return Ok(());
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
} else if !forwarded_objects.is_empty() {
let mut sub_with_context = sub.as_ref().clone();
// CR 707.10: `CopySpell { SelfRef }` copies the resolving spell
Expand Down
86 changes: 86 additions & 0 deletions crates/engine/tests/integration/issue_1515_emperor_of_bones.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,92 @@ fn emperor_of_bones_counter_trigger_uses_returned_creature_in_cast_pipeline() {
);
}

#[test]
fn emperor_of_bones_adapt_pipeline_binds_delayed_sacrifice_to_returned_creature() {
let mut scenario = GameScenario::new();
scenario.at_phase(Phase::PreCombatMain);
let emperor = scenario
.add_creature_from_oracle(P0, "Emperor of Bones", 2, 2, EMPEROR_ORACLE)
.id();
let returned = scenario
.add_creature_to_exile(P0, "Linked Gravebeast", 3, 3)
.id();
let swamp_a = scenario.add_basic_land(P0, engine::types::mana::ManaColor::Black);
let swamp_b = scenario.add_basic_land(P0, engine::types::mana::ManaColor::Black);

let mut runner = scenario.build();
runner.state_mut().exile_links.push(ExileLink {
exiled_id: returned,
source_id: emperor,
kind: ExileLinkKind::TrackedBySource,
});

runner
.activate(emperor, 0)
.pay_with(&[swamp_a, swamp_b])
.resolve();

let state = runner.state();
assert_eq!(
state.objects[&returned].zone,
Zone::Battlefield,
"Adapt must resolve Emperor's counter trigger and return the linked creature"
);
assert_eq!(
state.delayed_triggers.len(),
1,
"the counter trigger must install one delayed sacrifice"
);
assert_eq!(
state.delayed_triggers[0].ability.targets,
vec![engine::types::ability::TargetRef::Object(returned)],
"the Adapt-triggered delayed sacrifice must snapshot the returned creature"
);
assert_eq!(
state.objects[&emperor].zone,
Zone::Battlefield,
"Emperor must remain on the battlefield until its own ability is removed"
);
}

#[test]
fn emperor_of_bones_adapt_without_linked_exile_has_no_riders_to_apply() {
let mut scenario = GameScenario::new();
scenario.at_phase(Phase::PreCombatMain);
let emperor = scenario
.add_creature_from_oracle(P0, "Emperor of Bones", 2, 2, EMPEROR_ORACLE)
.id();
let swamp_a = scenario.add_basic_land(P0, engine::types::mana::ManaColor::Black);
let swamp_b = scenario.add_basic_land(P0, engine::types::mana::ManaColor::Black);

let mut runner = scenario.build();
runner
.activate(emperor, 0)
.pay_with(&[swamp_a, swamp_b])
.resolve();

let state = runner.state();
assert_eq!(
state.objects[&emperor]
.counters
.get(&CounterType::Plus1Plus1)
.copied()
.unwrap_or(0),
2,
"Adapt must still put its counters on Emperor"
);
assert_eq!(
state.delayed_triggers.len(),
0,
"no returned creature means Emperor's haste and delayed Sacrifice riders must not run"
);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
assert_eq!(
state.objects[&emperor].zone,
Zone::Battlefield,
"Emperor must remain on the battlefield when no linked creature was exiled"
);
}

/// CR 614.12a + CR 400.7j: An as-enters choice on the returned permanent must
/// complete without losing later instructions that refer to that permanent.
#[test]
Expand Down
Loading