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::{CombatState, CombatResult, Enemy};
10use super::perception::PerceptionManager;
11use super::runtime::{
12    Alter, AlterCategory, AlterPresenceState, AnimaState, FrontingState,
13    MemoryAccess, 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 { combat.result.clone() } else { None }
278        } else {
279            None
280        };
281
282        // End combat if result is set
283        if let Some(result) = combat_result {
284            self.end_combat(Some(result));
285        }
286    }
287
288    /// Update during dialogue
289    fn update_dialogue(&mut self, _delta: Duration) {
290        // Dialogue is event-driven, minimal updates needed
291    }
292
293    /// Update during cutscene
294    fn update_cutscene(&mut self, delta: Duration) {
295        // Advance cutscene based on timing
296        if let Some(cutscene) = &mut self.state.scene.active_cutscene {
297            cutscene.advance(delta);
298            if cutscene.is_complete() {
299                self.end_cutscene();
300            }
301        }
302    }
303
304    /// Update headspace navigation
305    fn update_headspace(&mut self, delta: Duration) {
306        // Update headspace-specific logic
307        self.update_inner_world(delta);
308    }
309
310    /// Update the plural system
311    fn update_plural_system(&mut self, delta: Duration) {
312        let delta_secs = delta.as_secs_f32();
313
314        // Update alter time tracking
315        for alter in self.state.system.alters.values_mut() {
316            if !matches!(alter.state, AlterPresenceState::Fronting) {
317                alter.time_since_front += (delta_secs * 1000.0) as u64;
318            }
319        }
320
321        // Natural stability recovery (when safe)
322        if self.state.phase == GamePhase::Exploration && self.state.scene.safety_level > 0.5 {
323            self.state.system.stability += delta_secs * 0.01;
324            self.state.system.stability = self.state.system.stability.min(1.0);
325        }
326
327        // Natural dissociation decay
328        if self.state.system.dissociation > 0.0 {
329            self.state.system.dissociation -= delta_secs * 0.005;
330            self.state.system.dissociation = self.state.system.dissociation.max(0.0);
331        }
332
333        // Update blended anima
334        self.state.system.update_blended_anima();
335
336        // Process any active triggers
337        let triggers: Vec<_> = self.state.system.active_triggers.drain(..).collect();
338        for trigger in triggers {
339            let result = self.state.system.process_trigger(trigger);
340            self.handle_trigger_result(result);
341        }
342    }
343
344    /// Update perception system
345    fn update_perception(&mut self, _delta: Duration) {
346        self.state.perception.update(&self.state.system);
347
348        // Process perception triggers
349        let triggers = self.state.perception.drain_triggers();
350        for trigger in triggers {
351            self.state.system.active_triggers.push(trigger);
352        }
353
354        // Reality layer affects gameplay
355        match self.state.perception.state.layer {
356            RealityLayer::Grounded => {
357                // Normal gameplay
358            }
359            RealityLayer::Fractured => {
360                // Reveal hidden entities/clues
361                self.reveal_fractured_content();
362            }
363            RealityLayer::Shattered => {
364                // Potential danger, unique interactions
365                self.handle_shattered_reality();
366            }
367            RealityLayer::Custom(_) => {}
368        }
369    }
370
371    /// Process queued events
372    fn process_events(&mut self) {
373        let events: Vec<_> = self.state.events.drain(..).collect();
374        for event in events {
375            self.handle_event(event);
376        }
377    }
378
379    /// Check for game over conditions
380    fn check_game_over(&mut self) {
381        // System collapse
382        if self.state.system.stability <= 0.0 && self.state.system.dissociation >= 1.0 {
383            self.state.phase = GamePhase::GameOver(GameOverReason::SystemCollapse);
384        }
385    }
386
387    // ========================================================================
388    // ACTION HANDLERS
389    // ========================================================================
390
391    /// Handle movement input
392    fn handle_movement(&mut self, direction: Direction) {
393        // Update player position in scene
394        self.state.scene.move_player(direction);
395
396        // Check for zone transitions
397        if let Some(transition) = self.state.scene.check_transition() {
398            self.transition_scene(transition);
399        }
400    }
401
402    /// Handle interaction with nearby objects
403    fn handle_interaction(&mut self) {
404        // Clone the interactable to avoid borrow issues
405        let interaction = self.state.scene.nearest_interactable().cloned();
406
407        if let Some(target) = interaction {
408            match &target.interaction_type {
409                InteractionType::Examine => {
410                    self.show_examination(&target);
411                }
412                InteractionType::Talk(npc_id) => {
413                    self.start_dialogue(npc_id.clone());
414                }
415                InteractionType::PickUp(item) => {
416                    self.pick_up_item(item.clone());
417                }
418                InteractionType::Use => {
419                    self.use_object(&target);
420                }
421                InteractionType::Enter(scene_id) => {
422                    self.transition_scene(scene_id.clone());
423                }
424            }
425        }
426    }
427
428    /// Request an alter switch
429    fn request_alter_switch(&mut self, alter_id: &str) {
430        let urgency = if self.state.phase == GamePhase::Combat { 0.8 } else { 0.5 };
431        let result = self.state.system.request_switch(alter_id, urgency, false);
432
433        match result {
434            super::runtime::SwitchResult::Success => {
435                self.state.events.push(GameEvent::AlterSwitched(alter_id.to_string()));
436
437                // Reality might shift based on new fronter
438                if let Some(alter) = self.state.system.alters.get(alter_id) {
439                    self.state.perception.state.layer = alter.preferred_reality.clone();
440                }
441            }
442            super::runtime::SwitchResult::Resisted { resistance } => {
443                self.state.events.push(GameEvent::SwitchResisted(resistance));
444            }
445            super::runtime::SwitchResult::Failed(reason) => {
446                self.state.events.push(GameEvent::SwitchFailed(format!("{:?}", reason)));
447            }
448            _ => {}
449        }
450    }
451
452    /// Attempt a grounding exercise
453    fn attempt_grounding(&mut self) {
454        // Grounding helps with dissociation and reality stability
455        let success_chance = 0.5 + self.state.system.stability * 0.3;
456
457        if rand_float() < success_chance {
458            self.state.system.dissociation -= 0.2;
459            self.state.system.dissociation = self.state.system.dissociation.max(0.0);
460
461            // Move toward grounded reality
462            if self.state.perception.state.layer != RealityLayer::Grounded {
463                self.state.perception.begin_layer_transition(RealityLayer::Grounded, 0.1);
464            }
465
466            self.state.events.push(GameEvent::GroundingSuccess);
467        } else {
468            self.state.events.push(GameEvent::GroundingFailed);
469        }
470    }
471
472    /// Start combat with an enemy
473    fn start_combat(&mut self, enemy: Enemy) {
474        let combat = CombatState::new(&self.state.system, vec![enemy]);
475        self.state.combat = Some(combat);
476        self.state.phase = GamePhase::Combat;
477        self.state.events.push(GameEvent::CombatStarted);
478    }
479
480    /// End combat
481    fn end_combat(&mut self, result: Option<CombatResult>) {
482        self.state.combat = None;
483        self.state.phase = GamePhase::Exploration;
484
485        match result {
486            Some(CombatResult::Victory) => {
487                self.state.events.push(GameEvent::CombatVictory);
488            }
489            Some(CombatResult::Defeat) => {
490                // Handle defeat (not game over, system protects itself)
491                self.handle_combat_defeat();
492            }
493            Some(CombatResult::Flee) => {
494                self.state.events.push(GameEvent::CombatFled);
495            }
496            _ => {}
497        }
498    }
499
500    /// Handle combat defeat
501    fn handle_combat_defeat(&mut self) {
502        // Emergency switch to protector
503        if let Some(protector) = self.find_protector() {
504            self.state.system.request_switch(&protector, 1.0, true);
505        }
506
507        // Increase dissociation
508        self.state.system.dissociation += 0.3;
509
510        // Retreat to safer area
511        self.state.events.push(GameEvent::EmergencyRetreat);
512    }
513
514    /// Start dialogue with an NPC
515    fn start_dialogue(&mut self, npc_id: String) {
516        let dialogue_state = DialogueState {
517            npc_id,
518            current_node: "start".to_string(),
519            history: Vec::new(),
520        };
521        self.state.phase = GamePhase::Dialogue(dialogue_state);
522        self.state.events.push(GameEvent::DialogueStarted);
523    }
524
525    /// Handle trigger result
526    fn handle_trigger_result(&mut self, result: super::runtime::TriggerResult) {
527        match result {
528            super::runtime::TriggerResult::ForcedSwitch(alter_id) => {
529                self.state.events.push(GameEvent::ForcedSwitch(alter_id));
530            }
531            super::runtime::TriggerResult::Activation(alters) => {
532                for alter_id in alters {
533                    self.state.events.push(GameEvent::AlterActivated(alter_id));
534                }
535            }
536            super::runtime::TriggerResult::Dissociation => {
537                self.state.system.dissociation += 0.2;
538                self.state.events.push(GameEvent::DissociationSpike);
539            }
540            super::runtime::TriggerResult::NoResponse => {}
541        }
542    }
543
544    /// Handle a game event
545    fn handle_event(&mut self, event: GameEvent) {
546        // Events can be processed by UI, narrative system, etc.
547        // This is the hook for external systems
548        match event {
549            GameEvent::AlterSwitched(id) => {
550                // Update UI, play animation, etc.
551            }
552            GameEvent::CombatStarted => {
553                // Transition music, UI, etc.
554            }
555            GameEvent::TraumaProcessed(id) => {
556                self.state.processed_traumas.push(id);
557            }
558            _ => {}
559        }
560    }
561
562    // ========================================================================
563    // HELPER METHODS
564    // ========================================================================
565
566    fn find_protector(&self) -> Option<String> {
567        self.state.system.alters.values()
568            .find(|a| a.abilities.contains("protection") || a.abilities.contains("combat"))
569            .map(|a| a.id.clone())
570    }
571
572    fn check_trigger_zones(&mut self) {
573        // Check if player is in a trigger zone
574        for zone in &self.state.scene.trigger_zones {
575            if zone.contains(self.state.scene.player_position) {
576                let trigger = Trigger {
577                    id: zone.trigger_id.clone(),
578                    name: zone.name.clone(),
579                    category: TriggerCategory::Environmental,
580                    intensity: zone.intensity,
581                    context: HashMap::new(),
582                };
583                self.state.system.active_triggers.push(trigger);
584            }
585        }
586    }
587
588    fn check_enemy_encounter(&self) -> Option<Enemy> {
589        // Check for enemy encounters in current scene
590        None // Placeholder
591    }
592
593    fn reveal_fractured_content(&mut self) {
594        // In Fractured reality, hidden content becomes visible
595    }
596
597    fn handle_shattered_reality(&mut self) {
598        // Shattered reality has unique dangers
599        if rand_float() < 0.01 {
600            self.state.system.dissociation += 0.05;
601        }
602    }
603
604    fn update_inner_world(&mut self, _delta: Duration) {
605        // Headspace-specific updates
606    }
607
608    fn show_examination(&mut self, _target: &Interactable) {}
609    fn pick_up_item(&mut self, item: Item) {
610        self.state.inventory.push(item);
611    }
612    fn use_object(&mut self, _target: &Interactable) {}
613    fn transition_scene(&mut self, _scene_id: String) {}
614    fn update_interactables(&mut self) {}
615    fn open_menu(&mut self, _menu: MenuType) {}
616    fn use_ability(&mut self, _ability_id: &str) {}
617    fn toggle_pause(&mut self) {
618        if self.state.phase == GamePhase::Paused {
619            self.state.phase = GamePhase::Exploration;
620        } else {
621            self.state.phase = GamePhase::Paused;
622        }
623    }
624    fn select_dialogue_choice(&mut self, _choice: usize) {}
625    fn execute_combat_action(&mut self, _action: CombatAction) {}
626    fn end_cutscene(&mut self) {
627        self.state.phase = GamePhase::Exploration;
628    }
629}
630
631// ============================================================================
632// SUPPORTING TYPES
633// ============================================================================
634
635/// Player input types
636#[derive(Debug, Clone)]
637pub enum PlayerInput {
638    Move(Direction),
639    Interact,
640    OpenMenu(MenuType),
641    SwitchAlter(String),
642    UseAbility(String),
643    Ground,
644    Pause,
645    DialogueChoice(usize),
646    CombatAction(CombatAction),
647}
648
649/// Movement directions
650#[derive(Debug, Clone, Copy)]
651pub enum Direction {
652    Up,
653    Down,
654    Left,
655    Right,
656}
657
658/// Menu types
659#[derive(Debug, Clone)]
660pub enum MenuType {
661    System,
662    Inventory,
663    Alters,
664    Map,
665}
666
667/// Combat actions
668#[derive(Debug, Clone)]
669pub enum CombatAction {
670    Attack(usize),
671    Defend,
672    UseItem(usize),
673    Switch(String),
674    Flee,
675}
676
677/// Game events for external systems
678#[derive(Debug, Clone)]
679pub enum GameEvent {
680    AlterSwitched(String),
681    AlterActivated(String),
682    ForcedSwitch(String),
683    SwitchResisted(f32),
684    SwitchFailed(String),
685    CombatStarted,
686    CombatVictory,
687    CombatDefeat,
688    CombatFled,
689    EmergencyRetreat,
690    DialogueStarted,
691    DialogueEnded,
692    GroundingSuccess,
693    GroundingFailed,
694    DissociationSpike,
695    RealityShift(RealityLayer),
696    TraumaProcessed(String),
697    ItemAcquired(String),
698    AbilityUnlocked(String),
699}
700
701/// Dialogue state
702#[derive(Debug, Clone, PartialEq)]
703pub struct DialogueState {
704    pub npc_id: String,
705    pub current_node: String,
706    pub history: Vec<String>,
707}
708
709/// Scene data
710#[derive(Debug, Clone, Default)]
711pub struct Scene {
712    pub id: String,
713    pub name: String,
714    pub safety_level: f32,
715    pub entities: Vec<SceneEntity>,
716    pub trigger_zones: Vec<TriggerZone>,
717    pub interactables: Vec<Interactable>,
718    pub player_position: (f32, f32),
719    pub active_cutscene: Option<Cutscene>,
720}
721
722impl Scene {
723    fn move_player(&mut self, direction: Direction) {
724        let speed = 5.0;
725        match direction {
726            Direction::Up => self.player_position.1 -= speed,
727            Direction::Down => self.player_position.1 += speed,
728            Direction::Left => self.player_position.0 -= speed,
729            Direction::Right => self.player_position.0 += speed,
730        }
731    }
732
733    fn check_transition(&self) -> Option<String> {
734        None
735    }
736
737    fn nearest_interactable(&self) -> Option<&Interactable> {
738        None
739    }
740}
741
742/// Scene entity
743#[derive(Debug, Clone)]
744pub struct SceneEntity {
745    pub id: String,
746    pub position: (f32, f32),
747    pub entity_type: String,
748}
749
750impl SceneEntity {
751    fn update(&mut self, _delta: Duration) {}
752}
753
754/// Trigger zone in scene
755#[derive(Debug, Clone)]
756pub struct TriggerZone {
757    pub name: String,
758    pub trigger_id: String,
759    pub bounds: ((f32, f32), (f32, f32)),
760    pub intensity: f32,
761}
762
763impl TriggerZone {
764    fn contains(&self, pos: (f32, f32)) -> bool {
765        pos.0 >= self.bounds.0.0 && pos.0 <= self.bounds.1.0 &&
766        pos.1 >= self.bounds.0.1 && pos.1 <= self.bounds.1.1
767    }
768}
769
770/// Interactable object
771#[derive(Debug, Clone)]
772pub struct Interactable {
773    pub id: String,
774    pub position: (f32, f32),
775    pub interaction_type: InteractionType,
776}
777
778/// Interaction types
779#[derive(Debug, Clone)]
780pub enum InteractionType {
781    Examine,
782    Talk(String),
783    PickUp(Item),
784    Use,
785    Enter(String),
786}
787
788/// Inventory item
789#[derive(Debug, Clone)]
790pub struct Item {
791    pub id: String,
792    pub name: String,
793    pub description: String,
794    pub item_type: ItemType,
795}
796
797/// Item types
798#[derive(Debug, Clone)]
799pub enum ItemType {
800    Key,
801    Consumable,
802    Document,
803    Memento,
804}
805
806/// Cutscene data
807#[derive(Debug, Clone)]
808pub struct Cutscene {
809    pub id: String,
810    pub duration: Duration,
811    pub elapsed: Duration,
812}
813
814impl Cutscene {
815    fn advance(&mut self, delta: Duration) {
816        self.elapsed += delta;
817    }
818
819    fn is_complete(&self) -> bool {
820        self.elapsed >= self.duration
821    }
822}
823
824/// Save data
825#[derive(Debug, Clone)]
826pub struct SaveData {
827    pub system: PluralSystem,
828    pub scene: Scene,
829    pub game_time: u64,
830    pub flags: HashMap<String, FlagValue>,
831    pub inventory: Vec<Item>,
832    pub unlocked_abilities: Vec<String>,
833    pub processed_traumas: Vec<String>,
834}
835
836/// Flag values for narrative state
837#[derive(Debug, Clone)]
838pub enum FlagValue {
839    Bool(bool),
840    Int(i32),
841    String(String),
842}
843
844// ============================================================================
845// INITIALIZATION HELPERS
846// ============================================================================
847
848fn create_host_alter() -> Alter {
849    use std::collections::HashSet;
850    Alter {
851        id: "host".to_string(),
852        name: "Host".to_string(),
853        category: AlterCategory::Council,
854        state: AlterPresenceState::Fronting,
855        anima: AnimaState::default(),
856        base_arousal: 0.0,
857        base_dominance: 0.0,
858        time_since_front: 0,
859        triggers: vec![],
860        abilities: HashSet::from(["perception".to_string()]),
861        preferred_reality: RealityLayer::Grounded,
862        memory_access: MemoryAccess::Partial(vec!["recent".to_string()]),
863    }
864}
865
866fn create_protector_alter() -> Alter {
867    use std::collections::HashSet;
868    Alter {
869        id: "protector".to_string(),
870        name: "Protector".to_string(),
871        category: AlterCategory::Council,
872        state: AlterPresenceState::Dormant,
873        anima: AnimaState::new(0.0, 0.5, 0.7),
874        base_arousal: 0.5,
875        base_dominance: 0.7,
876        time_since_front: 1000,
877        triggers: vec!["threat".to_string(), "danger".to_string()],
878        abilities: HashSet::from(["combat".to_string(), "protection".to_string()]),
879        preferred_reality: RealityLayer::Grounded,
880        memory_access: MemoryAccess::Full,
881    }
882}
883
884/// Placeholder random function
885fn rand_float() -> f32 {
886    // In real implementation, use proper RNG
887    0.5
888}
889
890// ============================================================================
891// TESTS
892// ============================================================================
893
894#[cfg(test)]
895mod tests {
896    use super::*;
897
898    #[test]
899    fn test_game_state_new() {
900        let state = GameState::new();
901        assert!(state.system.alters.contains_key("host"));
902        assert!(state.system.alters.contains_key("protector"));
903        assert_eq!(state.phase, GamePhase::Exploration);
904    }
905
906    #[test]
907    fn test_game_loop_creation() {
908        let game_loop = GameLoop::new();
909        assert!(game_loop.running);
910        assert_eq!(game_loop.target_ups, 60);
911    }
912
913    #[test]
914    fn test_alter_switch_in_game() {
915        let mut game_loop = GameLoop::new();
916        game_loop.request_alter_switch("protector");
917
918        // Check that an event was generated
919        assert!(!game_loop.state.events.is_empty());
920    }
921
922    #[test]
923    fn test_grounding_mechanic() {
924        let mut game_loop = GameLoop::new();
925        game_loop.state.system.dissociation = 0.5;
926        game_loop.state.system.stability = 0.8;
927
928        game_loop.attempt_grounding();
929
930        // Event should be generated
931        assert!(!game_loop.state.events.is_empty());
932    }
933}