sigil_parser/plurality/
combat.rs

1//! # Combat System for DAEMONIORUM
2//!
3//! Combat mechanics that integrate with plurality, alter switching,
4//! and reality perception systems.
5
6use std::collections::HashMap;
7
8use super::runtime::{
9    Alter, AlterPresenceState, AnimaState, FrontingState, PluralSystem, RealityLayer,
10    SwitchResult, Trigger, TriggerCategory, TriggerResult,
11};
12
13// ============================================================================
14// COMBAT STATE
15// ============================================================================
16
17/// The current combat encounter state
18#[derive(Debug, Clone)]
19pub struct CombatState {
20    /// Active combatants
21    pub combatants: Vec<Combatant>,
22    /// Current turn order
23    pub turn_order: Vec<usize>,
24    /// Current active combatant index
25    pub current_turn: usize,
26    /// Combat round number
27    pub round: u32,
28    /// Environmental factors
29    pub environment: CombatEnvironment,
30    /// Active combat effects
31    pub effects: Vec<CombatEffect>,
32    /// Combat phase
33    pub phase: CombatPhase,
34    /// Is combat over?
35    pub is_over: bool,
36    /// Victory/defeat result (if over)
37    pub result: Option<CombatResult>,
38}
39
40impl CombatState {
41    /// Create a new combat encounter
42    pub fn new(player_system: &PluralSystem, enemies: Vec<Enemy>) -> Self {
43        let mut combatants = vec![Combatant::Player(PlayerCombatant::from_system(player_system))];
44        combatants.extend(enemies.into_iter().map(Combatant::Enemy));
45
46        let mut state = Self {
47            combatants,
48            turn_order: Vec::new(),
49            current_turn: 0,
50            round: 1,
51            environment: CombatEnvironment::default(),
52            effects: Vec::new(),
53            phase: CombatPhase::PreCombat,
54            is_over: false,
55            result: None,
56        };
57
58        state.calculate_turn_order();
59        state
60    }
61
62    /// Calculate turn order based on initiative
63    fn calculate_turn_order(&mut self) {
64        let mut order: Vec<(usize, f32)> = self
65            .combatants
66            .iter()
67            .enumerate()
68            .map(|(i, c)| (i, c.initiative()))
69            .collect();
70
71        // Sort by initiative (highest first)
72        order.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
73
74        self.turn_order = order.into_iter().map(|(i, _)| i).collect();
75    }
76
77    /// Get the current active combatant
78    pub fn current_combatant(&self) -> Option<&Combatant> {
79        self.turn_order
80            .get(self.current_turn)
81            .and_then(|&idx| self.combatants.get(idx))
82    }
83
84    /// Get the current active combatant mutably
85    pub fn current_combatant_mut(&mut self) -> Option<&mut Combatant> {
86        let idx = *self.turn_order.get(self.current_turn)?;
87        self.combatants.get_mut(idx)
88    }
89
90    /// Advance to the next turn
91    pub fn next_turn(&mut self) {
92        self.current_turn += 1;
93        if self.current_turn >= self.turn_order.len() {
94            self.current_turn = 0;
95            self.round += 1;
96            self.phase = CombatPhase::Combat;
97        }
98    }
99
100    /// Check if combat should end
101    pub fn check_combat_end(&mut self) {
102        let player_alive = self.combatants.iter().any(|c| {
103            matches!(c, Combatant::Player(p) if p.health > 0.0)
104        });
105
106        let enemies_alive = self.combatants.iter().any(|c| {
107            matches!(c, Combatant::Enemy(e) if e.health > 0.0)
108        });
109
110        if !player_alive {
111            self.is_over = true;
112            self.result = Some(CombatResult::Defeat);
113        } else if !enemies_alive {
114            self.is_over = true;
115            self.result = Some(CombatResult::Victory);
116        }
117    }
118}
119
120/// Combat phases
121#[derive(Debug, Clone, PartialEq)]
122pub enum CombatPhase {
123    /// Before combat starts (preparation)
124    PreCombat,
125    /// Active combat
126    Combat,
127    /// Special event during combat
128    Event,
129    /// Combat is ending
130    PostCombat,
131}
132
133/// Combat result
134#[derive(Debug, Clone, PartialEq)]
135pub enum CombatResult {
136    Victory,
137    Defeat,
138    Flee,
139    Negotiated,
140}
141
142// ============================================================================
143// COMBATANTS
144// ============================================================================
145
146/// A participant in combat
147#[derive(Debug, Clone)]
148pub enum Combatant {
149    Player(PlayerCombatant),
150    Enemy(Enemy),
151}
152
153impl Combatant {
154    /// Get the combatant's initiative value
155    pub fn initiative(&self) -> f32 {
156        match self {
157            Combatant::Player(p) => p.initiative(),
158            Combatant::Enemy(e) => e.initiative,
159        }
160    }
161
162    /// Get the combatant's name
163    pub fn name(&self) -> &str {
164        match self {
165            Combatant::Player(p) => &p.fronting_alter,
166            Combatant::Enemy(e) => &e.name,
167        }
168    }
169}
170
171/// Player combatant (the plural system)
172#[derive(Debug, Clone)]
173pub struct PlayerCombatant {
174    /// Currently fronting alter name
175    pub fronting_alter: String,
176    /// Current health (0.0 to max_health)
177    pub health: f32,
178    /// Maximum health
179    pub max_health: f32,
180    /// Current psyche (mental/emotional resource)
181    pub psyche: f32,
182    /// Maximum psyche
183    pub max_psyche: f32,
184    /// Current anima state
185    pub anima: AnimaState,
186    /// Current reality perception
187    pub reality: RealityLayer,
188    /// Available abilities for current fronter
189    pub abilities: Vec<CombatAbility>,
190    /// Status effects
191    pub status_effects: Vec<StatusEffect>,
192    /// Alter switching cooldown (turns remaining)
193    pub switch_cooldown: u32,
194    /// Available alters that can switch in
195    pub available_alters: Vec<String>,
196    /// Combat-specific triggers active
197    pub active_triggers: Vec<String>,
198}
199
200impl PlayerCombatant {
201    /// Create from a plural system
202    pub fn from_system(system: &PluralSystem) -> Self {
203        let fronting = match &system.fronting {
204            FrontingState::Single(id) => id.clone(),
205            FrontingState::Blended(ids) => ids.first().cloned().unwrap_or_default(),
206            _ => "Unknown".to_string(),
207        };
208
209        let fronter = system.alters.get(&fronting);
210        let abilities = fronter
211            .map(|a| generate_combat_abilities(a))
212            .unwrap_or_default();
213
214        let available: Vec<String> = system
215            .alters
216            .values()
217            .filter(|a| {
218                !matches!(a.state, AlterPresenceState::Dormant | AlterPresenceState::Dissociating)
219            })
220            .map(|a| a.id.clone())
221            .collect();
222
223        Self {
224            fronting_alter: fronting,
225            health: 100.0,
226            max_health: 100.0,
227            psyche: 50.0,
228            max_psyche: 50.0,
229            anima: system.anima.clone(),
230            reality: system.reality_layer.clone(),
231            abilities,
232            status_effects: Vec::new(),
233            switch_cooldown: 0,
234            available_alters: available,
235            active_triggers: Vec::new(),
236        }
237    }
238
239    /// Calculate initiative based on current state
240    pub fn initiative(&self) -> f32 {
241        let base = 10.0;
242        let arousal_bonus = self.anima.arousal * 5.0; // Higher arousal = faster
243        let dominance_bonus = self.anima.dominance * 2.0;
244        base + arousal_bonus + dominance_bonus
245    }
246
247    /// Execute an ability
248    pub fn execute_ability(&mut self, ability_index: usize, target: &mut Combatant) -> AbilityResult {
249        if ability_index >= self.abilities.len() {
250            return AbilityResult::Failed("Invalid ability".to_string());
251        }
252
253        let ability = &self.abilities[ability_index].clone();
254
255        // Check psyche cost
256        if self.psyche < ability.psyche_cost {
257            return AbilityResult::Failed("Not enough psyche".to_string());
258        }
259
260        // Check reality requirements
261        if let Some(ref required) = ability.reality_requirement {
262            if &self.reality != required {
263                return AbilityResult::Failed("Wrong reality layer".to_string());
264            }
265        }
266
267        // Pay cost
268        self.psyche -= ability.psyche_cost;
269
270        // Calculate damage/effect
271        let mut damage = ability.base_damage;
272
273        // Apply anima modifiers
274        match ability.damage_type {
275            DamageType::Physical => {
276                damage *= 1.0 + self.anima.dominance * 0.3;
277            }
278            DamageType::Psychic => {
279                damage *= 1.0 + self.anima.arousal * 0.3;
280            }
281            DamageType::Emotional => {
282                damage *= 1.0 + (1.0 - self.anima.stability) * 0.5;
283            }
284            DamageType::Reality => {
285                if matches!(self.reality, RealityLayer::Fractured | RealityLayer::Shattered) {
286                    damage *= 1.5;
287                }
288            }
289        }
290
291        // Apply to target
292        match target {
293            Combatant::Enemy(e) => {
294                e.health = (e.health - damage).max(0.0);
295            }
296            Combatant::Player(p) => {
297                p.health = (p.health - damage).max(0.0);
298            }
299        }
300
301        // Apply status effects
302        for effect in &ability.applies_effects {
303            match target {
304                Combatant::Enemy(e) => e.status_effects.push(effect.clone()),
305                Combatant::Player(p) => p.status_effects.push(effect.clone()),
306            }
307        }
308
309        AbilityResult::Success { damage, effects: ability.applies_effects.clone() }
310    }
311
312    /// Attempt to switch alters mid-combat
313    pub fn combat_switch(&mut self, target_alter: &str, system: &mut PluralSystem) -> CombatSwitchResult {
314        if self.switch_cooldown > 0 {
315            return CombatSwitchResult::OnCooldown(self.switch_cooldown);
316        }
317
318        if !self.available_alters.contains(&target_alter.to_string()) {
319            return CombatSwitchResult::NotAvailable;
320        }
321
322        // Attempt the switch
323        let urgency = 0.5 + self.anima.arousal * 0.3; // Higher arousal = easier combat switches
324        let result = system.request_switch(target_alter, urgency, false);
325
326        match result {
327            SwitchResult::Success => {
328                self.fronting_alter = target_alter.to_string();
329                self.switch_cooldown = 2; // 2 turn cooldown
330
331                // Update abilities for new fronter
332                if let Some(alter) = system.alters.get(target_alter) {
333                    self.abilities = generate_combat_abilities(alter);
334                    self.anima = alter.anima.clone();
335                }
336
337                // Reality might shift based on alter preference
338                if let Some(alter) = system.alters.get(target_alter) {
339                    self.reality = alter.preferred_reality.clone();
340                }
341
342                CombatSwitchResult::Success
343            }
344            SwitchResult::Resisted { resistance } => {
345                CombatSwitchResult::Resisted(resistance)
346            }
347            SwitchResult::Failed(reason) => {
348                CombatSwitchResult::Failed(format!("{:?}", reason))
349            }
350            SwitchResult::InProgress { eta } => {
351                CombatSwitchResult::InProgress(eta)
352            }
353        }
354    }
355
356    /// Process a combat trigger
357    pub fn process_combat_trigger(&mut self, trigger: Trigger, system: &mut PluralSystem) -> CombatTriggerResult {
358        let trigger_result = system.process_trigger(trigger.clone());
359
360        match trigger_result {
361            TriggerResult::ForcedSwitch(alter_id) => {
362                // Forced switch ignores cooldown
363                self.switch_cooldown = 0;
364                let switch_result = self.combat_switch(&alter_id, system);
365                CombatTriggerResult::ForcedSwitch(alter_id, switch_result)
366            }
367            TriggerResult::Activation(alters) => {
368                // Activation makes alters more available
369                for alter_id in &alters {
370                    if !self.available_alters.contains(alter_id) {
371                        self.available_alters.push(alter_id.clone());
372                    }
373                }
374                CombatTriggerResult::AltersActivated(alters)
375            }
376            TriggerResult::Dissociation => {
377                self.anima.apply_trauma_response(0.5);
378                CombatTriggerResult::Dissociation
379            }
380            TriggerResult::NoResponse => {
381                CombatTriggerResult::NoEffect
382            }
383        }
384    }
385}
386
387/// Result of a combat switch attempt
388#[derive(Debug, Clone)]
389pub enum CombatSwitchResult {
390    Success,
391    OnCooldown(u32),
392    NotAvailable,
393    Resisted(f32),
394    Failed(String),
395    InProgress(u64),
396}
397
398/// Result of a combat trigger
399#[derive(Debug, Clone)]
400pub enum CombatTriggerResult {
401    NoEffect,
402    ForcedSwitch(String, CombatSwitchResult),
403    AltersActivated(Vec<String>),
404    Dissociation,
405}
406
407// ============================================================================
408// ENEMIES
409// ============================================================================
410
411/// An enemy combatant
412#[derive(Debug, Clone)]
413pub struct Enemy {
414    /// Enemy ID
415    pub id: String,
416    /// Display name
417    pub name: String,
418    /// Enemy type
419    pub enemy_type: EnemyType,
420    /// Current health
421    pub health: f32,
422    /// Maximum health
423    pub max_health: f32,
424    /// Initiative value
425    pub initiative: f32,
426    /// Available abilities
427    pub abilities: Vec<CombatAbility>,
428    /// Status effects
429    pub status_effects: Vec<StatusEffect>,
430    /// Trigger behaviors (what triggers this enemy causes)
431    pub trigger_behaviors: Vec<TriggerBehavior>,
432    /// Reality perception (affects which layer player sees them in)
433    pub reality_visibility: Vec<RealityLayer>,
434}
435
436/// Types of enemies
437#[derive(Debug, Clone, PartialEq)]
438pub enum EnemyType {
439    /// Normal human threat
440    Human,
441    /// Symbolic/metaphorical threat (trauma manifestation)
442    Manifestation,
443    /// Environmental hazard
444    Hazard,
445    /// Internal threat (persecutor alter, intrusive thought)
446    Internal,
447    /// Supernatural entity
448    Entity,
449}
450
451/// Behavior that creates triggers
452#[derive(Debug, Clone)]
453pub struct TriggerBehavior {
454    /// Trigger ID this enemy can cause
455    pub trigger_id: String,
456    /// Condition for triggering
457    pub condition: TriggerCondition,
458    /// Intensity when triggered
459    pub intensity: f32,
460}
461
462/// Conditions for enemy triggers
463#[derive(Debug, Clone)]
464pub enum TriggerCondition {
465    /// Always trigger at start of enemy turn
466    OnTurn,
467    /// Trigger when enemy attacks
468    OnAttack,
469    /// Trigger when enemy health drops below threshold
470    HealthBelow(f32),
471    /// Trigger when player is at low health
472    PlayerLowHealth,
473    /// Trigger when specific ability is used
474    OnAbility(String),
475    /// Random chance per turn
476    Random(f32),
477}
478
479// ============================================================================
480// ABILITIES
481// ============================================================================
482
483/// A combat ability
484#[derive(Debug, Clone)]
485pub struct CombatAbility {
486    /// Ability ID
487    pub id: String,
488    /// Display name
489    pub name: String,
490    /// Description
491    pub description: String,
492    /// Base damage/healing
493    pub base_damage: f32,
494    /// Damage type
495    pub damage_type: DamageType,
496    /// Psyche cost
497    pub psyche_cost: f32,
498    /// Cooldown in turns
499    pub cooldown: u32,
500    /// Current cooldown remaining
501    pub current_cooldown: u32,
502    /// Required reality layer (if any)
503    pub reality_requirement: Option<RealityLayer>,
504    /// Required alter category (if any)
505    pub alter_requirement: Option<String>,
506    /// Status effects applied
507    pub applies_effects: Vec<StatusEffect>,
508    /// Is this a defensive ability?
509    pub is_defensive: bool,
510}
511
512/// Types of damage
513#[derive(Debug, Clone, PartialEq)]
514pub enum DamageType {
515    Physical,
516    Psychic,
517    Emotional,
518    Reality, // Affects reality perception
519}
520
521/// Result of using an ability
522#[derive(Debug, Clone)]
523pub enum AbilityResult {
524    Success { damage: f32, effects: Vec<StatusEffect> },
525    Failed(String),
526    Blocked,
527}
528
529// ============================================================================
530// STATUS EFFECTS
531// ============================================================================
532
533/// A status effect that modifies combat
534#[derive(Debug, Clone)]
535pub struct StatusEffect {
536    /// Effect ID
537    pub id: String,
538    /// Display name
539    pub name: String,
540    /// Duration in turns (None = permanent)
541    pub duration: Option<u32>,
542    /// Effect type
543    pub effect_type: StatusEffectType,
544    /// Intensity/magnitude
545    pub intensity: f32,
546}
547
548/// Types of status effects
549#[derive(Debug, Clone)]
550pub enum StatusEffectType {
551    /// Damage over time
552    Bleed,
553    /// Psyche drain
554    PsycheDrain,
555    /// Cannot switch alters
556    SwitchLocked,
557    /// Reality perception forced
558    RealityLocked(RealityLayer),
559    /// Damage reduction
560    Shielded,
561    /// Increased damage output
562    Empowered,
563    /// Reduced accuracy/effectiveness
564    Disoriented,
565    /// Cannot use abilities
566    Silenced,
567    /// Specific trigger is active
568    Triggered(String),
569    /// Healing over time
570    Regenerating,
571}
572
573// ============================================================================
574// COMBAT ENVIRONMENT
575// ============================================================================
576
577/// Environmental factors in combat
578#[derive(Debug, Clone, Default)]
579pub struct CombatEnvironment {
580    /// Current reality layer visibility
581    pub reality_layers: Vec<RealityLayer>,
582    /// Environmental hazards
583    pub hazards: Vec<EnvironmentHazard>,
584    /// Cover/defensive positions available
585    pub defensive_positions: u32,
586    /// Light level (affects perception)
587    pub light_level: f32,
588    /// Ambient trigger intensity
589    pub ambient_trigger_intensity: f32,
590}
591
592/// Environmental hazards
593#[derive(Debug, Clone)]
594pub struct EnvironmentHazard {
595    pub name: String,
596    pub damage_per_turn: f32,
597    pub affects_reality: Option<RealityLayer>,
598}
599
600// ============================================================================
601// COMBAT EFFECTS
602// ============================================================================
603
604/// Active effects in combat
605#[derive(Debug, Clone)]
606pub struct CombatEffect {
607    pub name: String,
608    pub duration: u32,
609    pub effect: CombatEffectType,
610}
611
612#[derive(Debug, Clone)]
613pub enum CombatEffectType {
614    /// Reality is shifting
615    RealityFlux,
616    /// Forced switching is occurring
617    SystemInstability,
618    /// Environmental change
619    EnvironmentChange(String),
620}
621
622// ============================================================================
623// HELPER FUNCTIONS
624// ============================================================================
625
626/// Generate combat abilities for an alter based on their traits
627fn generate_combat_abilities(alter: &Alter) -> Vec<CombatAbility> {
628    let mut abilities = Vec::new();
629
630    // Basic attack available to all
631    abilities.push(CombatAbility {
632        id: "basic_attack".to_string(),
633        name: "Strike".to_string(),
634        description: "A basic physical attack".to_string(),
635        base_damage: 10.0,
636        damage_type: DamageType::Physical,
637        psyche_cost: 0.0,
638        cooldown: 0,
639        current_cooldown: 0,
640        reality_requirement: None,
641        alter_requirement: None,
642        applies_effects: Vec::new(),
643        is_defensive: false,
644    });
645
646    // Add abilities based on alter's abilities set
647    for ability_name in &alter.abilities {
648        match ability_name.as_str() {
649            "combat" | "combat_master" => {
650                abilities.push(CombatAbility {
651                    id: "power_strike".to_string(),
652                    name: "Power Strike".to_string(),
653                    description: "A devastating physical attack".to_string(),
654                    base_damage: 25.0,
655                    damage_type: DamageType::Physical,
656                    psyche_cost: 10.0,
657                    cooldown: 2,
658                    current_cooldown: 0,
659                    reality_requirement: None,
660                    alter_requirement: None,
661                    applies_effects: Vec::new(),
662                    is_defensive: false,
663                });
664            }
665            "perception" | "perceive" => {
666                abilities.push(CombatAbility {
667                    id: "reality_sight".to_string(),
668                    name: "Reality Sight".to_string(),
669                    description: "Pierce the veil between layers".to_string(),
670                    base_damage: 0.0,
671                    damage_type: DamageType::Reality,
672                    psyche_cost: 15.0,
673                    cooldown: 3,
674                    current_cooldown: 0,
675                    reality_requirement: None,
676                    alter_requirement: None,
677                    applies_effects: Vec::new(),
678                    is_defensive: true,
679                });
680            }
681            "protection" | "shield" => {
682                abilities.push(CombatAbility {
683                    id: "protective_barrier".to_string(),
684                    name: "Protective Barrier".to_string(),
685                    description: "Shield the system from harm".to_string(),
686                    base_damage: 0.0,
687                    damage_type: DamageType::Psychic,
688                    psyche_cost: 20.0,
689                    cooldown: 3,
690                    current_cooldown: 0,
691                    reality_requirement: None,
692                    alter_requirement: None,
693                    applies_effects: vec![StatusEffect {
694                        id: "shielded".to_string(),
695                        name: "Shielded".to_string(),
696                        duration: Some(2),
697                        effect_type: StatusEffectType::Shielded,
698                        intensity: 0.5,
699                    }],
700                    is_defensive: true,
701                });
702            }
703            "trauma_processing" => {
704                abilities.push(CombatAbility {
705                    id: "trauma_strike".to_string(),
706                    name: "Trauma Strike".to_string(),
707                    description: "Channel trauma into devastating force".to_string(),
708                    base_damage: 35.0,
709                    damage_type: DamageType::Emotional,
710                    psyche_cost: 25.0,
711                    cooldown: 4,
712                    current_cooldown: 0,
713                    reality_requirement: Some(RealityLayer::Fractured),
714                    alter_requirement: None,
715                    applies_effects: Vec::new(),
716                    is_defensive: false,
717                });
718            }
719            _ => {}
720        }
721    }
722
723    // Add reality-specific ability if alter prefers fractured
724    if matches!(alter.preferred_reality, RealityLayer::Fractured | RealityLayer::Shattered) {
725        abilities.push(CombatAbility {
726            id: "fractured_assault".to_string(),
727            name: "Fractured Assault".to_string(),
728            description: "Attack from the fractured realm".to_string(),
729            base_damage: 20.0,
730            damage_type: DamageType::Reality,
731            psyche_cost: 15.0,
732            cooldown: 2,
733            current_cooldown: 0,
734            reality_requirement: Some(RealityLayer::Fractured),
735            alter_requirement: None,
736            applies_effects: Vec::new(),
737            is_defensive: false,
738        });
739    }
740
741    abilities
742}
743
744// ============================================================================
745// TESTS
746// ============================================================================
747
748#[cfg(test)]
749mod tests {
750    use super::*;
751    use std::collections::HashSet;
752
753    fn create_test_system() -> PluralSystem {
754        let mut system = PluralSystem::new("Test System");
755
756        let alter = Alter {
757            id: "protector".to_string(),
758            name: "Protector".to_string(),
759            category: super::super::runtime::AlterCategory::Council,
760            state: AlterPresenceState::Fronting,
761            anima: AnimaState::new(0.0, 0.5, 0.7),
762            base_arousal: 0.5,
763            base_dominance: 0.7,
764            time_since_front: 0,
765            triggers: vec!["threat".to_string()],
766            abilities: HashSet::from(["combat".to_string(), "protection".to_string()]),
767            preferred_reality: RealityLayer::Grounded,
768            memory_access: super::super::runtime::MemoryAccess::Full,
769        };
770
771        system.add_alter(alter);
772        system.fronting = FrontingState::Single("protector".to_string());
773        system
774    }
775
776    #[test]
777    fn test_combat_state_creation() {
778        let system = create_test_system();
779        let enemy = Enemy {
780            id: "enemy1".to_string(),
781            name: "Shadow".to_string(),
782            enemy_type: EnemyType::Manifestation,
783            health: 50.0,
784            max_health: 50.0,
785            initiative: 8.0,
786            abilities: Vec::new(),
787            status_effects: Vec::new(),
788            trigger_behaviors: Vec::new(),
789            reality_visibility: vec![RealityLayer::Fractured],
790        };
791
792        let combat = CombatState::new(&system, vec![enemy]);
793        assert_eq!(combat.combatants.len(), 2);
794        assert_eq!(combat.round, 1);
795        assert!(!combat.is_over);
796    }
797
798    #[test]
799    fn test_ability_execution() {
800        let system = create_test_system();
801        let mut player = PlayerCombatant::from_system(&system);
802        let mut enemy = Combatant::Enemy(Enemy {
803            id: "enemy1".to_string(),
804            name: "Target".to_string(),
805            enemy_type: EnemyType::Human,
806            health: 100.0,
807            max_health: 100.0,
808            initiative: 5.0,
809            abilities: Vec::new(),
810            status_effects: Vec::new(),
811            trigger_behaviors: Vec::new(),
812            reality_visibility: vec![RealityLayer::Grounded],
813        });
814
815        // Basic attack should work
816        let result = player.execute_ability(0, &mut enemy);
817        assert!(matches!(result, AbilityResult::Success { .. }));
818
819        // Enemy should have taken damage
820        if let Combatant::Enemy(e) = &enemy {
821            assert!(e.health < 100.0);
822        }
823    }
824}