1use std::collections::HashMap;
8
9use super::runtime::{AnimaState, PluralSystem, RealityLayer, Trigger, TriggerCategory};
10
11#[derive(Debug, Clone)]
17pub struct PerceptionState {
18 pub layer: RealityLayer,
20 pub intensity: f32,
22 pub stability: f32,
24 pub modifiers: Vec<PerceptionModifier>,
26 pub transition: Option<PerceptionTransition>,
28 pub visible_entities: HashMap<RealityLayer, Vec<String>>,
30 pub overlays: Vec<EnvironmentalOverlay>,
32}
33
34impl Default for PerceptionState {
35 fn default() -> Self {
36 Self {
37 layer: RealityLayer::Grounded,
38 intensity: 0.5,
39 stability: 0.8,
40 modifiers: Vec::new(),
41 transition: None,
42 visible_entities: HashMap::new(),
43 overlays: Vec::new(),
44 }
45 }
46}
47
48impl PerceptionState {
49 pub fn at_layer(layer: RealityLayer) -> Self {
51 Self {
52 layer,
53 ..Default::default()
54 }
55 }
56
57 pub fn update_from_system(&mut self, system: &PluralSystem) {
59 self.intensity = 0.5 + system.dissociation * 0.4;
61
62 self.stability = system.stability * 0.7 + 0.3;
64
65 if system.dissociation > 0.7 && self.layer == RealityLayer::Grounded {
67 self.begin_transition(RealityLayer::Fractured, 0.5);
68 }
69
70 if system.dissociation > 0.9 && self.layer == RealityLayer::Fractured {
72 self.begin_transition(RealityLayer::Shattered, 0.3);
73 }
74
75 if system.stability > 0.8 && system.dissociation < 0.3 {
77 if self.layer == RealityLayer::Shattered {
78 self.begin_transition(RealityLayer::Fractured, 0.7);
79 } else if self.layer == RealityLayer::Fractured {
80 self.begin_transition(RealityLayer::Grounded, 0.5);
81 }
82 }
83
84 if let Some(ref mut transition) = self.transition {
86 transition.progress += transition.rate;
87 if transition.progress >= 1.0 {
88 self.layer = transition.target.clone();
89 self.transition = None;
90 }
91 }
92 }
93
94 pub fn begin_transition(&mut self, target: RealityLayer, rate: f32) {
96 if self.layer != target && self.transition.is_none() {
97 self.transition = Some(PerceptionTransition {
98 from: self.layer.clone(),
99 target,
100 progress: 0.0,
101 rate,
102 visual_effects: Vec::new(),
103 });
104 }
105 }
106
107 pub fn force_layer(&mut self, layer: RealityLayer) {
109 self.transition = None;
110 self.layer = layer;
111 }
112
113 pub fn add_modifier(&mut self, modifier: PerceptionModifier) {
115 self.modifiers.push(modifier);
116 }
117
118 pub fn tick_modifiers(&mut self) {
120 self.modifiers.retain(|m| {
121 if let Some(duration) = m.duration {
122 duration > 0
123 } else {
124 true
125 }
126 });
127
128 for modifier in &mut self.modifiers {
129 if let Some(ref mut duration) = modifier.duration {
130 *duration = duration.saturating_sub(1);
131 }
132 }
133 }
134
135 pub fn effective_intensity(&self) -> f32 {
137 let mut intensity = self.intensity;
138 for modifier in &self.modifiers {
139 if let PerceptionModifierType::IntensityChange(delta) = modifier.modifier_type {
140 intensity += delta;
141 }
142 }
143 intensity.clamp(0.0, 1.0)
144 }
145
146 pub fn can_see_entity(&self, entity: &PerceivableEntity) -> bool {
148 if !entity.visible_in.contains(&self.layer) {
150 return false;
151 }
152
153 if let Some(min_intensity) = entity.min_perception_intensity {
155 if self.effective_intensity() < min_intensity {
156 return false;
157 }
158 }
159
160 for modifier in &self.modifiers {
162 if let PerceptionModifierType::BlockEntity(ref id) = modifier.modifier_type {
163 if id == &entity.id {
164 return false;
165 }
166 }
167 }
168
169 true
170 }
171
172 pub fn add_overlay(&mut self, overlay: EnvironmentalOverlay) {
174 if let Some(existing) = self.overlays.iter_mut().find(|o| o.id == overlay.id) {
176 *existing = overlay;
177 } else {
178 self.overlays.push(overlay);
179 }
180 }
181
182 pub fn active_overlays(&self) -> Vec<&EnvironmentalOverlay> {
184 self.overlays
185 .iter()
186 .filter(|o| o.applies_to.contains(&self.layer) || o.applies_to.is_empty())
187 .collect()
188 }
189}
190
191#[derive(Debug, Clone)]
197pub struct PerceptionTransition {
198 pub from: RealityLayer,
200 pub target: RealityLayer,
202 pub progress: f32,
204 pub rate: f32,
206 pub visual_effects: Vec<TransitionEffect>,
208}
209
210impl PerceptionTransition {
211 pub fn blend_ratio(&self) -> (f32, f32) {
213 (1.0 - self.progress, self.progress)
214 }
215}
216
217#[derive(Debug, Clone)]
219pub enum TransitionEffect {
220 Distortion { intensity: f32 },
222 ColorShift { hue_offset: f32, saturation: f32 },
224 Vignette { radius: f32 },
226 Static { amount: f32 },
228 Blur { radius: f32 },
230 EntityFlicker { ids: Vec<String> },
232}
233
234#[derive(Debug, Clone)]
240pub struct PerceptionModifier {
241 pub id: String,
243 pub name: String,
245 pub duration: Option<u32>,
247 pub modifier_type: PerceptionModifierType,
249 pub source: ModifierSource,
251}
252
253#[derive(Debug, Clone)]
255pub enum PerceptionModifierType {
256 IntensityChange(f32),
258 StabilityChange(f32),
260 LayerLock(RealityLayer),
262 ForceTransition(RealityLayer),
264 BlockEntity(String),
266 RevealEntity(String),
268 AddOverlay(String),
270 Custom(String),
272}
273
274#[derive(Debug, Clone)]
276pub enum ModifierSource {
277 Alter(String),
279 Ability(String),
281 Environment,
283 Trigger(String),
285 Combat,
287 System,
289}
290
291#[derive(Debug, Clone)]
297pub struct PerceivableEntity {
298 pub id: String,
300 pub name: String,
302 pub entity_type: EntityType,
304 pub visible_in: Vec<RealityLayer>,
306 pub min_perception_intensity: Option<f32>,
308 pub layer_representations: HashMap<RealityLayer, EntityRepresentation>,
310 pub perception_triggers: Vec<PerceptionTrigger>,
312 pub symbolic_meaning: Option<String>,
314}
315
316impl PerceivableEntity {
317 pub fn representation_for(&self, layer: &RealityLayer) -> Option<&EntityRepresentation> {
319 self.layer_representations.get(layer)
320 }
321
322 pub fn name_for_layer(&self, layer: &RealityLayer) -> &str {
324 self.layer_representations
325 .get(layer)
326 .and_then(|r| r.display_name.as_deref())
327 .unwrap_or(&self.name)
328 }
329}
330
331#[derive(Debug, Clone, PartialEq)]
333pub enum EntityType {
334 Character,
336 Object,
338 Location,
340 Environmental,
342 Manifestation,
344 Interface,
346 Symbol,
348}
349
350#[derive(Debug, Clone)]
352pub struct EntityRepresentation {
353 pub display_name: Option<String>,
355 pub description: String,
357 pub visual_id: String,
359 pub color_tint: Option<(f32, f32, f32, f32)>,
361 pub scale: f32,
363 pub opacity: f32,
365 pub animation: Option<String>,
367 pub effects: Vec<String>,
369}
370
371impl Default for EntityRepresentation {
372 fn default() -> Self {
373 Self {
374 display_name: None,
375 description: String::new(),
376 visual_id: String::new(),
377 color_tint: None,
378 scale: 1.0,
379 opacity: 1.0,
380 animation: None,
381 effects: Vec::new(),
382 }
383 }
384}
385
386#[derive(Debug, Clone)]
388pub struct PerceptionTrigger {
389 pub trigger_id: String,
391 pub conditions: Vec<PerceptionTriggerCondition>,
393 pub intensity: f32,
395}
396
397#[derive(Debug, Clone)]
399pub enum PerceptionTriggerCondition {
400 OnFirstSight,
402 OnEnterView,
404 OnLeaveView,
406 AfterDuration(u32),
408 AtIntensity(f32),
410 WhenAlterFronting(String),
412 AtLayer(RealityLayer),
414}
415
416#[derive(Debug, Clone)]
422pub struct EnvironmentalOverlay {
423 pub id: String,
425 pub name: String,
427 pub applies_to: Vec<RealityLayer>,
429 pub overlay_type: OverlayType,
431 pub intensity: f32,
433 pub duration: Option<u32>,
435}
436
437#[derive(Debug, Clone)]
439pub enum OverlayType {
440 Blood { spread: f32, color: (f32, f32, f32) },
442 Fire { spread: f32 },
444 Corruption { spread: f32, pattern: String },
446 Symbolic { symbol: String, repeating: bool },
448 Memory { memory_id: String, opacity: f32 },
450 Text { content: String, font: String },
452 Weather { weather_type: String },
454 ColorFilter {
456 hue: f32,
457 saturation: f32,
458 brightness: f32,
459 },
460 Shader {
462 shader_id: String,
463 params: HashMap<String, f32>,
464 },
465}
466
467#[derive(Debug, Clone)]
473pub struct PerceptionManager {
474 pub state: PerceptionState,
476 pub entities: HashMap<String, PerceivableEntity>,
478 pub perception_history: HashMap<String, PerceptionHistory>,
480 pub pending_triggers: Vec<Trigger>,
482}
483
484impl Default for PerceptionManager {
485 fn default() -> Self {
486 Self {
487 state: PerceptionState::default(),
488 entities: HashMap::new(),
489 perception_history: HashMap::new(),
490 pending_triggers: Vec::new(),
491 }
492 }
493}
494
495impl PerceptionManager {
496 pub fn new() -> Self {
498 Self::default()
499 }
500
501 pub fn update(&mut self, system: &PluralSystem) {
503 self.state.update_from_system(system);
504 self.update_entity_visibility();
505 self.process_perception_triggers(system);
506 self.state.tick_modifiers();
507 }
508
509 pub fn add_entity(&mut self, entity: PerceivableEntity) {
511 let id = entity.id.clone();
512 self.entities.insert(id.clone(), entity);
513 self.perception_history
514 .insert(id, PerceptionHistory::default());
515 }
516
517 pub fn remove_entity(&mut self, id: &str) {
519 self.entities.remove(id);
520 self.perception_history.remove(id);
521 }
522
523 fn update_entity_visibility(&mut self) {
525 let mut visible = HashMap::new();
526
527 for (id, entity) in &self.entities {
528 if self.state.can_see_entity(entity) {
529 let layer = self.state.layer.clone();
530 visible
531 .entry(layer)
532 .or_insert_with(Vec::new)
533 .push(id.clone());
534
535 if let Some(history) = self.perception_history.get_mut(id) {
537 if !history.currently_visible {
538 history.currently_visible = true;
539 history.time_visible = 0;
540 history.times_seen += 1;
541 } else {
542 history.time_visible += 1;
543 }
544 }
545 } else {
546 if let Some(history) = self.perception_history.get_mut(id) {
548 if history.currently_visible {
549 history.currently_visible = false;
550 history.time_visible = 0;
551 }
552 }
553 }
554 }
555
556 self.state.visible_entities = visible;
557 }
558
559 fn process_perception_triggers(&mut self, system: &PluralSystem) {
561 for (id, entity) in &self.entities {
562 let history = match self.perception_history.get(id) {
563 Some(h) => h,
564 None => continue,
565 };
566
567 for trigger_def in &entity.perception_triggers {
568 let should_trigger = trigger_def.conditions.iter().all(|cond| match cond {
569 PerceptionTriggerCondition::OnFirstSight => {
570 history.times_seen == 1 && history.time_visible == 0
571 }
572 PerceptionTriggerCondition::OnEnterView => {
573 history.currently_visible && history.time_visible == 0
574 }
575 PerceptionTriggerCondition::OnLeaveView => {
576 !history.currently_visible && history.time_visible == 0
577 }
578 PerceptionTriggerCondition::AfterDuration(dur) => {
579 history.currently_visible && history.time_visible >= *dur
580 }
581 PerceptionTriggerCondition::AtIntensity(int) => {
582 self.state.effective_intensity() >= *int
583 }
584 PerceptionTriggerCondition::WhenAlterFronting(alter) => {
585 match &system.fronting {
586 super::runtime::FrontingState::Single(id) => id == alter,
587 super::runtime::FrontingState::Blended(ids) => ids.contains(alter),
588 _ => false,
589 }
590 }
591 PerceptionTriggerCondition::AtLayer(layer) => &self.state.layer == layer,
592 });
593
594 if should_trigger {
595 self.pending_triggers.push(Trigger {
596 id: trigger_def.trigger_id.clone(),
597 name: format!("Perception: {}", entity.name),
598 category: TriggerCategory::Environmental,
599 intensity: trigger_def.intensity,
600 context: HashMap::from([
601 ("entity_id".to_string(), id.clone()),
602 ("layer".to_string(), format!("{:?}", self.state.layer)),
603 ]),
604 });
605 }
606 }
607 }
608 }
609
610 pub fn visible_entities(&self) -> Vec<&PerceivableEntity> {
612 let layer = &self.state.layer;
613 self.state
614 .visible_entities
615 .get(layer)
616 .map(|ids| ids.iter().filter_map(|id| self.entities.get(id)).collect())
617 .unwrap_or_default()
618 }
619
620 pub fn drain_triggers(&mut self) -> Vec<Trigger> {
622 std::mem::take(&mut self.pending_triggers)
623 }
624
625 pub fn force_layer_shift(&mut self, layer: RealityLayer) {
627 self.state.force_layer(layer);
628 self.update_entity_visibility();
629 }
630
631 pub fn begin_layer_transition(&mut self, target: RealityLayer, rate: f32) {
633 self.state.begin_transition(target, rate);
634 }
635}
636
637#[derive(Debug, Clone, Default)]
639pub struct PerceptionHistory {
640 pub currently_visible: bool,
642 pub time_visible: u32,
644 pub times_seen: u32,
646}
647
648pub struct LayerCharacteristics {
654 pub color_grade: ColorGrade,
656 pub audio_mod: AudioModification,
658 pub entity_rules: Vec<EntityVisibilityRule>,
660 pub environment_mod: EnvironmentModification,
662}
663
664#[derive(Debug, Clone)]
666pub struct ColorGrade {
667 pub saturation: f32,
668 pub contrast: f32,
669 pub brightness: f32,
670 pub hue_shift: f32,
671 pub vignette: f32,
672 pub grain: f32,
673}
674
675impl ColorGrade {
676 pub fn grounded() -> Self {
678 Self {
679 saturation: 1.0,
680 contrast: 1.0,
681 brightness: 1.0,
682 hue_shift: 0.0,
683 vignette: 0.1,
684 grain: 0.0,
685 }
686 }
687
688 pub fn fractured() -> Self {
690 Self {
691 saturation: 0.6,
692 contrast: 1.3,
693 brightness: 0.9,
694 hue_shift: -15.0,
695 vignette: 0.3,
696 grain: 0.1,
697 }
698 }
699
700 pub fn shattered() -> Self {
702 Self {
703 saturation: 0.3,
704 contrast: 1.5,
705 brightness: 0.7,
706 hue_shift: 30.0,
707 vignette: 0.5,
708 grain: 0.3,
709 }
710 }
711}
712
713#[derive(Debug, Clone)]
715pub struct AudioModification {
716 pub reverb: f32,
717 pub low_pass_filter: f32,
718 pub pitch_variation: f32,
719 pub ambient_volume: f32,
720 pub distortion: f32,
721}
722
723#[derive(Debug, Clone)]
725pub struct EntityVisibilityRule {
726 pub entity_type: EntityType,
727 pub visible: bool,
728 pub transform: Option<EntityTransform>,
729}
730
731#[derive(Debug, Clone)]
733pub struct EntityTransform {
734 pub scale: f32,
735 pub opacity: f32,
736 pub color_shift: Option<(f32, f32, f32)>,
737 pub effect: Option<String>,
738}
739
740#[derive(Debug, Clone)]
742pub struct EnvironmentModification {
743 pub fog_density: f32,
744 pub shadow_intensity: f32,
745 pub ambient_light: f32,
746 pub weather_override: Option<String>,
747 pub geometry_distortion: f32,
748}
749
750#[cfg(test)]
755mod tests {
756 use super::*;
757
758 #[test]
759 fn test_perception_state_default() {
760 let state = PerceptionState::default();
761 assert_eq!(state.layer, RealityLayer::Grounded);
762 assert!((state.intensity - 0.5).abs() < 0.01);
763 }
764
765 #[test]
766 fn test_layer_transition() {
767 let mut state = PerceptionState::default();
768 state.begin_transition(RealityLayer::Fractured, 0.5);
769
770 assert!(state.transition.is_some());
771 let transition = state.transition.as_ref().unwrap();
772 assert_eq!(transition.target, RealityLayer::Fractured);
773 }
774
775 #[test]
776 fn test_entity_visibility() {
777 let state = PerceptionState::at_layer(RealityLayer::Grounded);
778
779 let visible_entity = PerceivableEntity {
780 id: "test1".to_string(),
781 name: "Test Entity".to_string(),
782 entity_type: EntityType::Character,
783 visible_in: vec![RealityLayer::Grounded, RealityLayer::Fractured],
784 min_perception_intensity: None,
785 layer_representations: HashMap::new(),
786 perception_triggers: Vec::new(),
787 symbolic_meaning: None,
788 };
789
790 let invisible_entity = PerceivableEntity {
791 id: "test2".to_string(),
792 name: "Fractured Only".to_string(),
793 entity_type: EntityType::Manifestation,
794 visible_in: vec![RealityLayer::Fractured],
795 min_perception_intensity: None,
796 layer_representations: HashMap::new(),
797 perception_triggers: Vec::new(),
798 symbolic_meaning: Some("Trauma manifestation".to_string()),
799 };
800
801 assert!(state.can_see_entity(&visible_entity));
802 assert!(!state.can_see_entity(&invisible_entity));
803 }
804
805 #[test]
806 fn test_perception_manager() {
807 let mut manager = PerceptionManager::new();
808
809 let entity = PerceivableEntity {
810 id: "church".to_string(),
811 name: "Church".to_string(),
812 entity_type: EntityType::Location,
813 visible_in: vec![RealityLayer::Grounded, RealityLayer::Fractured],
814 min_perception_intensity: None,
815 layer_representations: HashMap::from([
816 (
817 RealityLayer::Grounded,
818 EntityRepresentation {
819 description: "A peaceful church".to_string(),
820 visual_id: "church_normal".to_string(),
821 ..Default::default()
822 },
823 ),
824 (
825 RealityLayer::Fractured,
826 EntityRepresentation {
827 description: "The church walls bleed".to_string(),
828 visual_id: "church_fractured".to_string(),
829 color_tint: Some((0.8, 0.2, 0.2, 1.0)),
830 ..Default::default()
831 },
832 ),
833 ]),
834 perception_triggers: Vec::new(),
835 symbolic_meaning: Some("Sanctuary lost to corruption".to_string()),
836 };
837
838 manager.add_entity(entity);
839
840 let system = PluralSystem::default();
841 manager.update(&system);
842
843 let visible = manager.visible_entities();
844 assert_eq!(visible.len(), 1);
845 assert_eq!(visible[0].name, "Church");
846 }
847}