1use 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#[derive(Debug)]
22pub struct GameState {
23 pub system: PluralSystem,
25 pub perception: PerceptionManager,
27 pub combat: Option<CombatState>,
29 pub scene: Scene,
31 pub game_time: u64,
33 pub real_time: Instant,
35 pub phase: GamePhase,
37 pub events: Vec<GameEvent>,
39 pub flags: HashMap<String, FlagValue>,
41 pub inventory: Vec<Item>,
43 pub unlocked_abilities: Vec<String>,
45 pub processed_traumas: Vec<String>,
47}
48
49impl GameState {
50 pub fn new() -> Self {
52 let mut system = PluralSystem::new("The Council");
53
54 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 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 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#[derive(Debug, Clone, PartialEq)]
109pub enum GamePhase {
110 Exploration,
112 Dialogue(DialogueState),
114 Combat,
116 Cutscene,
118 Headspace,
120 Paused,
122 GameOver(GameOverReason),
124}
125
126#[derive(Debug, Clone, PartialEq)]
128pub enum GameOverReason {
129 SystemCollapse,
131 PlayerChoice,
133 Ending(String),
135}
136
137pub struct GameLoop {
143 pub state: GameState,
145 pub target_ups: u32,
147 pub running: bool,
149 pub input_queue: Vec<PlayerInput>,
151}
152
153impl GameLoop {
154 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 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 std::thread::sleep(Duration::from_millis(1));
180 }
181 }
182
183 pub fn update(&mut self, delta: Duration) {
185 self.process_input();
187
188 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 self.update_plural_system(delta);
201 self.update_perception(delta);
202 self.process_events();
203
204 self.state.game_time += delta.as_millis() as u64 / 100; self.check_game_over();
209 }
210
211 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 fn update_exploration(&mut self, delta: Duration) {
254 for entity in &mut self.state.scene.entities {
256 entity.update(delta);
257 }
258
259 self.check_trigger_zones();
261
262 if let Some(enemy) = self.check_enemy_encounter() {
264 self.start_combat(enemy);
265 }
266
267 self.update_interactables();
269 }
270
271 fn update_combat(&mut self, _delta: Duration) {
273 let combat_result = if let Some(ref mut combat) = self.state.combat {
275 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 if let Some(result) = combat_result {
288 self.end_combat(Some(result));
289 }
290 }
291
292 fn update_dialogue(&mut self, _delta: Duration) {
294 }
296
297 fn update_cutscene(&mut self, delta: Duration) {
299 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 fn update_headspace(&mut self, delta: Duration) {
310 self.update_inner_world(delta);
312 }
313
314 fn update_plural_system(&mut self, delta: Duration) {
316 let delta_secs = delta.as_secs_f32();
317
318 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 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 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 self.state.system.update_blended_anima();
339
340 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 fn update_perception(&mut self, _delta: Duration) {
350 self.state.perception.update(&self.state.system);
351
352 let triggers = self.state.perception.drain_triggers();
354 for trigger in triggers {
355 self.state.system.active_triggers.push(trigger);
356 }
357
358 match self.state.perception.state.layer {
360 RealityLayer::Grounded => {
361 }
363 RealityLayer::Fractured => {
364 self.reveal_fractured_content();
366 }
367 RealityLayer::Shattered => {
368 self.handle_shattered_reality();
370 }
371 RealityLayer::Custom(_) => {}
372 }
373 }
374
375 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 fn check_game_over(&mut self) {
385 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 fn handle_movement(&mut self, direction: Direction) {
397 self.state.scene.move_player(direction);
399
400 if let Some(transition) = self.state.scene.check_transition() {
402 self.transition_scene(transition);
403 }
404 }
405
406 fn handle_interaction(&mut self) {
408 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 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 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 fn attempt_grounding(&mut self) {
468 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 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 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 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 self.handle_combat_defeat();
508 }
509 Some(CombatResult::Flee) => {
510 self.state.events.push(GameEvent::CombatFled);
511 }
512 _ => {}
513 }
514 }
515
516 fn handle_combat_defeat(&mut self) {
518 if let Some(protector) = self.find_protector() {
520 self.state.system.request_switch(&protector, 1.0, true);
521 }
522
523 self.state.system.dissociation += 0.3;
525
526 self.state.events.push(GameEvent::EmergencyRetreat);
528 }
529
530 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 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 fn handle_event(&mut self, event: GameEvent) {
562 match event {
565 GameEvent::AlterSwitched(id) => {
566 }
568 GameEvent::CombatStarted => {
569 }
571 GameEvent::TraumaProcessed(id) => {
572 self.state.processed_traumas.push(id);
573 }
574 _ => {}
575 }
576 }
577
578 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 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 None }
611
612 fn reveal_fractured_content(&mut self) {
613 }
615
616 fn handle_shattered_reality(&mut self) {
617 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 }
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#[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#[derive(Debug, Clone, Copy)]
670pub enum Direction {
671 Up,
672 Down,
673 Left,
674 Right,
675}
676
677#[derive(Debug, Clone)]
679pub enum MenuType {
680 System,
681 Inventory,
682 Alters,
683 Map,
684}
685
686#[derive(Debug, Clone)]
688pub enum CombatAction {
689 Attack(usize),
690 Defend,
691 UseItem(usize),
692 Switch(String),
693 Flee,
694}
695
696#[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#[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#[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#[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#[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#[derive(Debug, Clone)]
793pub struct Interactable {
794 pub id: String,
795 pub position: (f32, f32),
796 pub interaction_type: InteractionType,
797}
798
799#[derive(Debug, Clone)]
801pub enum InteractionType {
802 Examine,
803 Talk(String),
804 PickUp(Item),
805 Use,
806 Enter(String),
807}
808
809#[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#[derive(Debug, Clone)]
820pub enum ItemType {
821 Key,
822 Consumable,
823 Document,
824 Memento,
825}
826
827#[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#[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#[derive(Debug, Clone)]
859pub enum FlagValue {
860 Bool(bool),
861 Int(i32),
862 String(String),
863}
864
865fn 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
905fn rand_float() -> f32 {
907 0.5
909}
910
911#[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 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 assert!(!game_loop.state.events.is_empty());
953 }
954}