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 { hue: f32, saturation: f32, brightness: f32 },
456 Shader { shader_id: String, params: HashMap<String, f32> },
458}
459
460#[derive(Debug, Clone)]
466pub struct PerceptionManager {
467 pub state: PerceptionState,
469 pub entities: HashMap<String, PerceivableEntity>,
471 pub perception_history: HashMap<String, PerceptionHistory>,
473 pub pending_triggers: Vec<Trigger>,
475}
476
477impl Default for PerceptionManager {
478 fn default() -> Self {
479 Self {
480 state: PerceptionState::default(),
481 entities: HashMap::new(),
482 perception_history: HashMap::new(),
483 pending_triggers: Vec::new(),
484 }
485 }
486}
487
488impl PerceptionManager {
489 pub fn new() -> Self {
491 Self::default()
492 }
493
494 pub fn update(&mut self, system: &PluralSystem) {
496 self.state.update_from_system(system);
497 self.update_entity_visibility();
498 self.process_perception_triggers(system);
499 self.state.tick_modifiers();
500 }
501
502 pub fn add_entity(&mut self, entity: PerceivableEntity) {
504 let id = entity.id.clone();
505 self.entities.insert(id.clone(), entity);
506 self.perception_history.insert(id, PerceptionHistory::default());
507 }
508
509 pub fn remove_entity(&mut self, id: &str) {
511 self.entities.remove(id);
512 self.perception_history.remove(id);
513 }
514
515 fn update_entity_visibility(&mut self) {
517 let mut visible = HashMap::new();
518
519 for (id, entity) in &self.entities {
520 if self.state.can_see_entity(entity) {
521 let layer = self.state.layer.clone();
522 visible.entry(layer).or_insert_with(Vec::new).push(id.clone());
523
524 if let Some(history) = self.perception_history.get_mut(id) {
526 if !history.currently_visible {
527 history.currently_visible = true;
528 history.time_visible = 0;
529 history.times_seen += 1;
530 } else {
531 history.time_visible += 1;
532 }
533 }
534 } else {
535 if let Some(history) = self.perception_history.get_mut(id) {
537 if history.currently_visible {
538 history.currently_visible = false;
539 history.time_visible = 0;
540 }
541 }
542 }
543 }
544
545 self.state.visible_entities = visible;
546 }
547
548 fn process_perception_triggers(&mut self, system: &PluralSystem) {
550 for (id, entity) in &self.entities {
551 let history = match self.perception_history.get(id) {
552 Some(h) => h,
553 None => continue,
554 };
555
556 for trigger_def in &entity.perception_triggers {
557 let should_trigger = trigger_def.conditions.iter().all(|cond| {
558 match cond {
559 PerceptionTriggerCondition::OnFirstSight => {
560 history.times_seen == 1 && history.time_visible == 0
561 }
562 PerceptionTriggerCondition::OnEnterView => {
563 history.currently_visible && history.time_visible == 0
564 }
565 PerceptionTriggerCondition::OnLeaveView => {
566 !history.currently_visible && history.time_visible == 0
567 }
568 PerceptionTriggerCondition::AfterDuration(dur) => {
569 history.currently_visible && history.time_visible >= *dur
570 }
571 PerceptionTriggerCondition::AtIntensity(int) => {
572 self.state.effective_intensity() >= *int
573 }
574 PerceptionTriggerCondition::WhenAlterFronting(alter) => {
575 match &system.fronting {
576 super::runtime::FrontingState::Single(id) => id == alter,
577 super::runtime::FrontingState::Blended(ids) => ids.contains(alter),
578 _ => false,
579 }
580 }
581 PerceptionTriggerCondition::AtLayer(layer) => {
582 &self.state.layer == layer
583 }
584 }
585 });
586
587 if should_trigger {
588 self.pending_triggers.push(Trigger {
589 id: trigger_def.trigger_id.clone(),
590 name: format!("Perception: {}", entity.name),
591 category: TriggerCategory::Environmental,
592 intensity: trigger_def.intensity,
593 context: HashMap::from([
594 ("entity_id".to_string(), id.clone()),
595 ("layer".to_string(), format!("{:?}", self.state.layer)),
596 ]),
597 });
598 }
599 }
600 }
601 }
602
603 pub fn visible_entities(&self) -> Vec<&PerceivableEntity> {
605 let layer = &self.state.layer;
606 self.state
607 .visible_entities
608 .get(layer)
609 .map(|ids| {
610 ids.iter()
611 .filter_map(|id| self.entities.get(id))
612 .collect()
613 })
614 .unwrap_or_default()
615 }
616
617 pub fn drain_triggers(&mut self) -> Vec<Trigger> {
619 std::mem::take(&mut self.pending_triggers)
620 }
621
622 pub fn force_layer_shift(&mut self, layer: RealityLayer) {
624 self.state.force_layer(layer);
625 self.update_entity_visibility();
626 }
627
628 pub fn begin_layer_transition(&mut self, target: RealityLayer, rate: f32) {
630 self.state.begin_transition(target, rate);
631 }
632}
633
634#[derive(Debug, Clone, Default)]
636pub struct PerceptionHistory {
637 pub currently_visible: bool,
639 pub time_visible: u32,
641 pub times_seen: u32,
643}
644
645pub struct LayerCharacteristics {
651 pub color_grade: ColorGrade,
653 pub audio_mod: AudioModification,
655 pub entity_rules: Vec<EntityVisibilityRule>,
657 pub environment_mod: EnvironmentModification,
659}
660
661#[derive(Debug, Clone)]
663pub struct ColorGrade {
664 pub saturation: f32,
665 pub contrast: f32,
666 pub brightness: f32,
667 pub hue_shift: f32,
668 pub vignette: f32,
669 pub grain: f32,
670}
671
672impl ColorGrade {
673 pub fn grounded() -> Self {
675 Self {
676 saturation: 1.0,
677 contrast: 1.0,
678 brightness: 1.0,
679 hue_shift: 0.0,
680 vignette: 0.1,
681 grain: 0.0,
682 }
683 }
684
685 pub fn fractured() -> Self {
687 Self {
688 saturation: 0.6,
689 contrast: 1.3,
690 brightness: 0.9,
691 hue_shift: -15.0,
692 vignette: 0.3,
693 grain: 0.1,
694 }
695 }
696
697 pub fn shattered() -> Self {
699 Self {
700 saturation: 0.3,
701 contrast: 1.5,
702 brightness: 0.7,
703 hue_shift: 30.0,
704 vignette: 0.5,
705 grain: 0.3,
706 }
707 }
708}
709
710#[derive(Debug, Clone)]
712pub struct AudioModification {
713 pub reverb: f32,
714 pub low_pass_filter: f32,
715 pub pitch_variation: f32,
716 pub ambient_volume: f32,
717 pub distortion: f32,
718}
719
720#[derive(Debug, Clone)]
722pub struct EntityVisibilityRule {
723 pub entity_type: EntityType,
724 pub visible: bool,
725 pub transform: Option<EntityTransform>,
726}
727
728#[derive(Debug, Clone)]
730pub struct EntityTransform {
731 pub scale: f32,
732 pub opacity: f32,
733 pub color_shift: Option<(f32, f32, f32)>,
734 pub effect: Option<String>,
735}
736
737#[derive(Debug, Clone)]
739pub struct EnvironmentModification {
740 pub fog_density: f32,
741 pub shadow_intensity: f32,
742 pub ambient_light: f32,
743 pub weather_override: Option<String>,
744 pub geometry_distortion: f32,
745}
746
747#[cfg(test)]
752mod tests {
753 use super::*;
754
755 #[test]
756 fn test_perception_state_default() {
757 let state = PerceptionState::default();
758 assert_eq!(state.layer, RealityLayer::Grounded);
759 assert!((state.intensity - 0.5).abs() < 0.01);
760 }
761
762 #[test]
763 fn test_layer_transition() {
764 let mut state = PerceptionState::default();
765 state.begin_transition(RealityLayer::Fractured, 0.5);
766
767 assert!(state.transition.is_some());
768 let transition = state.transition.as_ref().unwrap();
769 assert_eq!(transition.target, RealityLayer::Fractured);
770 }
771
772 #[test]
773 fn test_entity_visibility() {
774 let state = PerceptionState::at_layer(RealityLayer::Grounded);
775
776 let visible_entity = PerceivableEntity {
777 id: "test1".to_string(),
778 name: "Test Entity".to_string(),
779 entity_type: EntityType::Character,
780 visible_in: vec![RealityLayer::Grounded, RealityLayer::Fractured],
781 min_perception_intensity: None,
782 layer_representations: HashMap::new(),
783 perception_triggers: Vec::new(),
784 symbolic_meaning: None,
785 };
786
787 let invisible_entity = PerceivableEntity {
788 id: "test2".to_string(),
789 name: "Fractured Only".to_string(),
790 entity_type: EntityType::Manifestation,
791 visible_in: vec![RealityLayer::Fractured],
792 min_perception_intensity: None,
793 layer_representations: HashMap::new(),
794 perception_triggers: Vec::new(),
795 symbolic_meaning: Some("Trauma manifestation".to_string()),
796 };
797
798 assert!(state.can_see_entity(&visible_entity));
799 assert!(!state.can_see_entity(&invisible_entity));
800 }
801
802 #[test]
803 fn test_perception_manager() {
804 let mut manager = PerceptionManager::new();
805
806 let entity = PerceivableEntity {
807 id: "church".to_string(),
808 name: "Church".to_string(),
809 entity_type: EntityType::Location,
810 visible_in: vec![RealityLayer::Grounded, RealityLayer::Fractured],
811 min_perception_intensity: None,
812 layer_representations: HashMap::from([
813 (RealityLayer::Grounded, EntityRepresentation {
814 description: "A peaceful church".to_string(),
815 visual_id: "church_normal".to_string(),
816 ..Default::default()
817 }),
818 (RealityLayer::Fractured, EntityRepresentation {
819 description: "The church walls bleed".to_string(),
820 visual_id: "church_fractured".to_string(),
821 color_tint: Some((0.8, 0.2, 0.2, 1.0)),
822 ..Default::default()
823 }),
824 ]),
825 perception_triggers: Vec::new(),
826 symbolic_meaning: Some("Sanctuary lost to corruption".to_string()),
827 };
828
829 manager.add_entity(entity);
830
831 let system = PluralSystem::default();
832 manager.update(&system);
833
834 let visible = manager.visible_entities();
835 assert_eq!(visible.len(), 1);
836 assert_eq!(visible[0].name, "Church");
837 }
838}