Commander-Specific Combat Tests
Overview
This document outlines test cases for Commander-specific combat scenarios, focusing on the unique rules and edge cases that arise in the Commander format. These tests are critical for ensuring our Commander engine correctly handles format-specific interactions during combat.
Test Case: Commander Damage Tracking
Test: Multiple Sources of Commander Damage
#![allow(unused)] fn main() { #[test] fn test_multiple_commanders_damage_tracking() { // Test setup let mut app = App::new(); app.add_plugins(MinimalPlugins) .add_systems(Update, (combat_damage_system, state_based_actions_system)); // Create player let player = app.world.spawn(( Player {}, Health { current: 40, maximum: 40 }, CommanderDamage::default(), )).id(); // Create commanders from different players let commander1 = app.world.spawn(( Creature { power: 5, toughness: 5 }, Commander { owner: opponent1 }, Attacking { defending: player }, )).id(); let commander2 = app.world.spawn(( Creature { power: 3, toughness: 3 }, Commander { owner: opponent2 }, Attacking { defending: player }, )).id(); let opponent1 = app.world.spawn(Player {}).id(); let opponent2 = app.world.spawn(Player {}).id(); // Process combat damage for first attack app.world.resource_mut::<TurnManager>().current_phase = Phase::Combat(CombatStep::CombatDamage); app.update(); // Verify commander damage was tracked separately let cmd_damage = app.world.get::<CommanderDamage>(player).unwrap(); assert_eq!(cmd_damage.get_damage(commander1), 5); assert_eq!(cmd_damage.get_damage(commander2), 3); assert_eq!(cmd_damage.total_damage(), 8); // Player's health should also be reduced let health = app.world.get::<Health>(player).unwrap(); assert_eq!(health.current, 32); // 40 - 5 - 3 = 32 // More damage from commander1 in a later combat app.world.entity_mut(commander1).insert( DamageEvent { source: commander1, target: player, amount: 5, is_combat_damage: true, } ); // Process the damage app.update(); // Check state-based actions after damage app.update(); // Verify commander damage accumulated correctly let cmd_damage_after = app.world.get::<CommanderDamage>(player).unwrap(); assert_eq!(cmd_damage_after.get_damage(commander1), 10); assert_eq!(cmd_damage_after.get_damage(commander2), 3); // Verify player still alive (no commander has dealt 21+ damage yet) assert!(app.world.entity(player).contains::<Health>()); // Deal more damage from commander1 to reach lethal commander damage app.world.entity_mut(commander1).insert( DamageEvent { source: commander1, target: player, amount: 11, is_combat_damage: true, } ); // Process the damage app.update(); // Check state-based actions after damage app.update(); // Verify player lost due to commander damage let cmd_damage_final = app.world.get::<CommanderDamage>(player).unwrap(); assert_eq!(cmd_damage_final.get_damage(commander1), 21); assert!(app.world.entity(player).contains::<PlayerEliminated>()); } }
Test: Commander Damage from Copied Commander
#![allow(unused)] fn main() { #[test] fn test_commander_damage_from_clone() { // Test setup let mut app = App::new(); app.add_plugins(MinimalPlugins) .add_systems(Update, (combat_damage_system, clone_effects_system)); // Create player let player = app.world.spawn(( Player {}, Health { current: 40, maximum: 40 }, CommanderDamage::default(), )).id(); // Create commander let commander = app.world.spawn(( Creature { power: 5, toughness: 5 }, Commander { owner: opponent }, CreatureCard { controller: opponent }, )).id(); let opponent = app.world.spawn(Player {}).id(); // Create clone of the commander let clone = app.world.spawn(( Creature { power: 5, toughness: 5 }, CloneEffect { source: commander }, CreatureCard { controller: opponent }, )).id(); // Make clone attack app.world.entity_mut(clone).insert(Attacking { defending: player }); // Process combat damage app.world.resource_mut::<TurnManager>().current_phase = Phase::Combat(CombatStep::CombatDamage); app.update(); // Verify damage from clone is NOT commander damage let cmd_damage = app.world.get::<CommanderDamage>(player).unwrap(); assert_eq!(cmd_damage.get_damage(commander), 0); assert_eq!(cmd_damage.get_damage(clone), 0); // Player's health should still be reduced let health = app.world.get::<Health>(player).unwrap(); assert_eq!(health.current, 35); // 40 - 5 = 35 } }
Test Case: Commander Combat Abilities
Test: Commander with Partner
#![allow(unused)] fn main() { #[test] fn test_partner_commanders_in_combat() { // Test setup let mut app = App::new(); app.add_plugins(MinimalPlugins) .add_systems(Update, (combat_damage_system, state_based_actions_system)); // Create player let player = app.world.spawn(( Player {}, Health { current: 40, maximum: 40 }, CommanderDamage::default(), )).id(); // Create partner commanders let partner1 = app.world.spawn(( Creature { power: 3, toughness: 3 }, Commander { owner: opponent }, Partner {}, Attacking { defending: player }, )).id(); let partner2 = app.world.spawn(( Creature { power: 2, toughness: 2 }, Commander { owner: opponent }, Partner {}, Attacking { defending: player }, )).id(); let opponent = app.world.spawn(Player {}).id(); // Process combat damage app.world.resource_mut::<TurnManager>().current_phase = Phase::Combat(CombatStep::CombatDamage); app.update(); // Verify commander damage was tracked separately for each partner let cmd_damage = app.world.get::<CommanderDamage>(player).unwrap(); assert_eq!(cmd_damage.get_damage(partner1), 3); assert_eq!(cmd_damage.get_damage(partner2), 2); // Player needs to take 21 damage from a single partner to lose // Simulate multiple combats to test this for _ in 0..6 { app.world.entity_mut(partner1).insert( DamageEvent { source: partner1, target: player, amount: 3, is_combat_damage: true, } ); app.update(); } // Check state-based actions app.update(); // Verify player lost due to commander damage from one partner let cmd_damage_final = app.world.get::<CommanderDamage>(player).unwrap(); assert_eq!(cmd_damage_final.get_damage(partner1), 21); assert!(app.world.entity(player).contains::<PlayerEliminated>()); } }
Test: Commander with Combat Damage Triggers
#![allow(unused)] fn main() { #[test] fn test_commander_combat_damage_triggers() { // Test setup let mut app = App::new(); app.add_plugins(MinimalPlugins) .add_systems(Update, (combat_damage_system, trigger_system)); // Create player let player = app.world.spawn(( Player {}, Health { current: 40, maximum: 40 }, CommanderDamage::default(), )).id(); // Create commander with combat damage trigger let commander = app.world.spawn(( Creature { power: 5, toughness: 5 }, Commander { owner: opponent }, Attacking { defending: player }, CombatDamageTrigger { effect: TriggerEffect::DrawCards { player: opponent, count: 1, } }, )).id(); let opponent = app.world.spawn(( Player {}, Library { cards: vec![card1, card2, card3] }, Hand { cards: vec![] }, )).id(); let card1 = app.world.spawn(Card {}).id(); let card2 = app.world.spawn(Card {}).id(); let card3 = app.world.spawn(Card {}).id(); // Process combat damage app.world.resource_mut::<TurnManager>().current_phase = Phase::Combat(CombatStep::CombatDamage); app.update(); // Process triggers app.update(); // Verify commander damage was tracked let cmd_damage = app.world.get::<CommanderDamage>(player).unwrap(); assert_eq!(cmd_damage.get_damage(commander), 5); // Verify combat damage trigger resolved let hand = app.world.get::<Hand>(opponent).unwrap(); assert_eq!(hand.cards.len(), 1); } }
Test Case: Multiplayer Combat Scenarios
Test: Attacking Multiple Players with Commander
#![allow(unused)] fn main() { #[test] fn test_attacking_multiple_players_with_commander() { // Test setup let mut app = App::new(); app.add_plugins(MinimalPlugins) .add_systems(Update, (declare_attackers_system, combat_damage_system)); // Create players let player1 = app.world.spawn(( Player {}, Health { current: 40, maximum: 40 }, CommanderDamage::default(), )).id(); let player2 = app.world.spawn(( Player {}, Health { current: 40, maximum: 40 }, CommanderDamage::default(), )).id(); let active_player = app.world.spawn(( Player {}, ActivePlayer {}, )).id(); // Create commander with vigilance (can attack multiple players) let commander = app.world.spawn(( Creature { power: 5, toughness: 5 }, Commander { owner: active_player }, CreatureCard { controller: active_player }, Vigilance {}, )).id(); // Create additional attacker let other_attacker = app.world.spawn(( Creature { power: 3, toughness: 3 }, CreatureCard { controller: active_player }, )).id(); // Declare attackers for different players app.world.resource_mut::<AttackDeclaration>().declare_attacker(commander, player1); app.world.resource_mut::<AttackDeclaration>().declare_attacker(other_attacker, player2); // Process declare attackers app.world.resource_mut::<TurnManager>().current_phase = Phase::Combat(CombatStep::DeclareAttackers); app.update(); // Process combat damage app.world.resource_mut::<TurnManager>().current_phase = Phase::Combat(CombatStep::CombatDamage); app.update(); // Verify commander damage was tracked for player1 only let cmd_damage1 = app.world.get::<CommanderDamage>(player1).unwrap(); assert_eq!(cmd_damage1.get_damage(commander), 5); let cmd_damage2 = app.world.get::<CommanderDamage>(player2).unwrap(); assert_eq!(cmd_damage2.get_damage(commander), 0); // Verify health reduction for both players let health1 = app.world.get::<Health>(player1).unwrap(); assert_eq!(health1.current, 35); // 40 - 5 = 35 let health2 = app.world.get::<Health>(player2).unwrap(); assert_eq!(health2.current, 37); // 40 - 3 = 37 } }
Test: Goad Effect on Commander
#![allow(unused)] fn main() { #[test] fn test_goaded_commander() { // Test setup let mut app = App::new(); app.add_plugins(MinimalPlugins) .add_systems(Update, (declare_attackers_system, attack_restrictions_system)); // Create players let player1 = app.world.spawn(Player {}).id(); let player2 = app.world.spawn(Player {}).id(); let player3 = app.world.spawn(Player {}).id(); // Create commander that's been goaded by player2 let commander = app.world.spawn(( Creature { power: 5, toughness: 5 }, Commander { owner: player1 }, CreatureCard { controller: player1 }, Goad { goaded_by: player2 }, )).id(); // Set up attack validation app.world.resource_mut::<TurnManager>().active_player = player1; app.world.resource_mut::<CombatSystem>().validate_attack = |attacker, defender, world| { // Basic validation - can't attack yourself let creature_card = world.get::<CreatureCard>(attacker).unwrap(); if creature_card.controller == defender { return Err(AttackError::IllegalTarget); } // Check goad restrictions if let Some(goad) = world.get::<Goad>(attacker) { if goad.goaded_by != defender && defender != player3 { return Err(AttackError::MustAttackGoader); } } Ok(()) }; // Try to attack player3 (allowed because not the goader) assert!(app.world.resource::<CombatSystem>() .validate_attack(commander, player3, &app.world) .is_ok()); // Try to attack player2 (allowed because this is the goader) assert!(app.world.resource::<CombatSystem>() .validate_attack(commander, player2, &app.world) .is_ok()); // Try to attack self (not allowed) assert!(app.world.resource::<CombatSystem>() .validate_attack(commander, player1, &app.world) .is_err()); // Commander must attack if able app.world.resource_mut::<AttackRequirements>().creatures_that_must_attack.push(commander); // Process attack requirements app.update(); // Verify commander is forced to attack assert!(app.world.get::<MustAttack>(commander).is_some()); } }
Test Case: Commander Zone Interactions
Test: Combat Damage Sending Commander to Command Zone
#![allow(unused)] fn main() { #[test] fn test_commander_to_command_zone_from_combat() { // Test setup let mut app = App::new(); app.add_plugins(MinimalPlugins) .add_systems(Update, (combat_damage_system, state_based_actions_system, zone_change_system)); // Create commander let commander = app.world.spawn(( Creature { power: 3, toughness: 3 }, Commander { owner: player }, CreatureCard { controller: player }, Health { current: 3, maximum: 3 }, )).id(); let player = app.world.spawn(( Player {}, CommandZone { commanders: vec![] }, )).id(); // Create attacking creature let attacker = app.world.spawn(( Creature { power: 5, toughness: 5 }, Attacking { defending: player }, )).id(); // Make commander block app.world.entity_mut(commander).insert( Blocking { blocked_attackers: vec![attacker] } ); // Process combat damage app.world.resource_mut::<TurnManager>().current_phase = Phase::Combat(CombatStep::CombatDamage); app.update(); // Check state-based actions - commander should die from damage app.update(); // Player should be prompted to send commander to command zone // We'll simulate them choosing to do so app.world.spawn( ZoneChangeRequest { entity: commander, from: Zone::Battlefield, to: Zone::CommandZone, reason: ZoneChangeReason::Death, } ); // Process zone change app.update(); // Verify commander is in command zone assert!(!app.world.entity(commander).contains::<Health>()); assert!(!app.world.entity(commander).contains::<Blocking>()); let command_zone = app.world.get::<CommandZone>(player).unwrap(); assert!(command_zone.commanders.contains(&commander)); } }
Test: Combat with Commander from Command Zone
#![allow(unused)] fn main() { #[test] fn test_cast_commander_from_command_zone() { // Test setup let mut app = App::new(); app.add_plugins(MinimalPlugins) .add_systems(Update, (cast_from_command_zone_system, declare_attackers_system)); // Create player let player = app.world.spawn(( Player {}, Mana { white: 5, blue: 5, black: 5, red: 5, green: 5, colorless: 5, }, CommandZone { commanders: vec![commander] }, )).id(); // Create commander in command zone let commander = app.world.spawn(( Commander { owner: player }, CreatureCard { controller: player }, ManaCost { white: 0, blue: 0, black: 0, red: 0, green: 0, colorless: 4, additional_cost: 0, // No command tax yet }, InZone { zone: Zone::CommandZone }, )).id(); // Cast commander from command zone app.world.spawn( CastRequest { card: commander, controller: player, from_zone: Zone::CommandZone, } ); // Process casting app.update(); // Commander should now be on battlefield assert!(app.world.entity(commander).contains::<Creature>()); assert_eq!(app.world.get::<InZone>(commander).unwrap().zone, Zone::Battlefield); // Verify mana was spent let mana_after = app.world.get::<Mana>(player).unwrap(); assert_eq!(mana_after.colorless, 1); // 5 - 4 = 1 // Declare commander as attacker app.world.resource_mut::<TurnManager>().current_phase = Phase::Combat(CombatStep::DeclareAttackers); app.world.resource_mut::<TurnManager>().active_player = player; app.world.resource_mut::<AttackDeclaration>().declare_attacker(commander, Entity::PLACEHOLDER); // Process declare attackers app.update(); // Verify commander is attacking assert!(app.world.entity(commander).contains::<Attacking>()); } }
Test Case: Commander Damage Edge Cases
Test: Commander Damage After Death and Recast
#![allow(unused)] fn main() { #[test] fn test_commander_damage_after_recast() { // Test setup let mut app = App::new(); app.add_plugins(MinimalPlugins) .add_systems(Update, (combat_damage_system, zone_change_system, cast_from_command_zone_system)); // Create player let target_player = app.world.spawn(( Player {}, Health { current: 40, maximum: 40 }, CommanderDamage::default(), )).id(); // Create commander owner let commander_owner = app.world.spawn(( Player {}, Mana { white: 10, blue: 10, black: 10, red: 10, green: 10, colorless: 10, }, CommandZone { commanders: vec![] }, )).id(); // Create commander let commander = app.world.spawn(( Creature { power: 5, toughness: 5 }, Commander { owner: commander_owner }, CreatureCard { controller: commander_owner }, InZone { zone: Zone::Battlefield }, ManaCost { white: 0, blue: 0, black: 0, red: 0, green: 0, colorless: 5, additional_cost: 0, }, )).id(); // Deal commander damage app.world.entity_mut(commander).insert(Attacking { defending: target_player }); app.world.resource_mut::<TurnManager>().current_phase = Phase::Combat(CombatStep::CombatDamage); app.update(); // Verify initial commander damage let cmd_damage1 = app.world.get::<CommanderDamage>(target_player).unwrap(); assert_eq!(cmd_damage1.get_damage(commander), 5); // Commander dies app.world.spawn( ZoneChangeRequest { entity: commander, from: Zone::Battlefield, to: Zone::CommandZone, reason: ZoneChangeReason::Death, } ); // Process zone change app.update(); // Update command zone app.world.entity_mut(commander_owner).insert( CommandZone { commanders: vec![commander] } ); // Update commander in command zone app.world.entity_mut(commander).insert( InZone { zone: Zone::CommandZone } ); // Cast commander from command zone (now with command tax) app.world.entity_mut(commander).insert( ManaCost { white: 0, blue: 0, black: 0, red: 0, green: 0, colorless: 5, additional_cost: 2, // Command tax } ); app.world.spawn( CastRequest { card: commander, controller: commander_owner, from_zone: Zone::CommandZone, } ); // Process casting app.update(); // Commander should be back on battlefield assert_eq!(app.world.get::<InZone>(commander).unwrap().zone, Zone::Battlefield); // Deal more commander damage app.world.entity_mut(commander).insert(Attacking { defending: target_player }); app.update(); // Verify commander damage accumulates even after recasting let cmd_damage2 = app.world.get::<CommanderDamage>(target_player).unwrap(); assert_eq!(cmd_damage2.get_damage(commander), 10); // 5 + 5 = 10 } }
Test: Commander Damage Through Redirect Effects
#![allow(unused)] fn main() { #[test] fn test_commander_damage_with_redirect() { // Test setup let mut app = App::new(); app.add_plugins(MinimalPlugins) .add_systems(Update, (combat_damage_system, replacement_effects_system)); // Create players let player1 = app.world.spawn(( Player {}, Health { current: 40, maximum: 40 }, CommanderDamage::default(), )).id(); let player2 = app.world.spawn(( Player {}, Health { current: 40, maximum: 40 }, CommanderDamage::default(), DamageReplacementEffect { effect_type: ReplacementType::Redirect { percentage: 100, target: TargetType::Player(player3), round_up: true, } }, )).id(); let player3 = app.world.spawn(( Player {}, Health { current: 40, maximum: 40 }, CommanderDamage::default(), )).id(); // Create commander let commander = app.world.spawn(( Creature { power: 5, toughness: 5 }, Commander { owner: player1 }, CreatureCard { controller: player1 }, Attacking { defending: player2 }, )).id(); // Process combat damage app.world.resource_mut::<TurnManager>().current_phase = Phase::Combat(CombatStep::CombatDamage); app.update(); // Verify damage was redirected let health2 = app.world.get::<Health>(player2).unwrap(); assert_eq!(health2.current, 40); // No damage taken let health3 = app.world.get::<Health>(player3).unwrap(); assert_eq!(health3.current, 35); // 40 - 5 = 35 // Verify commander damage is tracked for the final recipient let cmd_damage3 = app.world.get::<CommanderDamage>(player3).unwrap(); assert_eq!(cmd_damage3.get_damage(commander), 5); // Original target should not have commander damage let cmd_damage2 = app.world.get::<CommanderDamage>(player2).unwrap(); assert_eq!(cmd_damage2.get_damage(commander), 0); } }
Performance Considerations for Commander Combat Tests
-
Efficient Commander Damage Tracking: Optimize how commander damage is tracked and accumulated.
-
Zone Change Optimizations: Efficiently handle commander zone changes during and after combat.
-
Multiplayer Combat Performance: Ensure combat involving multiple players remains performant.
Test Coverage Checklist
- Multiple sources of commander damage tracking
- Commander damage from clones/copies
- Partner commanders in combat
- Combat damage triggers from commanders
- Attacking multiple players with a commander
- Goaded commanders
- Commander dying in combat and going to command zone
- Casting commander from command zone for combat
- Commander damage persistence after recasting
- Commander damage with redirection effects
Additional Commander-Specific Edge Cases
- Multiple copies of the same commander (from Clone effects)
- Commanders with alternate combat damage effects (infect, wither)
- Face-down commanders (via Morph or similar effects)
- Commander damage with replacement effects (damage doubling)
- "Voltron" strategies with heavily equipped/enchanted commanders