Phasing During Combat - Test Cases
Overview
Phasing is a complex mechanic where permanents temporarily leave and re-enter the game without triggering "leaves the battlefield" or "enters the battlefield" effects. When a creature phases out during combat, several edge cases can occur that require special handling.
This document outlines test cases for phasing interactions during combat in our Commander engine.
Test Case: Attacker Phases Out
Test: Attacking Creature Phases Out During Declare Attackers
#![allow(unused)] fn main() { #[test] fn test_attacker_phases_out_after_declare_attackers() { // Test setup let mut app = App::new(); app.add_plugins(MinimalPlugins) .add_systems(Update, (declare_attackers_system, phasing_system)); // Create attacker and defending player let attacker = app.world.spawn(( Creature { power: 3, toughness: 3, }, Attacking { defending: defender_entity }, )).id(); let defender_entity = app.world.spawn(( Player {}, Health { current: 20, maximum: 20 }, )).id(); // Phase out the attacker app.world.spawn(PhaseOutCommand { target: attacker }); // Process phasing app.update(); // Verify the creature is phased out but still marked as attacking assert!(app.world.get::<PhasedOut>(attacker).is_some()); assert!(app.world.get::<Attacking>(attacker).is_some()); // When combat damage is processed, it should not deal damage app.world.resource_mut::<TurnManager>().current_phase = Phase::Combat(CombatStep::CombatDamage); app.update(); // Verify no damage was dealt to defender let health = app.world.get::<Health>(defender_entity).unwrap(); assert_eq!(health.current, 20); } }
Test: Phased Out Attacker at End of Combat
#![allow(unused)] fn main() { #[test] fn test_phased_out_attacker_at_end_of_combat() { // Test setup let mut app = App::new(); app.add_plugins(MinimalPlugins) .add_systems(Update, (end_of_combat_system, phasing_system)); // Create phased out attacker let attacker = app.world.spawn(( Creature { power: 3, toughness: 3, }, Attacking { defending: defender_entity }, PhasedOut {}, )).id(); // Process end of combat app.world.resource_mut::<TurnManager>().current_phase = Phase::Combat(CombatStep::End); app.world.resource_mut::<CombatSystem>().end_of_combat.triggers_processed = true; app.update(); // Verify attacking status is removed, even though creature is phased out assert!(!app.world.entity(attacker).contains::<Attacking>()); } }
Test Case: Blocker Phases Out
Test: Blocking Creature Phases Out During Declare Blockers
#![allow(unused)] fn main() { #[test] fn test_blocker_phases_out_after_declare_blockers() { // Test setup let mut app = App::new(); app.add_plugins(MinimalPlugins) .add_systems(Update, (declare_blockers_system, phasing_system, combat_damage_system)); // Create attacker, blocker, and player let attacker = app.world.spawn(( Creature { power: 3, toughness: 3, }, Attacking { defending: defender_entity }, )).id(); let blocker = app.world.spawn(( Creature { power: 2, toughness: 2, }, Blocking { blocked_attackers: vec![attacker] }, )).id(); let defender_entity = app.world.spawn(( Player {}, Health { current: 20, maximum: 20 }, )).id(); // Phase out the blocker app.world.spawn(PhaseOutCommand { target: blocker }); // Process phasing app.update(); // Verify the creature is phased out but still marked as blocking assert!(app.world.get::<PhasedOut>(blocker).is_some()); assert!(app.world.get::<Blocking>(blocker).is_some()); // When combat damage is processed, attacker should deal damage to player // since phased out blocker cannot block app.world.resource_mut::<TurnManager>().current_phase = Phase::Combat(CombatStep::CombatDamage); app.update(); // Verify damage was dealt to defender (attacker was effectively unblocked) let health = app.world.get::<Health>(defender_entity).unwrap(); assert_eq!(health.current, 17); } }
Test Case: Phasing During Combat Damage
Test: Creature Phases Out in Response to Damage
#![allow(unused)] fn main() { #[test] fn test_creature_phases_out_in_response_to_damage() { // Test setup let mut app = App::new(); app.add_plugins(MinimalPlugins) .add_systems(Update, (combat_damage_system, phasing_system)); // Create attacker and blocker let attacker = app.world.spawn(( Creature { power: 3, toughness: 3, }, Attacking { defending: defender_entity }, )).id(); let blocker = app.world.spawn(( Creature { power: 2, toughness: 2, }, Blocking { blocked_attackers: vec![attacker] }, Health { current: 2, maximum: 2 }, )).id(); // Simulate player responding to damage by phasing out the blocker // (in a real implementation, this would be triggered by priority passing) app.world.spawn(PhaseOutCommand { target: blocker, trigger_condition: TriggerCondition::BeforeDamage, }); // Process combat damage app.world.resource_mut::<TurnManager>().current_phase = Phase::Combat(CombatStep::CombatDamage); app.update(); // Verify blocker phased out and did not receive damage assert!(app.world.get::<PhasedOut>(blocker).is_some()); let health = app.world.get::<Health>(blocker).unwrap(); assert_eq!(health.current, 2); // Health unchanged // Verify attacker did not receive damage either let attacker_health = app.world.get::<Health>(attacker).unwrap(); assert_eq!(attacker_health.current, attacker_health.maximum); } }
Test Case: Phasing In During Combat
Test: Creature Phases In During Combat
#![allow(unused)] fn main() { #[test] fn test_creature_phases_in_during_combat() { // Test setup let mut app = App::new(); app.add_plugins(MinimalPlugins) .add_systems(Update, (phasing_system, combat_system)); // Create phased out creature let creature = app.world.spawn(( Creature { power: 3, toughness: 3, }, PhasedOut {}, )).id(); // Set up phase in trigger for declare blockers step app.world.spawn(PhaseInCommand { target: creature, trigger_phase: Phase::Combat(CombatStep::DeclareBlockers), }); // Advance to declare blockers app.world.resource_mut::<TurnManager>().current_phase = Phase::Combat(CombatStep::DeclareBlockers); app.update(); // Verify creature phased in assert!(app.world.get::<PhasedOut>(creature).is_none()); // Verify creature cannot be declared as a blocker this combat // (creatures that phase in are treated as though they just entered the battlefield) let can_block = app.world.get_resource::<CombatSystem>().unwrap() .can_block(creature, attacker_entity); assert!(!can_block); } }
Test Case: Phasing with Auras and Equipment
Test: Equipped Creature Phases Out
#![allow(unused)] fn main() { #[test] fn test_equipped_creature_phases_out_during_combat() { // Test setup let mut app = App::new(); app.add_plugins(MinimalPlugins) .add_systems(Update, (phasing_system, combat_damage_system)); // Create creature with equipment let creature = app.world.spawn(( Creature { power: 2, toughness: 2, }, Attacking { defending: defender_entity }, )).id(); let equipment = app.world.spawn(( Equipment { equipped_to: creature, power_bonus: 2, toughness_bonus: 0, }, Attached { attached_to: creature }, )).id(); // Phase out the creature app.world.spawn(PhaseOutCommand { target: creature }); app.update(); // Verify creature and equipment are both phased out assert!(app.world.get::<PhasedOut>(creature).is_some()); assert!(app.world.get::<PhasedOut>(equipment).is_some()); // Process combat damage app.world.resource_mut::<TurnManager>().current_phase = Phase::Combat(CombatStep::CombatDamage); app.update(); // Verify no damage was dealt let health = app.world.get::<Health>(defender_entity).unwrap(); assert_eq!(health.current, 20); // Phase in during a later turn app.world.spawn(PhaseInCommand { target: creature, trigger_phase: Phase::Main(MainPhaseStep::First), }); app.world.resource_mut::<TurnManager>().current_phase = Phase::Main(MainPhaseStep::First); app.update(); // Verify both creature and equipment phased in and are still attached assert!(app.world.get::<PhasedOut>(creature).is_none()); assert!(app.world.get::<PhasedOut>(equipment).is_none()); assert_eq!(app.world.get::<Equipment>(equipment).unwrap().equipped_to, creature); assert_eq!(app.world.get::<Attached>(equipment).unwrap().attached_to, creature); } }
Integration with Turn Structure
Test: Phasing and Turn Phases
#![allow(unused)] fn main() { #[test] fn test_phasing_respects_turn_structure() { // Test setup let mut app = App::new(); app.add_plugins(MinimalPlugins) .add_systems(Update, (phasing_system, turn_system)); // Create creature that phases out let creature = app.world.spawn(( Creature { power: 3, toughness: 3, }, )).id(); // Phase out every other turn app.world.spawn(RecurringPhaseCommand { target: creature, phase_out_on: TurnCondition::ActivePlayerTurn, phase_in_on: TurnCondition::NonActivePlayerTurn, }); // Set active player app.world.resource_mut::<TurnManager>().active_player = player1_entity; app.update(); // Verify creature is phased out on active player's turn assert!(app.world.get::<PhasedOut>(creature).is_some()); // Change active player app.world.resource_mut::<TurnManager>().active_player = player2_entity; app.update(); // Verify creature phases in on non-active player's turn assert!(app.world.get::<PhasedOut>(creature).is_none()); } }
Performance Considerations for Phasing Tests
-
Batch Phasing Operations: Group phasing operations to minimize entity access.
-
Efficient Phase Tracking: Use a more efficient system for tracking phased entities.
-
Minimize Query Iterations: Structure queries to minimize iterations through phased entities.
Test Coverage Checklist
- Attackers phasing out during declare attackers
- Attackers phasing out during combat damage
- Blockers phasing out during declare blockers
- Blockers phasing out during combat damage
- Creatures phasing in during combat
- End of combat cleanup with phased entities
- Phasing with attached permanents (equipment, auras)
- Turn structure integration with phasing
Additional Edge Cases to Consider
- Multiple creatures phasing in/out simultaneously
- Phasing commander in and out (commander damage tracking)
- Phasing and "enters the battlefield" replacement effects
- Phasing and state-based actions