From 30b5b361a8fa6c3cc9859317fa3c4838c93aee8d Mon Sep 17 00:00:00 2001 From: philluiz2323 Date: Fri, 24 Jul 2026 00:13:36 +0400 Subject: [PATCH 1/2] fix(engine): keep a "return to hand" card as the referent for "that card" (#6486) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Volcanic Vision ("Return target instant or sorcery card from your graveyard to your hand. Volcanic Vision deals damage equal to that card's mana value to each creature your opponents control. Exile Volcanic Vision.") returned the card but dealt no damage. The spell parses correctly — the damage sub-effect's amount is the Demonstrative "that card's mana value". That resolves against the earlier-instruction referent (`effect_context_object`), which `parent_referent_context_from_events` derives from the parent effect's `ZoneChanged` events via `moved_object_context_from_events`. That helper only captured moves to a PUBLIC zone, so the graveyard->HAND return bound no referent, "that card's mana value" resolved to 0, and every opponent creature took 0 damage. CR 608.2c: a later instruction may refer to an object an earlier instruction returned to hand — its identity was established in the public source zone (graveyard), and the reference reads the move-time last-known mana value, so the hidden destination is immaterial. Capture the moved referent for a move to hand when the SOURCE zone is public. A move from a hidden source (a draw, library -> hand) establishes no such referent and is excluded; library destinations stay excluded entirely (a shuffle loses identity). Verified: cargo test -p engine --lib -> 17591 passed, 0 failed. New card-level cast test drives Volcanic Vision through the real pipeline: with a mana-value-3 instant in the graveyard, each opponent creature takes 3 damage, the card is in hand, and Volcanic Vision is exiled. --- crates/engine/src/game/effects/mod.rs | 71 +++++++++++++++++++++++++-- 1 file changed, 66 insertions(+), 5 deletions(-) diff --git a/crates/engine/src/game/effects/mod.rs b/crates/engine/src/game/effects/mod.rs index 414d1df19a..376b494b8f 100644 --- a/crates/engine/src/game/effects/mod.rs +++ b/crates/engine/src/game/effects/mod.rs @@ -1849,13 +1849,26 @@ fn moved_object_context_from_events(events: &[GameEvent]) -> Option Some(CostPaidObjectSnapshot { - object_id: *object_id, - lki: lki_snapshot_from_zone_change_record(record), - }), + } if is_public_zone(*to) + // CR 608.2c: a later instruction may refer to an object an earlier + // instruction returned to hand, provided its identity was established + // in a PUBLIC source zone — Volcanic Vision ("Return target instant + // or sorcery card from your graveyard to your hand. ~ deals damage + // equal to THAT CARD'S MANA VALUE ..."). The reference reads the + // move-time LKI snapshot, so the hidden destination is immaterial. A + // move from a hidden source (a draw, library -> hand) establishes no + // such referent and is excluded. Library-destination moves stay + // excluded entirely (a shuffle/reorder loses the object's identity). + || (*to == Zone::Hand && is_public_zone(*from_zone)) => + { + Some(CostPaidObjectSnapshot { + object_id: *object_id, + lki: lki_snapshot_from_zone_change_record(record), + }) + } _ => None, }); let first = moved.next()?; @@ -11245,6 +11258,54 @@ mod tests { use crate::types::triggers::TriggerMode; use crate::types::zones::Zone; + // CR 608.2c (#6486): Volcanic Vision — "Return target instant or sorcery card + // from your graveyard to your hand. ~ deals damage equal to that card's mana + // value to each creature your opponents control. Exile ~." The card is + // returned to HAND (a hidden zone); "that card's mana value" must still read + // the returned card's move-time mana value, so each opponent creature takes + // that much damage. Before the fix the returned card was not bound as the + // earlier-instruction referent (only PUBLIC-zone moves were), so the damage + // resolved to 0. + #[test] + fn volcanic_vision_deals_returned_cards_mana_value_after_return_to_hand() { + use crate::game::scenario::{GameScenario, P0, P1}; + + const ORACLE: &str = "Return target instant or sorcery card from your graveyard to your hand. \ + Volcanic Vision deals damage equal to that card's mana value to each creature your opponents control. \ + Exile Volcanic Vision."; + + let mut scenario = GameScenario::new(); + scenario.at_phase(Phase::PreCombatMain); + scenario.with_mana_pool( + P0, + (0..8) + .map(|_| ManaUnit::new(ManaType::Red, ObjectId(0), false, vec![])) + .collect(), + ); + let volcanic = scenario + .add_spell_to_hand_from_oracle(P0, "Volcanic Vision", false, ORACLE) + .id(); + // Return target: an instant in the caster's graveyard with mana value 3. + let bolt = scenario + .add_spell_to_graveyard(P0, "Fire Blast", true) + .with_mana_cost(ManaCost::generic(3)) + .id(); + let goblin = scenario.add_creature(P1, "Goblin", 2, 2).id(); + let ogre = scenario.add_creature(P1, "Ogre", 3, 3).id(); + + let mut runner = scenario.build(); + let outcome = runner.cast(volcanic).target_objects(&[bolt]).resolve(); + + outcome.assert_zone(&[bolt], Zone::Hand); + assert_eq!( + outcome.damage_marked(goblin), + 3, + "each opponent creature must take the returned card's mana value (3)" + ); + assert_eq!(outcome.damage_marked(ogre), 3); + outcome.assert_zone(&[volcanic], Zone::Exile); + } + #[test] fn search_filter_dynamic_property_axes_consume_the_tracked_set() { let tracked = || QuantityExpr::Ref { From 5a182ec3a130e837c4537d95f3b6b4cf31fb648f Mon Sep 17 00:00:00 2001 From: matthewevans Date: Thu, 23 Jul 2026 13:42:42 -0700 Subject: [PATCH 2/2] fix(PR-6561): correct CR citation and pin the widened move predicate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The referent widening is rules-correct, but its annotation cited CR 608.2c (instruction ordering / later-text-modifies-earlier-text), which does not speak to source-zone publicity or last known information. Cite the two rules that actually carry the clause, both grep-verified against docs/MagicCompRules.txt: CR 400.7j findability on a move to a PUBLIC zone (the pre-existing arm) CR 608.2h "... or if the effect has moved it from a public zone to a hidden zone, the effect uses the object's last known information" (the new public -> Hand arm) Add three helper-level tests on parent_referent_context_from_events so the widened predicate's boundaries are pinned: - Library -> Hand (a draw) binds no referent. After the widening, is_public_zone's exclusion of Library is the only thing keeping every draw in the game from binding one, and nothing tested it. - Graveyard -> Hand binds the referent and carries the move-time mana value (the Volcanic Vision case, at helper level). - Two qualifying moves in one span bind nothing — the CR 608.2k singular guard now has a wider surface to trip on. Also name the dominant newly-captured population (ordinary Battlefield -> Hand bounce) in the predicate comment so the next reader sizes the blast radius correctly. Co-authored-by: philluiz2323 --- crates/engine/src/game/effects/mod.rs | 96 ++++++++++++++++++++++++--- 1 file changed, 86 insertions(+), 10 deletions(-) diff --git a/crates/engine/src/game/effects/mod.rs b/crates/engine/src/game/effects/mod.rs index 376b494b8f..f936ffed9c 100644 --- a/crates/engine/src/game/effects/mod.rs +++ b/crates/engine/src/game/effects/mod.rs @@ -1853,15 +1853,26 @@ fn moved_object_context_from_events(events: &[GameEvent]) -> Option