Complex Combat Edge Cases - Test Documentation
Overview
This document outlines test cases for complex interactions in combat that involve multiple mechanics working together. These scenarios test the robustness of our Commander engine when dealing with intricate card interactions and edge cases.
Test Case: Multiple Triggers During Combat
Test: Death Triggers During Combat
#![allow(unused)] fn main() { #[test] fn test_multiple_death_triggers_during_combat() { // Test setup let mut app = App::new(); app.add_plugins(MinimalPlugins) .add_systems(Update, (combat_damage_system, trigger_system, state_based_actions_system)); // Create creatures with death triggers let creature1 = app.world.spawn(( Creature { power: 3, toughness: 3 }, Attacking { defending: defender_entity }, Health { current: 3, maximum: 3 }, DiesTrigger { effect: TriggerEffect::DealDamage { amount: 2, target: TargetType::Player(defender_entity), } }, )).id(); let creature2 = app.world.spawn(( Creature { power: 3, toughness: 3 }, Blocking { blocked_attackers: vec![creature1] }, Health { current: 3, maximum: 3 }, DiesTrigger { effect: TriggerEffect::GainLife { amount: 2, target: TargetType::Player(controller_entity), } }, )).id(); let defender_entity = app.world.spawn(( Player {}, Health { current: 20, maximum: 20 }, )).id(); let controller_entity = app.world.spawn(( Player {}, Health { current: 20, maximum: 20 }, )).id(); // Process combat damage app.world.resource_mut::<TurnManager>().current_phase = Phase::Combat(CombatStep::CombatDamage); app.update(); // Check state-based actions app.update(); // Process death triggers app.update(); // Verify both creatures died assert!(app.world.get::<Health>(creature1).is_none()); assert!(app.world.get::<Health>(creature2).is_none()); // Verify both death triggers resolved let defender_health = app.world.get::<Health>(defender_entity).unwrap(); assert_eq!(defender_health.current, 18); // 20 - 2 from death trigger let controller_health = app.world.get::<Health>(controller_entity).unwrap(); assert_eq!(controller_health.current, 22); // 20 + 2 from death trigger } }
Test Case: Replacement Effects During Combat
Test: Damage Prevention/Redirection
#![allow(unused)] fn main() { #[test] fn test_damage_replacement_effects() { // Test setup let mut app = App::new(); app.add_plugins(MinimalPlugins) .add_systems(Update, (combat_damage_system, replacement_effects_system)); // Create attacker and blocker let attacker = app.world.spawn(( Creature { power: 5, toughness: 5 }, Attacking { defending: defender_entity }, )).id(); let blocker = app.world.spawn(( Creature { power: 3, toughness: 3 }, Blocking { blocked_attackers: vec![attacker] }, Health { current: 3, maximum: 3 }, // Replacement effect: Redirect half of all damage to controller (rounded up) DamageReplacementEffect { effect_type: ReplacementType::Redirect { percentage: 50, target: TargetType::Player(controller_entity), round_up: true, } }, )).id(); let defender_entity = app.world.spawn(( Player {}, Health { current: 20, maximum: 20 }, )).id(); let controller_entity = app.world.spawn(( Player {}, Health { current: 20, maximum: 20 }, )).id(); // Process combat damage app.world.resource_mut::<TurnManager>().current_phase = Phase::Combat(CombatStep::CombatDamage); app.update(); // Verify damage was redirected let blocker_health = app.world.get::<Health>(blocker).unwrap(); assert_eq!(blocker_health.current, 1); // 3 - (5 - 3) = 1 let controller_health = app.world.get::<Health>(controller_entity).unwrap(); assert_eq!(controller_health.current, 17); // 20 - 3 (redirected damage) = 17 // Verify attacker also took damage let attacker_health = app.world.get::<Health>(attacker).unwrap(); assert_eq!(attacker_health.current, 2); // 5 - 3 = 2 } }
Test Case: Layer-Dependent Effects
Test: Characteristic-Defining Abilities and Pump Spells
#![allow(unused)] fn main() { #[test] fn test_layer_dependent_effects() { // Test setup let mut app = App::new(); app.add_plugins(MinimalPlugins) .add_systems(Update, (spell_resolution_system, characteristic_layer_system)); // Create creature with CDA (power/toughness equal to cards in hand) let creature = app.world.spawn(( CreatureCard {}, // Base power/toughness are handled in layer 7a CharacteristicDefiningAbility { affects: Characteristic::PowerToughness, calculation: Box::new(|world, entity| { // We'll mock this for the test - cards in hand = 4 let cards_in_hand = 4; (cards_in_hand, cards_in_hand) }), }, )).id(); // Process CDA to establish base power/toughness app.update(); // Verify creature has power/toughness equal to cards in hand let creature_stats = app.world.get::<Creature>(creature).unwrap(); assert_eq!(creature_stats.power, 4); assert_eq!(creature_stats.toughness, 4); // Cast pump spell (+2/+2) - this applies in layer 7c app.world.spawn(( PumpSpell { target: creature, power_bonus: 2, toughness_bonus: 2, duration: SpellDuration::EndOfTurn, }, SpellOnStack {}, )); // Resolve spell app.update(); // Verify creature stats were boosted correctly let boosted_creature = app.world.get::<Creature>(creature).unwrap(); assert_eq!(boosted_creature.power, 6); // 4 + 2 = 6 assert_eq!(boosted_creature.toughness, 6); // 4 + 2 = 6 // Discard a card - affects layer 7a CDA app.world.spawn(DiscardCardCommand { player: Entity::PLACEHOLDER, count: 1, }); app.update(); // Verify stats update correctly (CDA now sees 3 cards, then +2/+2 applies) let updated_creature = app.world.get::<Creature>(creature).unwrap(); assert_eq!(updated_creature.power, 5); // 3 + 2 = 5 assert_eq!(updated_creature.toughness, 5); // 3 + 2 = 5 } }
Test Case: Changing Controllers During Combat
Test: Creature Changes Controller During Combat
#![allow(unused)] fn main() { #[test] fn test_creature_changes_controller_during_combat() { // Test setup let mut app = App::new(); app.add_plugins(MinimalPlugins) .add_systems(Update, (combat_damage_system, spell_resolution_system, controller_system)); let player1 = app.world.spawn(Player {}).id(); let player2 = app.world.spawn(Player {}).id(); // Create attacking creature controlled by player 1 let attacker = app.world.spawn(( Creature { power: 3, toughness: 3 }, Attacking { defending: player2 }, CreatureCard { controller: player1 }, )).id(); // Cast control-changing spell before damage app.world.spawn(( ControlChangeSpell { target: attacker, new_controller: player2, duration: SpellDuration::EndOfTurn, }, SpellOnStack {}, )); // Resolve spell app.update(); // Verify creature changed controllers let creature_card = app.world.get::<CreatureCard>(attacker).unwrap(); assert_eq!(creature_card.controller, player2); // Process combat damage app.world.resource_mut::<TurnManager>().current_phase = Phase::Combat(CombatStep::CombatDamage); app.update(); // Verify creature is still attacking the same player assert!(app.world.get::<Attacking>(attacker).is_some()); assert_eq!(app.world.get::<Attacking>(attacker).unwrap().defending, player2); // Verify no damage was dealt (can't attack yourself) let player2_health = app.world.get::<Health>(player2).unwrap(); assert_eq!(player2_health.current, player2_health.maximum); } }
Test Case: Triggered Abilities Affecting Combat
Test: Attack Triggers Modifying Other Creatures
#![allow(unused)] fn main() { #[test] fn test_attack_trigger_modifying_combat() { // Test setup let mut app = App::new(); app.add_plugins(MinimalPlugins) .add_systems(Update, (declare_attackers_system, trigger_system, combat_damage_system)); let player1 = app.world.spawn(Player {}).id(); let player2 = app.world.spawn(Player { health: Health { current: 20, maximum: 20 }, }).id(); // Create creature with attack trigger let battle_leader = app.world.spawn(( Creature { power: 2, toughness: 2 }, CreatureCard { controller: player1 }, AttackTrigger { effect: TriggerEffect::PumpCreatures { target_filter: TargetFilter::Controlled { controller: player1 }, power_bonus: 1, toughness_bonus: 1, duration: SpellDuration::EndOfTurn, } }, )).id(); // Create another creature that will benefit from the trigger let other_attacker = app.world.spawn(( Creature { power: 2, toughness: 2 }, CreatureCard { controller: player1 }, )).id(); // Declare attackers app.world.entity_mut(battle_leader).insert(Attacking { defending: player2 }); app.world.entity_mut(other_attacker).insert(Attacking { defending: player2 }); // Process attack triggers app.world.resource_mut::<TurnManager>().current_phase = Phase::Combat(CombatStep::DeclareAttackers); app.update(); // Verify both creatures were boosted let battle_leader_stats = app.world.get::<Creature>(battle_leader).unwrap(); assert_eq!(battle_leader_stats.power, 3); // 2 + 1 = 3 let other_attacker_stats = app.world.get::<Creature>(other_attacker).unwrap(); assert_eq!(other_attacker_stats.power, 3); // 2 + 1 = 3 // Process combat damage app.world.resource_mut::<TurnManager>().current_phase = Phase::Combat(CombatStep::CombatDamage); app.update(); // Verify damage with the boost let player2_health = app.world.get::<Health>(player2).unwrap(); assert_eq!(player2_health.current, 14); // 20 - 3 - 3 = 14 } }
Test Case: Damage Doubling and Prevention Interactions
Test: Damage Doubling with Prevention Shield
#![allow(unused)] fn main() { #[test] fn test_damage_doubling_with_prevention() { // Test setup let mut app = App::new(); app.add_plugins(MinimalPlugins) .add_systems(Update, (combat_damage_system, replacement_effects_system)); // Create attacker with damage doubling effect let attacker = app.world.spawn(( Creature { power: 4, toughness: 4 }, Attacking { defending: defender_entity }, DamageReplacementEffect { effect_type: ReplacementType::Double { condition: DamageCondition::DealingCombatDamage, } }, )).id(); // Create defender with protection shield let defender_entity = app.world.spawn(( Player {}, Health { current: 20, maximum: 20 }, PreventDamage { amount: 5, source_filter: None, }, )).id(); // Process combat damage app.world.resource_mut::<TurnManager>().current_phase = Phase::Combat(CombatStep::CombatDamage); app.update(); // The original 4 damage is doubled to 8, then 5 is prevented let defender_health = app.world.get::<Health>(defender_entity).unwrap(); assert_eq!(defender_health.current, 17); // 20 - (8 - 5) = 17 } }
Test Case: Indestructible in Combat
Test: Indestructible Creature with Lethal Damage
#![allow(unused)] fn main() { #[test] fn test_indestructible_with_lethal_damage() { // Test setup let mut app = App::new(); app.add_plugins(MinimalPlugins) .add_systems(Update, (combat_damage_system, state_based_actions_system)); // Create indestructible attacker let indestructible_attacker = app.world.spawn(( Creature { power: 4, toughness: 4 }, Attacking { defending: defender_entity }, Health { current: 4, maximum: 4 }, Indestructible {}, )).id(); // Create blocker with deathtouch let deathtouch_blocker = app.world.spawn(( Creature { power: 1, toughness: 1 }, Blocking { blocked_attackers: vec![indestructible_attacker] }, Health { current: 1, maximum: 1 }, Deathtouch {}, )).id(); let defender_entity = app.world.spawn(( Player {}, Health { current: 20, maximum: 20 }, )).id(); // Process combat damage app.world.resource_mut::<TurnManager>().current_phase = Phase::Combat(CombatStep::CombatDamage); app.update(); // Check state-based actions app.update(); // Verify blocker died assert!(app.world.get::<Health>(deathtouch_blocker).is_none()); // Verify indestructible attacker is damaged but alive let attacker_health = app.world.get::<Health>(indestructible_attacker).unwrap(); assert_eq!(attacker_health.current, 3); // 4 - 1 = 3 assert!(app.world.entity(indestructible_attacker).contains::<Indestructible>()); // Verify it can still be in further combats despite lethal deathtouch damage let attacker_health = app.world.get::<Health>(indestructible_attacker).unwrap(); app.world.entity_mut(indestructible_attacker).insert( DamageReceived { amount: 10, source: deathtouch_blocker, is_combat_damage: true, } ); // Process more damage app.update(); // Check state-based actions app.update(); // Verify indestructible creature is still alive despite lethal damage assert!(app.world.get::<Health>(indestructible_attacker).is_some()); } }
Test Case: Phasing and Pump Spell Interactions
Test: Phasing Out After Pump Spell
#![allow(unused)] fn main() { #[test] fn test_phasing_after_pump_spell() { // Test setup let mut app = App::new(); app.add_plugins(MinimalPlugins) .add_systems(Update, (spell_resolution_system, phasing_system, end_of_turn_system)); // Create creature let creature = app.world.spawn(( Creature { power: 2, toughness: 2 }, CreatureCard {}, )).id(); // Cast pump spell app.world.spawn(( PumpSpell { target: creature, power_bonus: 2, toughness_bonus: 2, duration: SpellDuration::EndOfTurn, }, SpellOnStack {}, )); // Resolve spell app.update(); // Verify creature stats were boosted let boosted_creature = app.world.get::<Creature>(creature).unwrap(); assert_eq!(boosted_creature.power, 4); assert_eq!(boosted_creature.toughness, 4); // Phase out the creature app.world.entity_mut(creature).insert(PhasedOut {}); // Process end of turn app.world.resource_mut::<TurnManager>().current_phase = Phase::End(EndPhaseStep::End); app.update(); // The creature is phased out, so end of turn effects shouldn't affect it // Phase the creature back in app.world.entity_mut(creature).remove::<PhasedOut>(); // Now the pump effect should still be active (since it didn't wear off while phased out) let still_boosted = app.world.get::<Creature>(creature).unwrap(); assert_eq!(still_boosted.power, 4); assert_eq!(still_boosted.toughness, 4); // Process another end of turn app.update(); // Now the effect should expire let end_of_turn = app.world.get::<Creature>(creature).unwrap(); assert_eq!(end_of_turn.power, 2); assert_eq!(end_of_turn.toughness, 2); } }
Performance Considerations for Edge Case Tests
-
Efficient Interaction Processing: Ensure complex interaction processing is optimized.
-
Minimize Redundant Query Operations: Structure queries to minimize redundant operations.
-
Layer System Optimization: Efficiently process layer-dependent effects in the correct order.
Test Coverage Checklist
- Multiple death triggers during combat
- Damage replacement/redirection effects
- Layer-dependent ability interactions
- Controller change during combat
- Attack triggers modifying other creatures
- Damage doubling with prevention
- Indestructible creatures with lethal damage
- Phasing and pump spell interactions
Additional Considerations
- Tests should verify both immediate effects and downstream consequences
- Multiple mechanics should be tested in combination to ensure correct interactions
- Order-dependent effects should be tested with different sequencing
- Extreme edge cases should be included to stress-test the system