Combat Abilities

Overview

Magic: The Gathering includes numerous abilities that specifically interact with combat. In Commander, these abilities gain additional complexity due to the multiplayer nature of the format and the special rules surrounding commander damage. This document details how combat-specific abilities are implemented and tested in our game engine.

Combat Keywords

Combat keywords are implemented through a combination of component flags and systems that check for these keywords during the appropriate combat steps.

#![allow(unused)]
fn main() {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Keyword {
    // Combat-specific keywords
    FirstStrike,
    DoubleStrike,
    Deathtouch,
    Trample,
    Vigilance,
    Menace,
    Flying,
    Reach,
    Indestructible,
    Lifelink,
    // Other keywords...
}

// Component that stores a creature's abilities
#[derive(Component, Clone)]
pub struct Abilities(pub Vec<Ability>);

// Different ability types
#[derive(Debug, Clone)]
pub enum Ability {
    Keyword(Keyword),
    Triggered(Trigger, Effect),
    Activated(ActivationCost, Effect),
    Static(StaticEffect),
    // Other ability types...
}

// Implement utility functions for checking abilities
impl Creature {
    pub fn has_ability(&self, ability: Ability) -> bool {
        // Implementation details omitted for brevity
        false
    }
    
    pub fn has_keyword(&self, keyword: Keyword) -> bool {
        // Check if the creature has the specified keyword
        if let Ok(abilities) = abilities_query.get(self.entity) {
            abilities.0.iter().any(|ability| {
                if let Ability::Keyword(kw) = ability {
                    *kw == keyword
                } else {
                    false
                }
            })
        } else {
            false
        }
    }
}
}

Keyword Implementation Systems

Each combat keyword has a dedicated system that implements its effect:

First Strike and Double Strike

#![allow(unused)]
fn main() {
pub fn first_strike_damage_system(
    mut combat_system: ResMut<CombatSystem>,
    turn_manager: Res<TurnManager>,
    creature_query: Query<(Entity, &Creature)>,
    mut game_events: EventWriter<GameEvent>,
) {
    // Only run during the first strike damage step
    if turn_manager.current_phase != Phase::Combat(CombatStep::FirstStrike) {
        return;
    }
    
    // Get all attackers with first strike or double strike
    let first_strike_attackers = combat_system.attackers
        .iter()
        .filter_map(|(attacker, attack_data)| {
            if let Ok((entity, creature)) = creature_query.get(*attacker) {
                if creature.has_keyword(Keyword::FirstStrike) || 
                   creature.has_keyword(Keyword::DoubleStrike) {
                    return Some((attacker, attack_data));
                }
            }
            None
        })
        .collect::<Vec<_>>();
    
    // Get all blockers with first strike or double strike
    let first_strike_blockers = combat_system.blockers
        .iter()
        .filter_map(|(blocker, block_data)| {
            if let Ok((entity, creature)) = creature_query.get(*blocker) {
                if creature.has_keyword(Keyword::FirstStrike) || 
                   creature.has_keyword(Keyword::DoubleStrike) {
                    return Some((blocker, block_data));
                }
            }
            None
        })
        .collect::<Vec<_>>();
    
    // Process first strike combat damage
    // Implementation details omitted for brevity
    
    // Emit event for first strike damage
    game_events.send(GameEvent::FirstStrikeDamageDealt {
        attackers: first_strike_attackers.len(),
        blockers: first_strike_blockers.len(),
    });
}
}

Deathtouch

#![allow(unused)]
fn main() {
pub fn apply_deathtouch_system(
    combat_system: Res<CombatSystem>,
    creature_query: Query<(Entity, &Creature)>,
    mut commands: Commands,
) {
    // Find all creatures with deathtouch that dealt damage
    let deathtouch_creatures = combat_system.combat_history
        .iter()
        .filter_map(|event| {
            if let CombatEvent::DamageDealt { source, target, amount, .. } = event {
                if *amount > 0 {
                    if let Ok((entity, creature)) = creature_query.get(*source) {
                        if creature.has_keyword(Keyword::Deathtouch) {
                            return Some((source, target));
                        }
                    }
                }
            }
            None
        })
        .collect::<Vec<_>>();
    
    // Apply destroy effect to creatures that were damaged by deathtouch
    for (source, target) in deathtouch_creatures {
        // Only apply to creatures, not players or planeswalkers
        if creature_query.contains(*target) {
            // Mark creature as destroyed
            commands.entity(*target).insert(Destroyed {
                source: *source,
                reason: DestructionReason::Deathtouch,
            });
        }
    }
}
}

Trample

#![allow(unused)]
fn main() {
pub fn apply_trample_damage_system(
    mut combat_system: ResMut<CombatSystem>,
    creature_query: Query<(Entity, &Creature)>,
    blocker_toughness_query: Query<&Creature>,
    player_query: Query<(Entity, &mut Player)>,
    mut game_events: EventWriter<GameEvent>,
) {
    // Find all creatures with trample that are being blocked
    for (attacker, attack_data) in combat_system.attackers.iter() {
        // Only process if the attacker has trample
        if let Ok((entity, creature)) = creature_query.get(*attacker) {
            if !creature.has_keyword(Keyword::Trample) {
                continue;
            }
            
            // Check if this attacker is blocked
            let blockers: Vec<Entity> = combat_system.blockers.iter()
                .filter_map(|(blocker, block_data)| {
                    if block_data.blocked_attackers.contains(attacker) {
                        Some(*blocker)
                    } else {
                        None
                    }
                })
                .collect();
            
            // Only apply trample if the creature is blocked
            if !blockers.is_empty() {
                // Calculate total blocker toughness
                let total_blocker_toughness: u32 = blockers.iter()
                    .filter_map(|blocker| {
                        blocker_toughness_query.get(*blocker).ok().map(|creature| creature.toughness)
                    })
                    .sum();
                
                // Calculate trample damage (attacker power - total blocker toughness)
                let trample_damage = creature.power.saturating_sub(total_blocker_toughness);
                
                // Apply trample damage to defending player if there's excess damage
                if trample_damage > 0 {
                    // Only apply to players, not planeswalkers
                    for (player_entity, mut player) in player_query.iter_mut() {
                        if player_entity == attack_data.defender {
                            // Apply the damage
                            player.life_total -= trample_damage as i32;
                            
                            // Record the damage event
                            combat_system.combat_history.push_back(CombatEvent::DamageDealt {
                                source: *attacker,
                                target: player_entity,
                                amount: trample_damage,
                                is_commander_damage: attack_data.is_commander,
                            });
                            
                            // Check if player lost from trample damage
                            if player.life_total <= 0 {
                                game_events.send(GameEvent::PlayerLost {
                                    player: player_entity,
                                    reason: LossReason::ZeroLife,
                                });
                            }
                            
                            break;
                        }
                    }
                }
            }
        }
    }
}
}

Flying and Reach

#![allow(unused)]
fn main() {
pub fn validate_blocks_flying_system(
    mut combat_system: ResMut<CombatSystem>,
    creature_query: Query<(Entity, &Creature)>,
    mut block_events: EventWriter<BlockValidationEvent>,
) {
    // Check for illegal blocks involving flying creatures
    let mut illegal_blocks = Vec::new();
    
    for (blocker, block_data) in combat_system.blockers.iter() {
        // Get blocker details
        if let Ok((blocker_entity, blocker_creature)) = creature_query.get(*blocker) {
            // Check if blocker has reach or flying
            let can_block_flying = blocker_creature.has_keyword(Keyword::Flying) || 
                                  blocker_creature.has_keyword(Keyword::Reach);
            
            // Check all attackers this creature is blocking
            for attacker in &block_data.blocked_attackers {
                if let Ok((attacker_entity, attacker_creature)) = creature_query.get(*attacker) {
                    // If attacker has flying and blocker can't block flying, this is illegal
                    if attacker_creature.has_keyword(Keyword::Flying) && !can_block_flying {
                        illegal_blocks.push((*blocker, *attacker));
                    }
                }
            }
        }
    }
    
    // Remove illegal blocks
    for (blocker, attacker) in illegal_blocks {
        // Remove the block relationship
        if let Some(mut block_data) = combat_system.blockers.get_mut(&blocker) {
            block_data.blocked_attackers.retain(|a| *a != attacker);
            
            // If no more attackers, remove blocker entry
            if block_data.blocked_attackers.is_empty() {
                combat_system.blockers.remove(&blocker);
            }
        }
        
        // Emit event for illegal block
        block_events.send(BlockValidationEvent::IllegalBlock {
            blocker,
            attacker,
            reason: "Can't block flying".to_string(),
        });
    }
}
}

Menace

#![allow(unused)]
fn main() {
pub fn validate_blocks_menace_system(
    mut combat_system: ResMut<CombatSystem>,
    creature_query: Query<(Entity, &Creature)>,
    mut block_events: EventWriter<BlockValidationEvent>,
) {
    // Find attackers with menace that are being blocked by only one creature
    let menace_violations = combat_system.attackers
        .iter()
        .filter_map(|(attacker, attack_data)| {
            // Check if attacker has menace
            if let Ok((entity, creature)) = creature_query.get(*attacker) {
                if creature.has_keyword(Keyword::Menace) {
                    // Count blockers for this attacker
                    let blocker_count = combat_system.blockers
                        .values()
                        .filter(|block_data| block_data.blocked_attackers.contains(attacker))
                        .count();
                    
                    // Menace requires at least 2 blockers
                    if blocker_count == 1 {
                        // Find the single blocker
                        let blocker = combat_system.blockers
                            .iter()
                            .find_map(|(blocker, block_data)| {
                                if block_data.blocked_attackers.contains(attacker) {
                                    Some(*blocker)
                                } else {
                                    None
                                }
                            })
                            .unwrap();
                        
                        return Some((*attacker, blocker));
                    }
                }
            }
            None
        })
        .collect::<Vec<_>>();
    
    // Remove blocks that violate menace
    for (attacker, blocker) in menace_violations {
        // Remove the block relationship
        if let Some(mut block_data) = combat_system.blockers.get_mut(&blocker) {
            block_data.blocked_attackers.retain(|a| *a != attacker);
            
            // If no more attackers, remove blocker entry
            if block_data.blocked_attackers.is_empty() {
                combat_system.blockers.remove(&blocker);
            }
        }
        
        // Emit event for illegal block
        block_events.send(BlockValidationEvent::IllegalBlock {
            blocker,
            attacker,
            reason: "Menace requires at least two blockers".to_string(),
        });
    }
}
}

Vigilance

#![allow(unused)]
fn main() {
pub fn apply_vigilance_system(
    combat_system: Res<CombatSystem>,
    creature_query: Query<(Entity, &Creature)>,
    mut commands: Commands,
) {
    // Find attackers with vigilance and ensure they don't get tapped
    for (attacker, _) in combat_system.attackers.iter() {
        if let Ok((entity, creature)) = creature_query.get(*attacker) {
            if creature.has_keyword(Keyword::Vigilance) {
                // Remove the Tapped component if it exists
                commands.entity(*attacker).remove::<Tapped>();
            }
        }
    }
}
}

Triggered Combat Abilities

Triggered abilities are a major part of combat in Magic. These are implemented using a trigger system:

#![allow(unused)]
fn main() {
#[derive(Debug, Clone)]
pub enum Trigger {
    // Combat-related triggers
    WhenAttacks { conditions: Vec<TriggerCondition> },
    WhenBlocks { conditions: Vec<TriggerCondition> },
    WhenBlocked { conditions: Vec<TriggerCondition> },
    WhenDealsCombat { conditions: Vec<TriggerCondition> },
    WheneverCreatureAttacks { conditions: Vec<TriggerCondition> },
    WheneverCreatureBlocks { conditions: Vec<TriggerCondition> },
    BeginningOfCombat { controller_condition: ControllerCondition },
    EndOfCombat { controller_condition: ControllerCondition },
    // Other triggers...
}

pub fn combat_trigger_system(
    combat_system: Res<CombatSystem>,
    turn_manager: Res<TurnManager>,
    mut stack: ResMut<Stack>,
    entity_query: Query<(Entity, &Card, &Abilities)>,
) {
    // Only run during the appropriate combat step
    if !matches!(turn_manager.current_phase, 
        Phase::Combat(CombatStep::DeclareAttackers) | 
        Phase::Combat(CombatStep::DeclareBlockers) | 
        Phase::Combat(CombatStep::CombatDamage)) {
        return;
    }
    
    // Collect triggered abilities based on the current combat step
    let mut triggered_abilities = Vec::new();
    
    // Check all entities with abilities
    for (entity, card, abilities) in entity_query.iter() {
        for ability in &abilities.0 {
            if let Ability::Triggered(trigger, effect) = ability {
                match trigger {
                    // Check if we're in declare attackers and this is a "when attacks" trigger
                    Trigger::WhenAttacks { conditions } 
                        if turn_manager.current_phase == Phase::Combat(CombatStep::DeclareAttackers) => {
                        // Check if this entity is attacking
                        if let Some(attack_data) = combat_system.attackers.get(&entity) {
                            // Check if conditions are met
                            if all_conditions_met(conditions, entity, attack_data.defender) {
                                triggered_abilities.push((entity, effect.clone(), get_controller(entity)));
                            }
                        }
                    },
                    
                    // Similar checks for other triggers...
                    _ => {}
                }
            }
        }
    }
    
    // Add triggered abilities to the stack in APNAP order
    let active_player = turn_manager.get_active_player();
    let ordered_triggers = order_triggers_by_apnap(triggered_abilities, active_player);
    
    for (source, effect, controller) in ordered_triggers {
        stack.push(StackItem::Ability {
            source,
            effect,
            controller,
        });
    }
}
}

Special Combat Abilities

Ninjutsu

#![allow(unused)]
fn main() {
pub fn handle_ninjutsu_system(
    mut combat_system: ResMut<CombatSystem>,
    turn_manager: Res<TurnManager>,
    mut commands: Commands,
    card_query: Query<(Entity, &Card, &Abilities, Option<&Commander>)>,
    mut activation_events: EventReader<NinjutsuActivationEvent>,
    mut creature_query: Query<(Entity, &mut Creature)>,
) {
    // Only active during declare attackers step after attacks are declared
    if turn_manager.current_phase != Phase::Combat(CombatStep::DeclareAttackers) 
       || combat_system.attackers.is_empty() {
        return;
    }
    
    // Process ninjutsu activations
    for event in activation_events.iter() {
        if let Ok((entity, card, abilities, commander)) = card_query.get(event.ninja) {
            // Verify card has ninjutsu or commander ninjutsu
            let has_ninjutsu = abilities.0.iter().any(|ability| {
                matches!(ability, Ability::Activated(ActivationCost::Ninjutsu(_), _))
            });
            
            let has_commander_ninjutsu = commander.is_some() && abilities.0.iter().any(|ability| {
                matches!(ability, Ability::Activated(ActivationCost::CommanderNinjutsu(_), _))
            });
            
            if has_ninjutsu || has_commander_ninjutsu {
                // Verify unblocked attacker
                if !combat_system.attackers.contains_key(&event.unblocked_attacker) {
                    continue;
                }
                
                // Verify attacker is unblocked
                let is_blocked = combat_system.blockers.values().any(|block_data| {
                    block_data.blocked_attackers.contains(&event.unblocked_attacker)
                });
                
                if is_blocked {
                    continue;
                }
                
                // Get defender
                let defender = combat_system.attackers[&event.unblocked_attacker].defender;
                
                // Return unblocked attacker to hand
                // Implementation details omitted for brevity
                
                // Put ninja onto battlefield tapped and attacking
                // Implementation details omitted for brevity
                
                // Update combat system
                combat_system.attackers.remove(&event.unblocked_attacker);
                combat_system.attackers.insert(event.ninja, AttackData {
                    attacker: event.ninja,
                    defender,
                    is_commander: commander.is_some(),
                    requirements: Vec::new(),
                    restrictions: Vec::new(),
                });
            }
        }
    }
}
}

Protection

#![allow(unused)]
fn main() {
pub fn apply_protection_system(
    mut combat_system: ResMut<CombatSystem>,
    creature_query: Query<(Entity, &Creature, &ActiveEffects)>,
    mut combat_events: EventWriter<CombatEvent>,
) {
    // Check for protection effects
    let mut invalid_blocks = Vec::new();
    let mut prevented_damage = Vec::new();
    
    // Check blocks against protection
    for (blocker, block_data) in combat_system.blockers.iter() {
        if let Ok((blocker_entity, _, effects)) = creature_query.get(*blocker) {
            for attacker in &block_data.blocked_attackers {
                if has_protection_from(*blocker, *attacker, &creature_query) {
                    invalid_blocks.push((*blocker, *attacker));
                }
            }
        }
    }
    
    // Check damage against protection
    let damage_events = combat_system.combat_history
        .iter()
        .filter_map(|event| {
            if let CombatEvent::DamageDealt { source, target, amount, is_commander_damage } = event {
                Some((*source, *target, *amount, *is_commander_damage))
            } else {
                None
            }
        })
        .collect::<Vec<_>>();
    
    for (source, target, amount, is_commander_damage) in damage_events {
        if has_protection_from(target, source, &creature_query) {
            prevented_damage.push((source, target, amount, is_commander_damage));
        }
    }
    
    // Remove invalid blocks
    for (blocker, attacker) in invalid_blocks {
        if let Some(mut block_data) = combat_system.blockers.get_mut(&blocker) {
            block_data.blocked_attackers.retain(|a| *a != attacker);
        }
    }
    
    // Record damage prevention events
    for (source, target, amount, is_commander_damage) in prevented_damage {
        combat_events.send(CombatEvent::DamagePrevented {
            source,
            target,
            amount,
            reason: PreventionReason::Protection,
        });
    }
}

// Helper function to check if an entity has protection from another
fn has_protection_from(
    protected: Entity,
    source: Entity,
    creature_query: &Query<(Entity, &Creature, &ActiveEffects)>,
) -> bool {
    if let Ok((_, _, effects)) = creature_query.get(protected) {
        for effect in &effects.0 {
            match effect {
                Effect::ProtectionFromColor(color) => {
                    // Check if source has the protected color
                    if let Ok((_, source_creature, _)) = creature_query.get(source) {
                        if source_creature.colors.contains(color) {
                            return true;
                        }
                    }
                },
                Effect::ProtectionFromCreatureType(creature_type) => {
                    // Check if source has the protected creature type
                    if let Ok((_, source_creature, _)) = creature_query.get(source) {
                        if source_creature.types.contains(creature_type) {
                            return true;
                        }
                    }
                },
                Effect::ProtectionFromPlayer(player) => {
                    // Check if source is controlled by the protected player
                    // Implementation details omitted for brevity
                },
                Effect::ProtectionFromEverything => {
                    return true;
                },
                _ => {}
            }
        }
    }
    
    false
}
}

Edge Cases

Multiple Combats

Some cards create additional combat phases. These need special handling:

#![allow(unused)]
fn main() {
pub fn handle_additional_combat_phases(
    mut turn_manager: ResMut<TurnManager>,
    mut combat_system: ResMut<CombatSystem>,
    mut creature_query: Query<(Entity, &mut Creature)>,
    mut phase_events: EventReader<PhaseEvent>,
) {
    // Check for additional combat phase events
    for event in phase_events.iter() {
        if let PhaseEvent::AdditionalPhase { phase, source } = event {
            if matches!(phase, Phase::Combat(_)) {
                // Reset combat state for the new phase
                combat_system.reset();
                combat_system.is_additional_combat_phase = true;
                
                // Reset attacked/blocked flags on creatures
                for (entity, mut creature) in creature_query.iter_mut() {
                    creature.attacking = None;
                    creature.blocking.clear();
                }
                
                // Update turn manager
                turn_manager.additional_phases.push_back((*phase, *source));
            }
        }
    }
}
}

Changing Abilities During Combat

Creatures can gain or lose abilities during combat:

#![allow(unused)]
fn main() {
pub fn update_combat_abilities_system(
    mut creature_query: Query<(Entity, &mut Creature, &ActiveEffects)>,
    mut combat_system: ResMut<CombatSystem>,
    mut ability_events: EventReader<AbilityChangeEvent>,
) {
    // Process ability change events
    for event in ability_events.iter() {
        if let AbilityChangeEvent::GainedKeyword { entity, keyword } = event {
            // Update entity if it's involved in combat
            if combat_system.attackers.contains_key(entity) || 
               combat_system.blockers.contains_key(entity) {
                // Special handling for combat-relevant keywords
                match keyword {
                    Keyword::FirstStrike | Keyword::DoubleStrike => {
                        // May need to recalculate damage for first strike step
                        if combat_system.active_combat_step == Some(CombatStep::DeclareBlockers) {
                            // Flag that first strike needs to be checked
                            combat_system.first_strike_recalculation_needed = true;
                        }
                    },
                    Keyword::Flying => {
                        // May need to recalculate blocks for flying
                        if let Some(block_data) = combat_system.blockers.get(entity) {
                            // Check if this creature is now blocking a flyer illegally
                            // Implementation details omitted for brevity
                        }
                    },
                    Keyword::Vigilance => {
                        // If creature already attacked, untap it
                        if combat_system.attackers.contains_key(entity) {
                            // Implementation details omitted for brevity
                        }
                    },
                    // Handle other keywords...
                    _ => {}
                }
            }
        }
        else if let AbilityChangeEvent::LostKeyword { entity, keyword } = event {
            // Similar handling for losing keywords
            // Implementation details omitted for brevity
        }
    }
}
}

Testing Strategy

Unit Tests for Combat Abilities

#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_deathtouch() {
        let mut app = App::new();
        app.add_systems(Update, apply_deathtouch_system);
        
        // Setup test entities with deathtouch
        let attacker = app.world.spawn((
            Card::default(),
            Creature { power: 1, toughness: 1, ..Default::default() },
            Abilities(vec![Ability::Keyword(Keyword::Deathtouch)]),
        )).id();
        
        let blocker = app.world.spawn((
            Card::default(),
            Creature { power: 5, toughness: 5, ..Default::default() },
        )).id();
        
        // Create combat history with damage event
        let mut combat_system = CombatSystem::default();
        combat_system.combat_history.push_back(CombatEvent::DamageDealt {
            source: attacker,
            target: blocker,
            amount: 1,
            is_commander_damage: false,
        });
        app.insert_resource(combat_system);
        
        // Run the system
        app.update();
        
        // Verify blocker was destroyed by deathtouch
        assert!(app.world.entity(blocker).contains::<Destroyed>());
    }
    
    #[test]
    fn test_trample() {
        let mut app = App::new();
        app.add_systems(Update, apply_trample_damage_system);
        
        // Setup test entities
        let player = app.world.spawn((
            Player { life_total: 40, commander_damage: HashMap::new(), ..Default::default() },
        )).id();
        
        let attacker = app.world.spawn((
            Card::default(),
            Creature { power: 5, toughness: 5, ..Default::default() },
            Abilities(vec![Ability::Keyword(Keyword::Trample)]),
        )).id();
        
        let blocker = app.world.spawn((
            Card::default(),
            Creature { power: 2, toughness: 2, ..Default::default() },
        )).id();
        
        // Create combat system with attacker and blocker
        let mut combat_system = CombatSystem::default();
        combat_system.attackers.insert(attacker, AttackData {
            attacker,
            defender: player,
            is_commander: false,
            requirements: Vec::new(),
            restrictions: Vec::new(),
        });
        
        combat_system.blockers.insert(blocker, BlockData {
            blocker,
            blocked_attackers: vec![attacker],
            requirements: Vec::new(),
            restrictions: Vec::new(),
        });
        
        app.insert_resource(combat_system);
        
        // Run the system
        app.update();
        
        // Verify trample damage was dealt to player (5 power - 2 toughness = 3 damage)
        let player_component = app.world.entity(player).get::<Player>().unwrap();
        assert_eq!(player_component.life_total, 40 - 3);
        
        // Verify combat history contains damage event
        let combat_system = app.world.resource::<CombatSystem>();
        assert!(combat_system.combat_history.iter().any(|event| {
            matches!(event, CombatEvent::DamageDealt { source, target, amount, .. } 
                if *source == attacker && *target == player && *amount == 3)
        }));
    }
    
    #[test]
    fn test_flying_and_reach() {
        let mut app = App::new();
        app.add_systems(Update, validate_blocks_flying_system);
        
        // Setup test entities
        let flying_creature = app.world.spawn((
            Card::default(),
            Creature { power: 3, toughness: 3, ..Default::default() },
            Abilities(vec![Ability::Keyword(Keyword::Flying)]),
        )).id();
        
        let normal_creature = app.world.spawn((
            Card::default(),
            Creature { power: 2, toughness: 2, ..Default::default() },
        )).id();
        
        let reach_creature = app.world.spawn((
            Card::default(),
            Creature { power: 1, toughness: 4, ..Default::default() },
            Abilities(vec![Ability::Keyword(Keyword::Reach)]),
        )).id();
        
        // Create combat system with illegal block
        let mut combat_system = CombatSystem::default();
        combat_system.attackers.insert(flying_creature, AttackData {
            attacker: flying_creature,
            defender: Entity::from_raw(999), // Dummy defender
            is_commander: false,
            requirements: Vec::new(),
            restrictions: Vec::new(),
        });
        
        combat_system.blockers.insert(normal_creature, BlockData {
            blocker: normal_creature,
            blocked_attackers: vec![flying_creature],
            requirements: Vec::new(),
            restrictions: Vec::new(),
        });
        
        combat_system.blockers.insert(reach_creature, BlockData {
            blocker: reach_creature,
            blocked_attackers: vec![flying_creature],
            requirements: Vec::new(),
            restrictions: Vec::new(),
        });
        
        app.insert_resource(combat_system);
        
        // Set up event listener
        app.add_event::<BlockValidationEvent>();
        
        // Run the system
        app.update();
        
        // Verify normal creature can't block the flyer
        let combat_system = app.world.resource::<CombatSystem>();
        assert!(!combat_system.blockers.get(&normal_creature)
            .map_or(false, |data| data.blocked_attackers.contains(&flying_creature)));
        
        // Verify reach creature can still block the flyer
        assert!(combat_system.blockers.get(&reach_creature)
            .map_or(false, |data| data.blocked_attackers.contains(&flying_creature)));
    }
    
    // Additional tests...
}
}

Integration Tests for Combat Abilities

#![allow(unused)]
fn main() {
#[cfg(test)]
mod integration_tests {
    use super::*;
    
    #[test]
    fn test_keyword_interactions() {
        let mut builder = CombatScenarioBuilder::new();
        
        // Setup a combat scenario with multiple keyword interactions
        let opponent = builder.add_player();
        
        // Flying attacker with lifelink and double strike
        let attacker = builder.add_attacker(3, 3, builder.active_player, false);
        builder.add_effect(attacker, Effect::Keyword(Keyword::Flying));
        builder.add_effect(attacker, Effect::Keyword(Keyword::Lifelink));
        builder.add_effect(attacker, Effect::Keyword(Keyword::DoubleStrike));
        
        // Normal blocker can't block flying
        let blocker1 = builder.add_blocker(2, 2, opponent);
        
        // Reach blocker can block flying
        let blocker2 = builder.add_blocker(1, 1, opponent);
        builder.add_effect(blocker2, Effect::Keyword(Keyword::Reach));
        
        // Set up attacks and blocks
        builder.declare_attacks(vec![(attacker, opponent)]);
        builder.declare_blocks(vec![(blocker2, vec![attacker])]);
        
        // Execute combat
        let result = builder.execute();
        
        // Verify: 
        // 1. First strike damage killed blocker2
        // 2. Double strike allowed second hit to player
        // 3. Lifelink gained life
        
        // Check blocker died
        let blocker2_status = result.creature_status.get(&blocker2).unwrap();
        assert!(blocker2_status.destroyed);
        
        // Check player took damage from second hit (double strike)
        assert_eq!(result.player_life[&opponent], 40 - 3);
        
        // Check controller gained life from lifelink
        let active_player_life = result.player_life[&builder.active_player];
        assert_eq!(active_player_life, 40 + 6); // 3 damage twice from double strike
    }
    
    #[test]
    fn test_triggered_abilities() {
        // Test implementation for combat-triggered abilities
        // Implementation details omitted for brevity
    }
    
    #[test]
    fn test_ninjutsu() {
        // Test implementation for ninjutsu
        // Implementation details omitted for brevity
    }
    
    // Additional integration tests...
}
}

Combos and Interactions

The combat system needs to handle complex ability interactions:

#![allow(unused)]
fn main() {
pub fn handle_ability_interactions(
    combat_system: Res<CombatSystem>,
    entity_query: Query<(Entity, &Creature, &ActiveEffects)>,
    mut destroy_events: EventWriter<DestroyEvent>,
    mut damage_events: EventWriter<DamageEvent>,
) {
    // Track relevant ability combinations
    let mut indestructible_entities = HashSet::new();
    let mut damage_redirection_map = HashMap::new();
    let mut regeneration_shields = HashMap::new();
    
    // Collect all combat-relevant abilities
    for (entity, _, effects) in entity_query.iter() {
        for effect in &effects.0 {
            match effect {
                Effect::Indestructible => {
                    indestructible_entities.insert(entity);
                },
                Effect::RedirectDamage { target } => {
                    damage_redirection_map.insert(entity, *target);
                },
                Effect::RegenerationShield => {
                    regeneration_shields.entry(entity).or_insert(0) += 1;
                },
                // Handle other effects
                _ => {}
            }
        }
    }
    
    // Process combat damage events considering special abilities
    for event in combat_system.combat_history.iter() {
        if let CombatEvent::DamageDealt { source, target, amount, is_commander_damage } = event {
            // Check for damage redirection
            let actual_target = damage_redirection_map.get(target).copied().unwrap_or(*target);
            
            // Check if damage would be lethal
            if let Ok((_, creature, _)) = entity_query.get(actual_target) {
                if creature.toughness <= creature.damage + amount {
                    // Check for indestructible
                    if indestructible_entities.contains(&actual_target) {
                        // Creature survives despite lethal damage
                        continue;
                    }
                    
                    // Check for regeneration shield
                    if let Some(shields) = regeneration_shields.get_mut(&actual_target) {
                        if *shields > 0 {
                            // Use a regeneration shield
                            *shields -= 1;
                            
                            // Emit regeneration event
                            // Implementation details omitted
                            
                            continue;
                        }
                    }
                    
                    // No protection, creature is destroyed
                    destroy_events.send(DestroyEvent {
                        entity: actual_target,
                        source: *source,
                        reason: DestructionReason::LethalDamage,
                    });
                }
            }
        }
    }
}
}

Conclusion

Combat abilities add significant complexity to the Commander format, requiring careful implementation and testing. The systems described here work together to handle the various keywords, triggers, and special cases that can arise during combat. By properly implementing and testing these abilities, we ensure that the combat system correctly follows the rules of Magic: The Gathering while providing the strategic depth that makes Commander such a popular format.