sigil_parser/plurality/
game_loop.rs

1//! # Game Loop for DAEMONIORUM
2//!
3//! The core game loop integrating plural system, combat, perception,
4//! and narrative progression.
5
6use std::collections::HashMap;
7use std::time::{Duration, Instant};
8
9use super::combat::{CombatResult, CombatState, Enemy};
10use super::perception::PerceptionManager;
11use super::runtime::{
12    Alter, AlterCategory, AlterPresenceState, AnimaState, FrontingState, MemoryAccess,
13    PluralSystem, RealityLayer, Trigger, TriggerCategory,
14};
15
16// ============================================================================
17// GAME STATE
18// ============================================================================
19
20/// The complete game state
21#[derive(Debug)]
22pub struct GameState {
23    /// The plural system (player)
24    pub system: PluralSystem,
25    /// Current perception state
26    pub perception: PerceptionManager,
27    /// Active combat (if any)
28    pub combat: Option<CombatState>,
29    /// Current scene/location
30    pub scene: Scene,
31    /// Game time (in-game seconds)
32    pub game_time: u64,
33    /// Real time tracking
34    pub real_time: Instant,
35    /// Game phase
36    pub phase: GamePhase,
37    /// Event queue
38    pub events: Vec<GameEvent>,
39    /// Narrative flags
40    pub flags: HashMap<String, FlagValue>,
41    /// Player inventory
42    pub inventory: Vec<Item>,
43    /// Unlocked abilities
44    pub unlocked_abilities: Vec<String>,
45    /// Processed traumas (for progression)
46    pub processed_traumas: Vec<String>,
47}
48
49impl GameState {
50    /// Create a new game with initial setup
51    pub fn new() -> Self {
52        let mut system = PluralSystem::new("The Council");
53
54        // Initialize with starter alters
55        system.add_alter(create_host_alter());
56        system.add_alter(create_protector_alter());
57        system.fronting = FrontingState::Single("host".to_string());
58
59        Self {
60            system,
61            perception: PerceptionManager::new(),
62            combat: None,
63            scene: Scene::default(),
64            game_time: 0,
65            real_time: Instant::now(),
66            phase: GamePhase::Exploration,
67            events: Vec::new(),
68            flags: HashMap::new(),
69            inventory: Vec::new(),
70            unlocked_abilities: Vec::new(),
71            processed_traumas: Vec::new(),
72        }
73    }
74
75    /// Load game from save data
76    pub fn from_save(save: SaveData) -> Self {
77        Self {
78            system: save.system,
79            perception: PerceptionManager::new(),
80            combat: None,
81            scene: save.scene,
82            game_time: save.game_time,
83            real_time: Instant::now(),
84            phase: GamePhase::Exploration,
85            events: Vec::new(),
86            flags: save.flags,
87            inventory: save.inventory,
88            unlocked_abilities: save.unlocked_abilities,
89            processed_traumas: save.processed_traumas,
90        }
91    }
92
93    /// Create save data
94    pub fn to_save(&self) -> SaveData {
95        SaveData {
96            system: self.system.clone(),
97            scene: self.scene.clone(),
98            game_time: self.game_time,
99            flags: self.flags.clone(),
100            inventory: self.inventory.clone(),
101            unlocked_abilities: self.unlocked_abilities.clone(),
102            processed_traumas: self.processed_traumas.clone(),
103        }
104    }
105}
106
107/// Game phases
108#[derive(Debug, Clone, PartialEq)]
109pub enum GamePhase {
110    /// Free exploration
111    Exploration,
112    /// In dialogue
113    Dialogue(DialogueState),
114    /// In combat
115    Combat,
116    /// Cutscene/narrative
117    Cutscene,
118    /// Headspace navigation
119    Headspace,
120    /// Paused
121    Paused,
122    /// Game over
123    GameOver(GameOverReason),
124}
125
126/// Reasons for game over
127#[derive(Debug, Clone, PartialEq)]
128pub enum GameOverReason {
129    /// System completely destabilized
130    SystemCollapse,
131    /// Player chose to end
132    PlayerChoice,
133    /// Story ending reached
134    Ending(String),
135}
136
137// ============================================================================
138// GAME LOOP
139// ============================================================================
140
141/// The main game loop
142pub struct GameLoop {
143    /// Current game state
144    pub state: GameState,
145    /// Target updates per second
146    pub target_ups: u32,
147    /// Is the game running?
148    pub running: bool,
149    /// Pending input
150    pub input_queue: Vec<PlayerInput>,
151}
152
153impl GameLoop {
154    /// Create a new game loop
155    pub fn new() -> Self {
156        Self {
157            state: GameState::new(),
158            target_ups: 60,
159            running: true,
160            input_queue: Vec::new(),
161        }
162    }
163
164    /// Run the game loop
165    pub fn run(&mut self) {
166        let frame_duration = Duration::from_secs(1) / self.target_ups;
167        let mut last_update = Instant::now();
168
169        while self.running {
170            let now = Instant::now();
171            let delta = now.duration_since(last_update);
172
173            if delta >= frame_duration {
174                self.update(delta);
175                last_update = now;
176            }
177
178            // Small sleep to prevent CPU spinning
179            std::thread::sleep(Duration::from_millis(1));
180        }
181    }
182
183    /// Single update tick
184    pub fn update(&mut self, delta: Duration) {
185        // Process input
186        self.process_input();
187
188        // Update based on phase
189        match &self.state.phase {
190            GamePhase::Exploration => self.update_exploration(delta),
191            GamePhase::Dialogue(_) => self.update_dialogue(delta),
192            GamePhase::Combat => self.update_combat(delta),
193            GamePhase::Cutscene => self.update_cutscene(delta),
194            GamePhase::Headspace => self.update_headspace(delta),
195            GamePhase::Paused => {}
196            GamePhase::GameOver(_) => {}
197        }
198
199        // Always update core systems
200        self.update_plural_system(delta);
201        self.update_perception(delta);
202        self.process_events();
203
204        // Update game time
205        self.state.game_time += delta.as_millis() as u64 / 100; // 10x faster than real time
206
207        // Check for game over conditions
208        self.check_game_over();
209    }
210
211    /// Process player input
212    fn process_input(&mut self) {
213        while let Some(input) = self.input_queue.pop() {
214            match input {
215                PlayerInput::Move(direction) => {
216                    if self.state.phase == GamePhase::Exploration {
217                        self.handle_movement(direction);
218                    }
219                }
220                PlayerInput::Interact => {
221                    self.handle_interaction();
222                }
223                PlayerInput::OpenMenu(menu) => {
224                    self.open_menu(menu);
225                }
226                PlayerInput::SwitchAlter(alter_id) => {
227                    self.request_alter_switch(&alter_id);
228                }
229                PlayerInput::UseAbility(ability_id) => {
230                    self.use_ability(&ability_id);
231                }
232                PlayerInput::Ground => {
233                    self.attempt_grounding();
234                }
235                PlayerInput::Pause => {
236                    self.toggle_pause();
237                }
238                PlayerInput::DialogueChoice(choice) => {
239                    if let GamePhase::Dialogue(ref mut dialogue) = self.state.phase {
240                        self.select_dialogue_choice(choice);
241                    }
242                }
243                PlayerInput::CombatAction(action) => {
244                    if self.state.phase == GamePhase::Combat {
245                        self.execute_combat_action(action);
246                    }
247                }
248            }
249        }
250    }
251
252    /// Update during exploration phase
253    fn update_exploration(&mut self, delta: Duration) {
254        // Update scene entities
255        for entity in &mut self.state.scene.entities {
256            entity.update(delta);
257        }
258
259        // Check for trigger zones
260        self.check_trigger_zones();
261
262        // Check for combat encounters
263        if let Some(enemy) = self.check_enemy_encounter() {
264            self.start_combat(enemy);
265        }
266
267        // Check for interactive objects
268        self.update_interactables();
269    }
270
271    /// Update during combat
272    fn update_combat(&mut self, _delta: Duration) {
273        // Get combat result if combat is over
274        let combat_result = if let Some(ref mut combat) = self.state.combat {
275            // Combat is turn-based, so this mainly handles animations/effects
276            combat.check_combat_end();
277            if combat.is_over {
278                combat.result.clone()
279            } else {
280                None
281            }
282        } else {
283            None
284        };
285
286        // End combat if result is set
287        if let Some(result) = combat_result {
288            self.end_combat(Some(result));
289        }
290    }
291
292    /// Update during dialogue
293    fn update_dialogue(&mut self, _delta: Duration) {
294        // Dialogue is event-driven, minimal updates needed
295    }
296
297    /// Update during cutscene
298    fn update_cutscene(&mut self, delta: Duration) {
299        // Advance cutscene based on timing
300        if let Some(cutscene) = &mut self.state.scene.active_cutscene {
301            cutscene.advance(delta);
302            if cutscene.is_complete() {
303                self.end_cutscene();
304            }
305        }
306    }
307
308    /// Update headspace navigation
309    fn update_headspace(&mut self, delta: Duration) {
310        // Update headspace-specific logic
311        self.update_inner_world(delta);
312    }
313
314    /// Update the plural system
315    fn update_plural_system(&mut self, delta: Duration) {
316        let delta_secs = delta.as_secs_f32();
317
318        // Update alter time tracking
319        for alter in self.state.system.alters.values_mut() {
320            if !matches!(alter.state, AlterPresenceState::Fronting) {
321                alter.time_since_front += (delta_secs * 1000.0) as u64;
322            }
323        }
324
325        // Natural stability recovery (when safe)
326        if self.state.phase == GamePhase::Exploration && self.state.scene.safety_level > 0.5 {
327            self.state.system.stability += delta_secs * 0.01;
328            self.state.system.stability = self.state.system.stability.min(1.0);
329        }
330
331        // Natural dissociation decay
332        if self.state.system.dissociation > 0.0 {
333            self.state.system.dissociation -= delta_secs * 0.005;
334            self.state.system.dissociation = self.state.system.dissociation.max(0.0);
335        }
336
337        // Update blended anima
338        self.state.system.update_blended_anima();
339
340        // Process any active triggers
341        let triggers: Vec<_> = self.state.system.active_triggers.drain(..).collect();
342        for trigger in triggers {
343            let result = self.state.system.process_trigger(trigger);
344            self.handle_trigger_result(result);
345        }
346    }
347
348    /// Update perception system
349    fn update_perception(&mut self, _delta: Duration) {
350        self.state.perception.update(&self.state.system);
351
352        // Process perception triggers
353        let triggers = self.state.perception.drain_triggers();
354        for trigger in triggers {
355            self.state.system.active_triggers.push(trigger);
356        }
357
358        // Reality layer affects gameplay
359        match self.state.perception.state.layer {
360            RealityLayer::Grounded => {
361                // Normal gameplay
362            }
363            RealityLayer::Fractured => {
364                // Reveal hidden entities/clues
365                self.reveal_fractured_content();
366            }
367            RealityLayer::Shattered => {
368                // Potential danger, unique interactions
369                self.handle_shattered_reality();
370            }
371            RealityLayer::Custom(_) => {}
372        }
373    }
374
375    /// Process queued events
376    fn process_events(&mut self) {
377        let events: Vec<_> = self.state.events.drain(..).collect();
378        for event in events {
379            self.handle_event(event);
380        }
381    }
382
383    /// Check for game over conditions
384    fn check_game_over(&mut self) {
385        // System collapse
386        if self.state.system.stability <= 0.0 && self.state.system.dissociation >= 1.0 {
387            self.state.phase = GamePhase::GameOver(GameOverReason::SystemCollapse);
388        }
389    }
390
391    // ========================================================================
392    // ACTION HANDLERS
393    // ========================================================================
394
395    /// Handle movement input
396    fn handle_movement(&mut self, direction: Direction) {
397        // Update player position in scene
398        self.state.scene.move_player(direction);
399
400        // Check for zone transitions
401        if let Some(transition) = self.state.scene.check_transition() {
402            self.transition_scene(transition);
403        }
404    }
405
406    /// Handle interaction with nearby objects
407    fn handle_interaction(&mut self) {
408        // Clone the interactable to avoid borrow issues
409        let interaction = self.state.scene.nearest_interactable().cloned();
410
411        if let Some(target) = interaction {
412            match &target.interaction_type {
413                InteractionType::Examine => {
414                    self.show_examination(&target);
415                }
416                InteractionType::Talk(npc_id) => {
417                    self.start_dialogue(npc_id.clone());
418                }
419                InteractionType::PickUp(item) => {
420                    self.pick_up_item(item.clone());
421                }
422                InteractionType::Use => {
423                    self.use_object(&target);
424                }
425                InteractionType::Enter(scene_id) => {
426                    self.transition_scene(scene_id.clone());
427                }
428            }
429        }
430    }
431
432    /// Request an alter switch
433    fn request_alter_switch(&mut self, alter_id: &str) {
434        let urgency = if self.state.phase == GamePhase::Combat {
435            0.8
436        } else {
437            0.5
438        };
439        let result = self.state.system.request_switch(alter_id, urgency, false);
440
441        match result {
442            super::runtime::SwitchResult::Success => {
443                self.state
444                    .events
445                    .push(GameEvent::AlterSwitched(alter_id.to_string()));
446
447                // Reality might shift based on new fronter
448                if let Some(alter) = self.state.system.alters.get(alter_id) {
449                    self.state.perception.state.layer = alter.preferred_reality.clone();
450                }
451            }
452            super::runtime::SwitchResult::Resisted { resistance } => {
453                self.state
454                    .events
455                    .push(GameEvent::SwitchResisted(resistance));
456            }
457            super::runtime::SwitchResult::Failed(reason) => {
458                self.state
459                    .events
460                    .push(GameEvent::SwitchFailed(format!("{:?}", reason)));
461            }
462            _ => {}
463        }
464    }
465
466    /// Attempt a grounding exercise
467    fn attempt_grounding(&mut self) {
468        // Grounding helps with dissociation and reality stability
469        let success_chance = 0.5 + self.state.system.stability * 0.3;
470
471        if rand_float() < success_chance {
472            self.state.system.dissociation -= 0.2;
473            self.state.system.dissociation = self.state.system.dissociation.max(0.0);
474
475            // Move toward grounded reality
476            if self.state.perception.state.layer != RealityLayer::Grounded {
477                self.state
478                    .perception
479                    .begin_layer_transition(RealityLayer::Grounded, 0.1);
480            }
481
482            self.state.events.push(GameEvent::GroundingSuccess);
483        } else {
484            self.state.events.push(GameEvent::GroundingFailed);
485        }
486    }
487
488    /// Start combat with an enemy
489    fn start_combat(&mut self, enemy: Enemy) {
490        let combat = CombatState::new(&self.state.system, vec![enemy]);
491        self.state.combat = Some(combat);
492        self.state.phase = GamePhase::Combat;
493        self.state.events.push(GameEvent::CombatStarted);
494    }
495
496    /// End combat
497    fn end_combat(&mut self, result: Option<CombatResult>) {
498        self.state.combat = None;
499        self.state.phase = GamePhase::Exploration;
500
501        match result {
502            Some(CombatResult::Victory) => {
503                self.state.events.push(GameEvent::CombatVictory);
504            }
505            Some(CombatResult::Defeat) => {
506                // Handle defeat (not game over, system protects itself)
507                self.handle_combat_defeat();
508            }
509            Some(CombatResult::Flee) => {
510                self.state.events.push(GameEvent::CombatFled);
511            }
512            _ => {}
513        }
514    }
515
516    /// Handle combat defeat
517    fn handle_combat_defeat(&mut self) {
518        // Emergency switch to protector
519        if let Some(protector) = self.find_protector() {
520            self.state.system.request_switch(&protector, 1.0, true);
521        }
522
523        // Increase dissociation
524        self.state.system.dissociation += 0.3;
525
526        // Retreat to safer area
527        self.state.events.push(GameEvent::EmergencyRetreat);
528    }
529
530    /// Start dialogue with an NPC
531    fn start_dialogue(&mut self, npc_id: String) {
532        let dialogue_state = DialogueState {
533            npc_id,
534            current_node: "start".to_string(),
535            history: Vec::new(),
536        };
537        self.state.phase = GamePhase::Dialogue(dialogue_state);
538        self.state.events.push(GameEvent::DialogueStarted);
539    }
540
541    /// Handle trigger result
542    fn handle_trigger_result(&mut self, result: super::runtime::TriggerResult) {
543        match result {
544            super::runtime::TriggerResult::ForcedSwitch(alter_id) => {
545                self.state.events.push(GameEvent::ForcedSwitch(alter_id));
546            }
547            super::runtime::TriggerResult::Activation(alters) => {
548                for alter_id in alters {
549                    self.state.events.push(GameEvent::AlterActivated(alter_id));
550                }
551            }
552            super::runtime::TriggerResult::Dissociation => {
553                self.state.system.dissociation += 0.2;
554                self.state.events.push(GameEvent::DissociationSpike);
555            }
556            super::runtime::TriggerResult::NoResponse => {}
557        }
558    }
559
560    /// Handle a game event
561    fn handle_event(&mut self, event: GameEvent) {
562        // Events can be processed by UI, narrative system, etc.
563        // This is the hook for external systems
564        match event {
565            GameEvent::AlterSwitched(id) => {
566                // Update UI, play animation, etc.
567            }
568            GameEvent::CombatStarted => {
569                // Transition music, UI, etc.
570            }
571            GameEvent::TraumaProcessed(id) => {
572                self.state.processed_traumas.push(id);
573            }
574            _ => {}
575        }
576    }
577
578    // ========================================================================
579    // HELPER METHODS
580    // ========================================================================
581
582    fn find_protector(&self) -> Option<String> {
583        self.state
584            .system
585            .alters
586            .values()
587            .find(|a| a.abilities.contains("protection") || a.abilities.contains("combat"))
588            .map(|a| a.id.clone())
589    }
590
591    fn check_trigger_zones(&mut self) {
592        // Check if player is in a trigger zone
593        for zone in &self.state.scene.trigger_zones {
594            if zone.contains(self.state.scene.player_position) {
595                let trigger = Trigger {
596                    id: zone.trigger_id.clone(),
597                    name: zone.name.clone(),
598                    category: TriggerCategory::Environmental,
599                    intensity: zone.intensity,
600                    context: HashMap::new(),
601                };
602                self.state.system.active_triggers.push(trigger);
603            }
604        }
605    }
606
607    fn check_enemy_encounter(&self) -> Option<Enemy> {
608        // Check for enemy encounters in current scene
609        None // Placeholder
610    }
611
612    fn reveal_fractured_content(&mut self) {
613        // In Fractured reality, hidden content becomes visible
614    }
615
616    fn handle_shattered_reality(&mut self) {
617        // Shattered reality has unique dangers
618        if rand_float() < 0.01 {
619            self.state.system.dissociation += 0.05;
620        }
621    }
622
623    fn update_inner_world(&mut self, _delta: Duration) {
624        // Headspace-specific updates
625    }
626
627    fn show_examination(&mut self, _target: &Interactable) {}
628    fn pick_up_item(&mut self, item: Item) {
629        self.state.inventory.push(item);
630    }
631    fn use_object(&mut self, _target: &Interactable) {}
632    fn transition_scene(&mut self, _scene_id: String) {}
633    fn update_interactables(&mut self) {}
634    fn open_menu(&mut self, _menu: MenuType) {}
635    fn use_ability(&mut self, _ability_id: &str) {}
636    fn toggle_pause(&mut self) {
637        if self.state.phase == GamePhase::Paused {
638            self.state.phase = GamePhase::Exploration;
639        } else {
640            self.state.phase = GamePhase::Paused;
641        }
642    }
643    fn select_dialogue_choice(&mut self, _choice: usize) {}
644    fn execute_combat_action(&mut self, _action: CombatAction) {}
645    fn end_cutscene(&mut self) {
646        self.state.phase = GamePhase::Exploration;
647    }
648}
649
650// ============================================================================
651// SUPPORTING TYPES
652// ============================================================================
653
654/// Player input types
655#[derive(Debug, Clone)]
656pub enum PlayerInput {
657    Move(Direction),
658    Interact,
659    OpenMenu(MenuType),
660    SwitchAlter(String),
661    UseAbility(String),
662    Ground,
663    Pause,
664    DialogueChoice(usize),
665    CombatAction(CombatAction),
666}
667
668/// Movement directions
669#[derive(Debug, Clone, Copy)]
670pub enum Direction {
671    Up,
672    Down,
673    Left,
674    Right,
675}
676
677/// Menu types
678#[derive(Debug, Clone)]
679pub enum MenuType {
680    System,
681    Inventory,
682    Alters,
683    Map,
684}
685
686/// Combat actions
687#[derive(Debug, Clone)]
688pub enum CombatAction {
689    Attack(usize),
690    Defend,
691    UseItem(usize),
692    Switch(String),
693    Flee,
694}
695
696/// Game events for external systems
697#[derive(Debug, Clone)]
698pub enum GameEvent {
699    AlterSwitched(String),
700    AlterActivated(String),
701    ForcedSwitch(String),
702    SwitchResisted(f32),
703    SwitchFailed(String),
704    CombatStarted,
705    CombatVictory,
706    CombatDefeat,
707    CombatFled,
708    EmergencyRetreat,
709    DialogueStarted,
710    DialogueEnded,
711    GroundingSuccess,
712    GroundingFailed,
713    DissociationSpike,
714    RealityShift(RealityLayer),
715    TraumaProcessed(String),
716    ItemAcquired(String),
717    AbilityUnlocked(String),
718}
719
720/// Dialogue state
721#[derive(Debug, Clone, PartialEq)]
722pub struct DialogueState {
723    pub npc_id: String,
724    pub current_node: String,
725    pub history: Vec<String>,
726}
727
728/// Scene data
729#[derive(Debug, Clone, Default)]
730pub struct Scene {
731    pub id: String,
732    pub name: String,
733    pub safety_level: f32,
734    pub entities: Vec<SceneEntity>,
735    pub trigger_zones: Vec<TriggerZone>,
736    pub interactables: Vec<Interactable>,
737    pub player_position: (f32, f32),
738    pub active_cutscene: Option<Cutscene>,
739}
740
741impl Scene {
742    fn move_player(&mut self, direction: Direction) {
743        let speed = 5.0;
744        match direction {
745            Direction::Up => self.player_position.1 -= speed,
746            Direction::Down => self.player_position.1 += speed,
747            Direction::Left => self.player_position.0 -= speed,
748            Direction::Right => self.player_position.0 += speed,
749        }
750    }
751
752    fn check_transition(&self) -> Option<String> {
753        None
754    }
755
756    fn nearest_interactable(&self) -> Option<&Interactable> {
757        None
758    }
759}
760
761/// Scene entity
762#[derive(Debug, Clone)]
763pub struct SceneEntity {
764    pub id: String,
765    pub position: (f32, f32),
766    pub entity_type: String,
767}
768
769impl SceneEntity {
770    fn update(&mut self, _delta: Duration) {}
771}
772
773/// Trigger zone in scene
774#[derive(Debug, Clone)]
775pub struct TriggerZone {
776    pub name: String,
777    pub trigger_id: String,
778    pub bounds: ((f32, f32), (f32, f32)),
779    pub intensity: f32,
780}
781
782impl TriggerZone {
783    fn contains(&self, pos: (f32, f32)) -> bool {
784        pos.0 >= self.bounds.0 .0
785            && pos.0 <= self.bounds.1 .0
786            && pos.1 >= self.bounds.0 .1
787            && pos.1 <= self.bounds.1 .1
788    }
789}
790
791/// Interactable object
792#[derive(Debug, Clone)]
793pub struct Interactable {
794    pub id: String,
795    pub position: (f32, f32),
796    pub interaction_type: InteractionType,
797}
798
799/// Interaction types
800#[derive(Debug, Clone)]
801pub enum InteractionType {
802    Examine,
803    Talk(String),
804    PickUp(Item),
805    Use,
806    Enter(String),
807}
808
809/// Inventory item
810#[derive(Debug, Clone)]
811pub struct Item {
812    pub id: String,
813    pub name: String,
814    pub description: String,
815    pub item_type: ItemType,
816}
817
818/// Item types
819#[derive(Debug, Clone)]
820pub enum ItemType {
821    Key,
822    Consumable,
823    Document,
824    Memento,
825}
826
827/// Cutscene data
828#[derive(Debug, Clone)]
829pub struct Cutscene {
830    pub id: String,
831    pub duration: Duration,
832    pub elapsed: Duration,
833}
834
835impl Cutscene {
836    fn advance(&mut self, delta: Duration) {
837        self.elapsed += delta;
838    }
839
840    fn is_complete(&self) -> bool {
841        self.elapsed >= self.duration
842    }
843}
844
845/// Save data
846#[derive(Debug, Clone)]
847pub struct SaveData {
848    pub system: PluralSystem,
849    pub scene: Scene,
850    pub game_time: u64,
851    pub flags: HashMap<String, FlagValue>,
852    pub inventory: Vec<Item>,
853    pub unlocked_abilities: Vec<String>,
854    pub processed_traumas: Vec<String>,
855}
856
857/// Flag values for narrative state
858#[derive(Debug, Clone)]
859pub enum FlagValue {
860    Bool(bool),
861    Int(i32),
862    String(String),
863}
864
865// ============================================================================
866// INITIALIZATION HELPERS
867// ============================================================================
868
869fn create_host_alter() -> Alter {
870    use std::collections::HashSet;
871    Alter {
872        id: "host".to_string(),
873        name: "Host".to_string(),
874        category: AlterCategory::Council,
875        state: AlterPresenceState::Fronting,
876        anima: AnimaState::default(),
877        base_arousal: 0.0,
878        base_dominance: 0.0,
879        time_since_front: 0,
880        triggers: vec![],
881        abilities: HashSet::from(["perception".to_string()]),
882        preferred_reality: RealityLayer::Grounded,
883        memory_access: MemoryAccess::Partial(vec!["recent".to_string()]),
884    }
885}
886
887fn create_protector_alter() -> Alter {
888    use std::collections::HashSet;
889    Alter {
890        id: "protector".to_string(),
891        name: "Protector".to_string(),
892        category: AlterCategory::Council,
893        state: AlterPresenceState::Dormant,
894        anima: AnimaState::new(0.0, 0.5, 0.7),
895        base_arousal: 0.5,
896        base_dominance: 0.7,
897        time_since_front: 1000,
898        triggers: vec!["threat".to_string(), "danger".to_string()],
899        abilities: HashSet::from(["combat".to_string(), "protection".to_string()]),
900        preferred_reality: RealityLayer::Grounded,
901        memory_access: MemoryAccess::Full,
902    }
903}
904
905/// Placeholder random function
906fn rand_float() -> f32 {
907    // In real implementation, use proper RNG
908    0.5
909}
910
911// ============================================================================
912// TESTS
913// ============================================================================
914
915#[cfg(test)]
916mod tests {
917    use super::*;
918
919    #[test]
920    fn test_game_state_new() {
921        let state = GameState::new();
922        assert!(state.system.alters.contains_key("host"));
923        assert!(state.system.alters.contains_key("protector"));
924        assert_eq!(state.phase, GamePhase::Exploration);
925    }
926
927    #[test]
928    fn test_game_loop_creation() {
929        let game_loop = GameLoop::new();
930        assert!(game_loop.running);
931        assert_eq!(game_loop.target_ups, 60);
932    }
933
934    #[test]
935    fn test_alter_switch_in_game() {
936        let mut game_loop = GameLoop::new();
937        game_loop.request_alter_switch("protector");
938
939        // Check that an event was generated
940        assert!(!game_loop.state.events.is_empty());
941    }
942
943    #[test]
944    fn test_grounding_mechanic() {
945        let mut game_loop = GameLoop::new();
946        game_loop.state.system.dissociation = 0.5;
947        game_loop.state.system.stability = 0.8;
948
949        game_loop.attempt_grounding();
950
951        // Event should be generated
952        assert!(!game_loop.state.events.is_empty());
953    }
954}