sigil_parser/plurality/
perception.rs

1//! # Perception System for DAEMONIORUM
2//!
3//! Handles reality layer perception (Grounded/Fractured/Shattered),
4//! entity visibility, environmental transformation, and the interplay
5//! between alter state and perceived reality.
6
7use std::collections::HashMap;
8
9use super::runtime::{AnimaState, PluralSystem, RealityLayer, Trigger, TriggerCategory};
10
11// ============================================================================
12// PERCEPTION STATE
13// ============================================================================
14
15/// The current perception state of the player
16#[derive(Debug, Clone)]
17pub struct PerceptionState {
18    /// Current reality layer
19    pub layer: RealityLayer,
20    /// Perception intensity (0.0 = barely perceiving, 1.0 = fully immersed)
21    pub intensity: f32,
22    /// Stability of perception (0.0 = fluctuating, 1.0 = stable)
23    pub stability: f32,
24    /// Active perception modifiers
25    pub modifiers: Vec<PerceptionModifier>,
26    /// Transition state (if currently shifting)
27    pub transition: Option<PerceptionTransition>,
28    /// Entities currently visible in each layer
29    pub visible_entities: HashMap<RealityLayer, Vec<String>>,
30    /// Environmental overlays active
31    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    /// Create a new perception state at the given layer
50    pub fn at_layer(layer: RealityLayer) -> Self {
51        Self {
52            layer,
53            ..Default::default()
54        }
55    }
56
57    /// Update perception based on system state
58    pub fn update_from_system(&mut self, system: &PluralSystem) {
59        // Dissociation affects perception intensity
60        self.intensity = 0.5 + system.dissociation * 0.4;
61
62        // System stability affects perception stability
63        self.stability = system.stability * 0.7 + 0.3;
64
65        // High dissociation can force layer shifts
66        if system.dissociation > 0.7 && self.layer == RealityLayer::Grounded {
67            self.begin_transition(RealityLayer::Fractured, 0.5);
68        }
69
70        // Very high dissociation pushes to Shattered
71        if system.dissociation > 0.9 && self.layer == RealityLayer::Fractured {
72            self.begin_transition(RealityLayer::Shattered, 0.3);
73        }
74
75        // Grounding can restore normal perception
76        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        // Process any active transition
85        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    /// Begin transitioning to a new reality layer
95    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    /// Force immediate layer change
108    pub fn force_layer(&mut self, layer: RealityLayer) {
109        self.transition = None;
110        self.layer = layer;
111    }
112
113    /// Add a perception modifier
114    pub fn add_modifier(&mut self, modifier: PerceptionModifier) {
115        self.modifiers.push(modifier);
116    }
117
118    /// Remove expired modifiers
119    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    /// Get the effective perception intensity (with modifiers)
136    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    /// Check if an entity is visible at current perception
147    pub fn can_see_entity(&self, entity: &PerceivableEntity) -> bool {
148        // Check if entity is visible in current layer
149        if !entity.visible_in.contains(&self.layer) {
150            return false;
151        }
152
153        // Check intensity requirements
154        if let Some(min_intensity) = entity.min_perception_intensity {
155            if self.effective_intensity() < min_intensity {
156                return false;
157            }
158        }
159
160        // Check modifier restrictions
161        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    /// Add an environmental overlay
173    pub fn add_overlay(&mut self, overlay: EnvironmentalOverlay) {
174        // Check if this overlay replaces an existing one
175        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    /// Get active overlays for the current layer
183    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// ============================================================================
192// PERCEPTION TRANSITION
193// ============================================================================
194
195/// A transition between reality layers
196#[derive(Debug, Clone)]
197pub struct PerceptionTransition {
198    /// Layer transitioning from
199    pub from: RealityLayer,
200    /// Layer transitioning to
201    pub target: RealityLayer,
202    /// Transition progress (0.0 to 1.0)
203    pub progress: f32,
204    /// Rate of transition per tick
205    pub rate: f32,
206    /// Visual effects during transition
207    pub visual_effects: Vec<TransitionEffect>,
208}
209
210impl PerceptionTransition {
211    /// Get the current blend ratio for visual rendering
212    pub fn blend_ratio(&self) -> (f32, f32) {
213        (1.0 - self.progress, self.progress)
214    }
215}
216
217/// Visual effects during layer transition
218#[derive(Debug, Clone)]
219pub enum TransitionEffect {
220    /// Screen distortion
221    Distortion { intensity: f32 },
222    /// Color shift
223    ColorShift { hue_offset: f32, saturation: f32 },
224    /// Vignette/tunnel vision
225    Vignette { radius: f32 },
226    /// Static/noise
227    Static { amount: f32 },
228    /// Blur
229    Blur { radius: f32 },
230    /// Entity flickering
231    EntityFlicker { ids: Vec<String> },
232}
233
234// ============================================================================
235// PERCEPTION MODIFIERS
236// ============================================================================
237
238/// A modifier affecting perception
239#[derive(Debug, Clone)]
240pub struct PerceptionModifier {
241    /// Modifier ID
242    pub id: String,
243    /// Display name
244    pub name: String,
245    /// Duration in ticks (None = permanent until removed)
246    pub duration: Option<u32>,
247    /// The actual modification
248    pub modifier_type: PerceptionModifierType,
249    /// Source of the modifier
250    pub source: ModifierSource,
251}
252
253/// Types of perception modifications
254#[derive(Debug, Clone)]
255pub enum PerceptionModifierType {
256    /// Change perception intensity
257    IntensityChange(f32),
258    /// Change perception stability
259    StabilityChange(f32),
260    /// Lock to a specific layer
261    LayerLock(RealityLayer),
262    /// Force layer transition
263    ForceTransition(RealityLayer),
264    /// Block seeing a specific entity
265    BlockEntity(String),
266    /// Reveal hidden entity
267    RevealEntity(String),
268    /// Add visual overlay
269    AddOverlay(String),
270    /// Custom effect
271    Custom(String),
272}
273
274/// Source of a perception modifier
275#[derive(Debug, Clone)]
276pub enum ModifierSource {
277    /// From an alter's influence
278    Alter(String),
279    /// From an item/ability
280    Ability(String),
281    /// From environment
282    Environment,
283    /// From a trigger event
284    Trigger(String),
285    /// From combat
286    Combat,
287    /// Unknown/system
288    System,
289}
290
291// ============================================================================
292// PERCEIVABLE ENTITIES
293// ============================================================================
294
295/// An entity that exists across reality layers
296#[derive(Debug, Clone)]
297pub struct PerceivableEntity {
298    /// Entity ID
299    pub id: String,
300    /// Display name (may vary by layer)
301    pub name: String,
302    /// Entity type
303    pub entity_type: EntityType,
304    /// Which layers this entity is visible in
305    pub visible_in: Vec<RealityLayer>,
306    /// Minimum perception intensity to see
307    pub min_perception_intensity: Option<f32>,
308    /// Visual representations per layer
309    pub layer_representations: HashMap<RealityLayer, EntityRepresentation>,
310    /// Does this entity cause triggers when perceived?
311    pub perception_triggers: Vec<PerceptionTrigger>,
312    /// Symbolic meaning (for Fractured/Shattered layers)
313    pub symbolic_meaning: Option<String>,
314}
315
316impl PerceivableEntity {
317    /// Get the representation for a specific layer
318    pub fn representation_for(&self, layer: &RealityLayer) -> Option<&EntityRepresentation> {
319        self.layer_representations.get(layer)
320    }
321
322    /// Get the appropriate name for the current layer
323    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/// Types of perceivable entities
332#[derive(Debug, Clone, PartialEq)]
333pub enum EntityType {
334    /// A person/NPC
335    Character,
336    /// An object
337    Object,
338    /// A location/area
339    Location,
340    /// An environmental feature
341    Environmental,
342    /// A manifestation (trauma-based entity)
343    Manifestation,
344    /// UI/HUD element that varies by layer
345    Interface,
346    /// Abstract/symbolic entity
347    Symbol,
348}
349
350/// How an entity appears in a specific layer
351#[derive(Debug, Clone)]
352pub struct EntityRepresentation {
353    /// Override display name
354    pub display_name: Option<String>,
355    /// Visual description
356    pub description: String,
357    /// Sprite/model ID
358    pub visual_id: String,
359    /// Color tint
360    pub color_tint: Option<(f32, f32, f32, f32)>,
361    /// Scale modifier
362    pub scale: f32,
363    /// Opacity
364    pub opacity: f32,
365    /// Animation state
366    pub animation: Option<String>,
367    /// Additional visual effects
368    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/// A trigger that activates when an entity is perceived
387#[derive(Debug, Clone)]
388pub struct PerceptionTrigger {
389    /// Trigger ID to fire
390    pub trigger_id: String,
391    /// Conditions for triggering
392    pub conditions: Vec<PerceptionTriggerCondition>,
393    /// Intensity of the trigger
394    pub intensity: f32,
395}
396
397/// Conditions for perception triggers
398#[derive(Debug, Clone)]
399pub enum PerceptionTriggerCondition {
400    /// Trigger when first perceived
401    OnFirstSight,
402    /// Trigger when perception starts (enter view)
403    OnEnterView,
404    /// Trigger when perception ends (leave view)
405    OnLeaveView,
406    /// Trigger when perceived for certain duration
407    AfterDuration(u32),
408    /// Trigger at specific perception intensity
409    AtIntensity(f32),
410    /// Trigger when specific alter is fronting
411    WhenAlterFronting(String),
412    /// Trigger when at specific layer
413    AtLayer(RealityLayer),
414}
415
416// ============================================================================
417// ENVIRONMENTAL OVERLAYS
418// ============================================================================
419
420/// A visual overlay applied to the environment
421#[derive(Debug, Clone)]
422pub struct EnvironmentalOverlay {
423    /// Overlay ID
424    pub id: String,
425    /// Display name
426    pub name: String,
427    /// Which layers this overlay applies to (empty = all)
428    pub applies_to: Vec<RealityLayer>,
429    /// Overlay type
430    pub overlay_type: OverlayType,
431    /// Intensity (0.0 to 1.0)
432    pub intensity: f32,
433    /// Duration (None = permanent)
434    pub duration: Option<u32>,
435}
436
437/// Types of environmental overlays
438#[derive(Debug, Clone)]
439pub enum OverlayType {
440    /// Blood/gore overlay
441    Blood { spread: f32, color: (f32, f32, f32) },
442    /// Fire/burning overlay
443    Fire { spread: f32 },
444    /// Corruption/decay overlay
445    Corruption { spread: f32, pattern: String },
446    /// Symbolic pattern overlay
447    Symbolic { symbol: String, repeating: bool },
448    /// Memory fragment overlay
449    Memory { memory_id: String, opacity: f32 },
450    /// Text/graffiti overlay
451    Text { content: String, font: String },
452    /// Weather effect
453    Weather { weather_type: String },
454    /// Color filter
455    ColorFilter {
456        hue: f32,
457        saturation: f32,
458        brightness: f32,
459    },
460    /// Custom shader effect
461    Shader {
462        shader_id: String,
463        params: HashMap<String, f32>,
464    },
465}
466
467// ============================================================================
468// PERCEPTION MANAGER
469// ============================================================================
470
471/// Manages perception updates and entity visibility
472#[derive(Debug, Clone)]
473pub struct PerceptionManager {
474    /// Current perception state
475    pub state: PerceptionState,
476    /// All perceivable entities in the current scene
477    pub entities: HashMap<String, PerceivableEntity>,
478    /// Entity perception history (for trigger timing)
479    pub perception_history: HashMap<String, PerceptionHistory>,
480    /// Queued triggers to fire
481    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    /// Create a new perception manager
497    pub fn new() -> Self {
498        Self::default()
499    }
500
501    /// Update perception state from the plural system
502    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    /// Add an entity to the scene
510    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    /// Remove an entity from the scene
518    pub fn remove_entity(&mut self, id: &str) {
519        self.entities.remove(id);
520        self.perception_history.remove(id);
521    }
522
523    /// Update which entities are visible
524    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                // Update perception history
536                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                // Update history for non-visible
547                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    /// Process perception triggers
560    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    /// Get all currently visible entities
611    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    /// Get pending triggers and clear the queue
621    pub fn drain_triggers(&mut self) -> Vec<Trigger> {
622        std::mem::take(&mut self.pending_triggers)
623    }
624
625    /// Force a layer shift
626    pub fn force_layer_shift(&mut self, layer: RealityLayer) {
627        self.state.force_layer(layer);
628        self.update_entity_visibility();
629    }
630
631    /// Begin a gradual layer transition
632    pub fn begin_layer_transition(&mut self, target: RealityLayer, rate: f32) {
633        self.state.begin_transition(target, rate);
634    }
635}
636
637/// History of perceiving an entity
638#[derive(Debug, Clone, Default)]
639pub struct PerceptionHistory {
640    /// Is currently visible
641    pub currently_visible: bool,
642    /// Time visible (ticks)
643    pub time_visible: u32,
644    /// Number of times seen
645    pub times_seen: u32,
646}
647
648// ============================================================================
649// REALITY LAYER DEFINITIONS
650// ============================================================================
651
652/// Predefined characteristics for each reality layer
653pub struct LayerCharacteristics {
654    /// Base color grading
655    pub color_grade: ColorGrade,
656    /// Audio modifications
657    pub audio_mod: AudioModification,
658    /// Entity visibility rules
659    pub entity_rules: Vec<EntityVisibilityRule>,
660    /// Environmental modifications
661    pub environment_mod: EnvironmentModification,
662}
663
664/// Color grading for a layer
665#[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    /// Grounded layer - normal, slightly warm
677    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    /// Fractured layer - desaturated, higher contrast, cold
689    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    /// Shattered layer - heavily stylized, surreal
701    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/// Audio modifications for a layer
714#[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/// Rules for entity visibility in a layer
724#[derive(Debug, Clone)]
725pub struct EntityVisibilityRule {
726    pub entity_type: EntityType,
727    pub visible: bool,
728    pub transform: Option<EntityTransform>,
729}
730
731/// Transform applied to entities in different layers
732#[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/// Environmental modifications for a layer
741#[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// ============================================================================
751// TESTS
752// ============================================================================
753
754#[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}