1use crate::context::apply_context_effects;
7use crate::entity::Entity;
8use crate::enums::{AlertTrigger, Direction, Emotion, HexacoPath, LifeDomain, LifeStage, StatePath};
9use crate::memory::{
10 apply_memory_consolidation, create_memory_from_event, MemoryEntry, MemoryLayer, MemoryLayers,
11};
12use crate::processor::{
13 advance_state, apply_developmental_effects, apply_interpreted_event_to_state,
14 compute_trust_modulation_factor, interpret_event, regress_state,
15 reverse_interpreted_event_from_state, InterpretedEvent,
16};
17use crate::relationship::{TrustContext, TrustDecision};
18use crate::simulation::{RegressionQuality, Simulation, TimestampedEvent};
19use crate::state::{
20 apply_formative_modifiers, effective_base_at, BaseShiftRecord, IndividualState,
21 StateInterpreter,
22};
23use crate::types::{Alert, Duration, EntityId, Timestamp};
24use std::collections::HashMap;
25
26pub struct EntityQueryHandle<'a> {
32 simulation: &'a Simulation,
33 entity_id: EntityId,
34}
35
36impl<'a> EntityQueryHandle<'a> {
37 pub(crate) fn new(simulation: &'a Simulation, entity_id: EntityId) -> Self {
39 EntityQueryHandle {
40 simulation,
41 entity_id,
42 }
43 }
44
45 #[must_use]
47 pub fn entity_id(&self) -> &EntityId {
48 &self.entity_id
49 }
50
51 #[must_use]
53 pub fn anchor_timestamp(&self) -> Option<Timestamp> {
54 self.simulation
55 .get_anchored_entity(&self.entity_id)
56 .map(|a| a.anchor_timestamp())
57 }
58
59 #[must_use]
75 pub fn state_at(&self, timestamp: Timestamp) -> ComputedState {
76 let anchored = self
77 .simulation
78 .get_anchored_entity(&self.entity_id)
79 .expect("EntityQueryHandle created for non-existent entity - use Simulation::entity() to check existence");
80 let anchor_timestamp = anchored.anchor_timestamp();
81 let entity = anchored.entity();
82
83 let mut state = entity.individual_state().clone();
85 let species = entity.species().clone();
86
87 if timestamp == anchor_timestamp {
89 let age_at_timestamp = self.compute_age_at_timestamp(entity, timestamp);
90 let life_stage =
91 LifeStage::from_age_years_for_species(&species, age_at_timestamp.as_years() as f64);
92
93 let pad_bounds = entity.context().compute_pad_bounds();
94 state
95 .mood_mut()
96 .valence_mut()
97 .set_bounds(pad_bounds.valence_min, pad_bounds.valence_max);
98 state
99 .mood_mut()
100 .arousal_mut()
101 .set_bounds(pad_bounds.arousal_min, pad_bounds.arousal_max);
102 state
103 .mood_mut()
104 .dominance_mut()
105 .set_bounds(pad_bounds.dominance_min, pad_bounds.dominance_max);
106
107 let interpreter = StateInterpreter::from_state(&state);
108 return ComputedState {
109 individual_state: state,
110 age_at_timestamp,
111 life_stage,
112 regression_quality: RegressionQuality::Exact,
113 alerts: std::cell::OnceCell::new(),
114 interpretations: interpreter.interpretations().clone(),
115 summary: interpreter.summary().to_string(),
116 delta_summary: None,
117 };
118 }
119
120 let is_forward = timestamp > anchor_timestamp;
122
123 let events = self.get_sorted_events_for_range(anchor_timestamp, timestamp, is_forward);
127
128 let regression_quality = if is_forward {
130 RegressionQuality::Exact
131 } else {
132 self.determine_regression_quality(&events)
133 };
134
135 let interpreted_events: Vec<InterpretedEvent> = events
138 .iter()
139 .map(|te| interpret_event(te.event(), entity))
140 .collect();
141
142 let base_shift_records: Vec<BaseShiftRecord> =
145 collect_base_shift_records(&events, entity, timestamp, is_forward);
146
147 let compute_age_at = |ts: Timestamp| -> Duration {
149 if let Some(birth_date) = entity.birth_date() {
150 if ts >= birth_date {
151 ts - birth_date
152 } else {
153 Duration::zero()
154 }
155 } else {
156 entity.age()
158 }
159 };
160
161 let mut computed_memories = MemoryLayers::new();
163
164 if is_forward {
165 let mut cursor = anchor_timestamp;
168
169 for (te, interpreted) in events.iter().zip(interpreted_events.iter()) {
170 let delta = te.timestamp() - cursor;
172 state = advance_state(state, delta);
173
174 let age_at_event = compute_age_at(te.timestamp());
177 let age_days = age_at_event.as_days();
178 let dev_factor =
179 apply_developmental_effects(entity, te.event(), 1.0, age_days, te.timestamp());
180
181 let trustor_propensity = state.disposition().trustor_propensity_effective();
185 let trust_factor = self.compute_trust_factor_for_event(
186 entity,
187 te.event(),
188 te.timestamp(),
189 trustor_propensity,
190 );
191
192 let combined_factor = dev_factor * trust_factor;
194
195 let scaled_interpreted = interpreted.scaled_by(combined_factor);
197
198 state = apply_interpreted_event_to_state(state, &scaled_interpreted);
200
201 let memory = create_memory_from_event(
204 &scaled_interpreted,
205 &state,
206 age_at_event,
207 Some(&self.entity_id),
208 );
209 computed_memories.add(MemoryLayer::Immediate, memory);
210
211 cursor = te.timestamp();
213 }
214
215 let remaining = timestamp - cursor;
217 state = advance_state(state, remaining);
218 } else {
219 let mut cursor = anchor_timestamp;
222
223 for i in (0..events.len()).rev() {
226 let te = &events[i];
227 let interpreted = &interpreted_events[i];
228
229 let delta = cursor - te.timestamp();
231 state = regress_state(state, delta);
232
233 let age_at_event = compute_age_at(te.timestamp());
236 let age_days = age_at_event.as_days();
237 let dev_factor =
238 apply_developmental_effects(entity, te.event(), 1.0, age_days, te.timestamp());
239
240 let trustor_propensity = state.disposition().trustor_propensity_effective();
242 let trust_factor = self.compute_trust_factor_for_event(
243 entity,
244 te.event(),
245 te.timestamp(),
246 trustor_propensity,
247 );
248
249 let combined_factor = dev_factor * trust_factor;
251
252 let scaled_interpreted = interpreted.scaled_by(combined_factor);
254
255 state = reverse_interpreted_event_from_state(state, &scaled_interpreted);
257 cursor = te.timestamp();
259 }
260
261 let remaining = cursor - timestamp;
263 state = regress_state(state, remaining);
264 }
265
266 let total_duration = if is_forward {
273 timestamp - anchor_timestamp
274 } else {
275 anchor_timestamp - timestamp
276 };
277 let relationship_quality = estimate_relationship_quality(entity);
278 let age_at_timestamp = self.compute_age_at_timestamp(entity, timestamp);
279 let life_stage =
280 LifeStage::from_age_years_for_species(&species, age_at_timestamp.as_years() as f64);
281 state = apply_context_effects(
282 state,
283 entity.context(),
284 relationship_quality,
285 total_duration,
286 life_stage,
287 age_at_timestamp.as_years() as f64,
288 timestamp,
289 );
290
291 let merged_memories = if is_forward {
297 merge_memory_layers(entity.memories(), &computed_memories)
298 } else {
299 entity.memories().clone()
300 };
301 state = apply_memory_consolidation(state, &merged_memories, total_duration);
302
303 state = apply_base_shifts_to_state(state, &base_shift_records, timestamp);
306
307 let pad_bounds = entity.context().compute_pad_bounds();
309 state
310 .mood_mut()
311 .valence_mut()
312 .set_bounds(pad_bounds.valence_min, pad_bounds.valence_max);
313 state
314 .mood_mut()
315 .arousal_mut()
316 .set_bounds(pad_bounds.arousal_min, pad_bounds.arousal_max);
317 state
318 .mood_mut()
319 .dominance_mut()
320 .set_bounds(pad_bounds.dominance_min, pad_bounds.dominance_max);
321
322 let baseline_state = entity.individual_state();
323 let interpreter = StateInterpreter::from_state_with_baseline(&state, baseline_state);
324 ComputedState {
325 individual_state: state,
326 age_at_timestamp,
327 life_stage,
328 regression_quality,
329 alerts: std::cell::OnceCell::new(),
330 interpretations: interpreter.interpretations().clone(),
331 summary: interpreter.summary().to_string(),
332 delta_summary: interpreter.delta_summary().map(|s| s.to_string()),
333 }
334 }
335
336 fn get_sorted_events_for_range(
348 &self,
349 anchor: Timestamp,
350 target: Timestamp,
351 is_forward: bool,
352 ) -> Vec<&'a TimestampedEvent> {
353 let mut events: Vec<_> = self
354 .simulation
355 .events_for(&self.entity_id)
356 .into_iter()
357 .filter(|te| {
358 let ts = te.timestamp();
359 if is_forward {
360 ts > anchor && ts <= target
362 } else {
363 ts > target && ts <= anchor
365 }
366 })
367 .collect();
368
369 events.sort_by_key(|te| te.timestamp());
370 events
371 }
372
373 fn determine_regression_quality(&self, events: &[&TimestampedEvent]) -> RegressionQuality {
378 for te in events {
379 let event = te.event();
380 let spec = event.spec();
381
382 if spec.impact.acquired_capability > 0.0 {
384 return RegressionQuality::Approximate;
385 }
386
387 }
388
389 RegressionQuality::Exact
390 }
391
392 fn compute_age_at_timestamp(
400 &self,
401 entity: &crate::entity::Entity,
402 timestamp: Timestamp,
403 ) -> Duration {
404 if let Some(birth_date) = entity.birth_date() {
406 if timestamp >= birth_date {
407 return timestamp - birth_date;
408 } else {
409 return Duration::zero();
411 }
412 }
413
414 entity.age()
417 }
418
419 fn compute_trust_factor_for_event(
437 &self,
438 entity: &Entity,
439 event: &crate::event::Event,
440 event_timestamp: Timestamp,
441 trustor_propensity: f32,
442 ) -> f64 {
443 let Some(source_id) = event.source() else {
445 return 1.0;
446 };
447
448 let relationships = self.simulation.relationships_for(&self.entity_id);
450 let relevant_rel = relationships.iter().find(|tr| {
451 tr.formed_timestamp() <= event_timestamp && tr.involves(source_id)
453 });
454
455 let Some(timestamped_rel) = relevant_rel else {
456 return 1.0;
458 };
459
460 let direction = if timestamped_rel.entity_a() == &self.entity_id {
462 Direction::AToB
463 } else {
464 Direction::BToA
465 };
466
467 let context = self.build_trust_context(entity, event);
468 let life_domain = event
469 .payload()
470 .life_domain()
471 .unwrap_or(LifeDomain::Relationship);
472 compute_trust_modulation_factor(
473 event,
474 Some(timestamped_rel.relationship()),
475 direction,
476 trustor_propensity,
477 &context,
478 life_domain,
479 )
480 }
481
482 fn build_trust_context(&self, entity: &Entity, event: &crate::event::Event) -> TrustContext {
483 let ecology = entity.context();
484 let macrosystem = ecology.macrosystem();
485 let exosystem = ecology.exosystem();
486 let historical = ecology.chronosystem().historical_period();
487
488 let mut social_norms = 1.0 - macrosystem.cultural_stress;
489 let institutional_safeguards = macrosystem.institutional_structure.rule_of_law
490 * (1.0 - macrosystem.institutional_structure.corruption_level);
491 let institutional_support = exosystem.institutional_support;
492 let cultural_expectations = (historical.institutional_trust * 0.6
493 + (1.0 - macrosystem.cultural_stress) * 0.4)
494 .clamp(0.0, 1.0);
495 let mut time_pressure = 0.5;
496
497 if let Some(context_id) = event.microsystem_context() {
498 if let Some(micro) = ecology.get_microsystem(context_id) {
499 let warmth = micro.warmth();
500 let hostility = micro.hostility();
501 social_norms += (warmth - hostility) * 0.2;
502 time_pressure = ((micro.interaction_frequency() + micro.interaction_complexity())
503 / 2.0)
504 .clamp(0.0, 1.0);
505 }
506 }
507
508 TrustContext::new()
509 .with_social_norms(social_norms.clamp(0.0, 1.0) as f32)
510 .with_institutional_safeguards(institutional_safeguards.clamp(0.0, 1.0) as f32)
511 .with_time_pressure(time_pressure as f32)
512 .with_institutional_support(institutional_support.clamp(0.0, 1.0) as f32)
513 .with_cultural_expectations(cultural_expectations as f32)
514 }
515
516 #[must_use]
523 pub fn trust_decision_for(
524 &self,
525 trustee: &EntityId,
526 timestamp: Timestamp,
527 context: &TrustContext,
528 life_domain: LifeDomain,
529 stakes: Option<crate::enums::ActionStakes>,
530 ) -> Option<TrustDecision> {
531 let relationships = self.simulation.relationships_for(&self.entity_id);
532 let relevant = relationships
533 .iter()
534 .find(|tr| tr.formed_timestamp() <= timestamp && tr.involves(trustee))?;
535
536 let direction = if relevant.entity_a() == &self.entity_id {
537 Direction::AToB
538 } else {
539 Direction::BToA
540 };
541
542 let trustor_propensity = self
543 .state_at(timestamp)
544 .individual_state()
545 .disposition()
546 .trustor_propensity_effective();
547
548 Some(
549 relevant
550 .relationship()
551 .compute_trust_decision(
552 direction,
553 trustor_propensity,
554 context,
555 life_domain,
556 stakes,
557 ),
558 )
559 }
560
561 #[must_use]
577 pub fn memories_at(&self, timestamp: Timestamp) -> Vec<MemoryEntry> {
578 let Some(anchored) = self.simulation.get_anchored_entity(&self.entity_id) else {
579 return Vec::new();
580 };
581
582 let entity = anchored.entity();
583 let anchor_timestamp = anchored.anchor_timestamp();
584 let anchor_age = entity.age();
585
586 let age_at_timestamp = if timestamp >= anchor_timestamp {
588 let elapsed = timestamp - anchor_timestamp;
589 anchor_age + elapsed
590 } else {
591 let elapsed = anchor_timestamp - timestamp;
592 if elapsed < anchor_age {
593 anchor_age - elapsed
594 } else {
595 Duration::zero()
596 }
597 };
598
599 entity
602 .memories()
603 .all_memories()
604 .filter(|memory: &&MemoryEntry| memory.timestamp() <= age_at_timestamp)
605 .cloned()
606 .collect()
607 }
608}
609
610fn estimate_relationship_quality(entity: &Entity) -> f64 {
611 let attached_count = entity
612 .relationship_slots()
613 .iter()
614 .filter(|slot| slot.is_attached())
615 .count();
616
617 if attached_count > 0 {
618 0.5 + 0.1 * (attached_count as f64).min(5.0)
619 } else {
620 0.3
621 }
622}
623
624fn merge_memory_layers(anchor: &MemoryLayers, computed: &MemoryLayers) -> MemoryLayers {
631 let mut merged = MemoryLayers::new();
632
633 for memory in anchor.immediate() {
635 merged.add(MemoryLayer::Immediate, memory.clone());
636 }
637 for memory in anchor.short_term() {
638 merged.add(MemoryLayer::ShortTerm, memory.clone());
639 }
640 for memory in anchor.long_term() {
641 merged.add(MemoryLayer::LongTerm, memory.clone());
642 }
643 for memory in anchor.legacy() {
644 merged.add(MemoryLayer::Legacy, memory.clone());
645 }
646
647 for memory in computed.immediate() {
649 merged.add(MemoryLayer::Immediate, memory.clone());
650 }
651 for memory in computed.short_term() {
652 merged.add(MemoryLayer::ShortTerm, memory.clone());
653 }
654 for memory in computed.long_term() {
655 merged.add(MemoryLayer::LongTerm, memory.clone());
656 }
657 for memory in computed.legacy() {
658 merged.add(MemoryLayer::Legacy, memory.clone());
659 }
660
661 merged
662}
663
664#[derive(Debug)]
671pub struct ComputedState {
672 pub individual_state: IndividualState,
674 pub age_at_timestamp: Duration,
676 pub life_stage: LifeStage,
678 regression_quality: RegressionQuality,
680 alerts: std::cell::OnceCell<Vec<Alert>>,
682 pub interpretations: HashMap<String, String>,
684 pub summary: String,
686 pub delta_summary: Option<String>,
688}
689
690impl ComputedState {
691 #[must_use]
693 pub fn individual_state(&self) -> &IndividualState {
694 &self.individual_state
695 }
696
697 #[must_use]
699 pub fn emotion_membership(&self) -> HashMap<Emotion, f64> {
700 self.individual_state.mood().emotion_membership()
701 }
702
703 #[must_use]
705 pub fn age_at_timestamp(&self) -> Duration {
706 self.age_at_timestamp
707 }
708
709 #[must_use]
711 pub fn life_stage(&self) -> LifeStage {
712 self.life_stage
713 }
714
715 #[must_use]
721 pub fn regression_quality(&self) -> RegressionQuality {
722 self.regression_quality
723 }
724
725 #[must_use]
732 pub fn alerts(&self) -> Vec<Alert> {
733 self.alerts.get_or_init(|| self.compute_alerts()).clone()
734 }
735
736 fn compute_alerts(&self) -> Vec<Alert> {
738 let state = &self.individual_state;
739 let social = state.social_cognition();
740 let mental = state.mental_health();
741 let timestamp = self.age_at_timestamp;
742 let mut alerts = Vec::new();
743
744 let desire = mental.compute_suicidal_desire(social);
745 if desire >= 0.8 {
746 alerts.push(Alert::critical(
747 AlertTrigger::threshold(
748 StatePath::MentalHealth(crate::enums::MentalHealthPath::SuicidalDesire),
749 desire as f64,
750 ),
751 timestamp,
752 "High suicidal desire detected",
753 ));
754 } else if desire >= 0.5 {
755 alerts.push(Alert::warning(
756 AlertTrigger::threshold(
757 StatePath::MentalHealth(crate::enums::MentalHealthPath::SuicidalDesire),
758 desire as f64,
759 ),
760 timestamp,
761 "Elevated suicidal desire detected",
762 ));
763 }
764
765 let attempt_risk = mental.compute_attempt_risk(social);
766 if attempt_risk >= 0.7 {
767 alerts.push(Alert::critical(
768 AlertTrigger::threshold(
769 StatePath::MentalHealth(crate::enums::MentalHealthPath::AttemptRisk),
770 attempt_risk as f64,
771 ),
772 timestamp,
773 "High attempt risk detected",
774 ));
775 } else if attempt_risk >= 0.4 {
776 alerts.push(Alert::warning(
777 AlertTrigger::threshold(
778 StatePath::MentalHealth(crate::enums::MentalHealthPath::AttemptRisk),
779 attempt_risk as f64,
780 ),
781 timestamp,
782 "Elevated attempt risk detected",
783 ));
784 }
785
786 let stress = state.needs().stress_effective();
787 let fatigue = state.needs().fatigue_effective();
788 let impulse_control = state.disposition().impulse_control_effective();
789 if stress > 0.8 && fatigue > 0.7 && impulse_control < 0.3 {
790 alerts.push(Alert::warning(
791 AlertTrigger::spiral(crate::enums::SpiralType::Stress),
792 timestamp,
793 "Stress spiral indicators detected",
794 ));
795 }
796
797 let depression = mental.depression_effective();
798 let loneliness = social.loneliness_effective();
799 if depression > 0.7 && loneliness > 0.7 {
800 alerts.push(Alert::warning(
801 AlertTrigger::spiral(crate::enums::SpiralType::Depression),
802 timestamp,
803 "Depression-loneliness spiral indicators detected",
804 ));
805 }
806
807 alerts
808 }
809
810 #[must_use]
824 pub fn get_effective(&self, path: StatePath) -> f64 {
825 use crate::enums::{
826 DispositionPath, HexacoPath, MentalHealthPath, MoodPath, NeedsPath,
827 PersonCharacteristicsPath, SocialCognitionPath,
828 };
829
830 let state = &self.individual_state;
831
832 let value: f32 = match path {
833 StatePath::Hexaco(p) => match p {
834 HexacoPath::HonestyHumility => state.hexaco().honesty_humility(),
835 HexacoPath::Neuroticism => state.hexaco().neuroticism(),
836 HexacoPath::Extraversion => state.hexaco().extraversion(),
837 HexacoPath::Agreeableness => state.hexaco().agreeableness(),
838 HexacoPath::Conscientiousness => state.hexaco().conscientiousness(),
839 HexacoPath::Openness => state.hexaco().openness(),
840 },
841 StatePath::Mood(p) => match p {
842 MoodPath::Valence => state.mood().valence_effective(),
843 MoodPath::Arousal => state.mood().arousal_effective(),
844 MoodPath::Dominance => state.mood().dominance_effective(),
845 },
846 StatePath::Needs(p) => match p {
847 NeedsPath::Stress => state.needs().stress_effective(),
848 NeedsPath::Fatigue => state.needs().fatigue_effective(),
849 NeedsPath::Purpose => state.needs().purpose_effective(),
850 },
851 StatePath::SocialCognition(p) => match p {
852 SocialCognitionPath::Loneliness => state.social_cognition().loneliness_effective(),
853 SocialCognitionPath::PerceivedReciprocalCaring => state
854 .social_cognition()
855 .perceived_reciprocal_caring_effective(),
856 SocialCognitionPath::PerceivedLiability => {
857 state.social_cognition().perceived_liability_effective()
858 }
859 SocialCognitionPath::SelfHate => state.social_cognition().self_hate_effective(),
860 SocialCognitionPath::PerceivedCompetence => {
861 state.social_cognition().perceived_competence_effective()
862 }
863 },
864 StatePath::MentalHealth(p) => match p {
865 MentalHealthPath::Depression => state.mental_health().depression_effective(),
866 MentalHealthPath::AcquiredCapability => {
867 state.mental_health().acquired_capability_effective()
868 }
869 MentalHealthPath::InterpersonalHopelessness => {
870 state.mental_health().interpersonal_hopelessness_effective()
871 }
872 MentalHealthPath::ThwartedBelongingness => state.compute_thwarted_belongingness(),
873 MentalHealthPath::PerceivedBurdensomeness => {
874 state.compute_perceived_burdensomeness()
875 }
876 MentalHealthPath::SuicidalDesire => state.compute_suicidal_desire(),
877 MentalHealthPath::AttemptRisk => state.compute_attempt_risk(),
878 MentalHealthPath::SelfWorth => state.mental_health().self_worth_effective(),
879 MentalHealthPath::Hopelessness => state.mental_health().hopelessness_effective(),
880 },
881 StatePath::Disposition(p) => match p {
882 DispositionPath::Empathy => state.disposition().empathy_effective(),
883 DispositionPath::Aggression => state.disposition().aggression_effective(),
884 DispositionPath::Grievance => state.disposition().grievance_effective(),
885 DispositionPath::ImpulseControl => state.disposition().impulse_control_effective(),
886 DispositionPath::Reactance => state.disposition().reactance_effective(),
887 DispositionPath::TrustorPropensity => {
888 state.disposition().trustor_propensity_effective()
889 }
890 },
891 StatePath::PersonCharacteristics(p) => match p {
892 PersonCharacteristicsPath::SocialCapital => {
893 state.person_characteristics().social_capital_effective()
894 }
895 PersonCharacteristicsPath::CognitiveAbility => {
896 state.person_characteristics().cognitive_ability_effective()
897 }
898 PersonCharacteristicsPath::EmotionalRegulationAssets => state
899 .person_characteristics()
900 .emotional_regulation_assets_effective(),
901 PersonCharacteristicsPath::MaterialSecurity => {
902 state.person_characteristics().material_security_effective()
903 }
904 PersonCharacteristicsPath::ExperienceDiversity => state
905 .person_characteristics()
906 .experience_diversity_effective(),
907 PersonCharacteristicsPath::BaselineMotivation => state
908 .person_characteristics()
909 .baseline_motivation_effective(),
910 PersonCharacteristicsPath::PersistenceTendency => state
911 .person_characteristics()
912 .persistence_tendency_effective(),
913 PersonCharacteristicsPath::CuriosityTendency => state
914 .person_characteristics()
915 .curiosity_tendency_effective(),
916 PersonCharacteristicsPath::Resource => state.person_characteristics().resource(),
918 PersonCharacteristicsPath::Force => state.person_characteristics().force(),
919 },
920 };
921
922 f64::from(value)
923 }
924}
925
926impl Clone for ComputedState {
927 fn clone(&self) -> Self {
928 ComputedState {
929 individual_state: self.individual_state.clone(),
930 age_at_timestamp: self.age_at_timestamp,
931 life_stage: self.life_stage,
932 regression_quality: self.regression_quality,
933 alerts: match self.alerts.get() {
934 Some(v) => {
935 let cell = std::cell::OnceCell::new();
936 let _ = cell.set(v.clone());
937 cell
938 }
939 None => std::cell::OnceCell::new(),
940 },
941 interpretations: self.interpretations.clone(),
942 summary: self.summary.clone(),
943 delta_summary: self.delta_summary.clone(),
944 }
945 }
946}
947
948fn collect_base_shift_records(
953 events: &[&TimestampedEvent],
954 entity: &Entity,
955 query_timestamp: Timestamp,
956 is_forward: bool,
957) -> Vec<BaseShiftRecord> {
958 if !is_forward {
960 return Vec::new();
961 }
962
963 let reference_timestamp = entity
964 .birth_date()
965 .unwrap_or_else(|| Timestamp::from_ymd_hms(1970, 1, 1, 0, 0, 0));
966
967 let mut records = Vec::new();
968 let mut cumulative_positive: HashMap<HexacoPath, f32> = HashMap::new();
969 let mut cumulative_negative: HashMap<HexacoPath, f32> = HashMap::new();
970
971 for te in events {
972 let event = te.event();
973
974 if !event.has_base_shifts() {
976 continue;
977 }
978
979 if te.timestamp() > query_timestamp {
981 continue;
982 }
983
984 let age_at_event = if let Some(birth_date) = entity.birth_date() {
986 if te.timestamp() >= birth_date {
987 (te.timestamp() - birth_date).as_years() as u16
988 } else {
989 0
990 }
991 } else {
992 entity.age().as_years() as u16
993 };
994
995 let event_duration = if te.timestamp() >= reference_timestamp {
997 te.timestamp() - reference_timestamp
998 } else {
999 Duration::zero()
1000 };
1001
1002 for (trait_path, raw_amount) in event.base_shifts() {
1004 let existing = if *raw_amount > 0.0 {
1006 *cumulative_positive.get(trait_path).unwrap_or(&0.0)
1007 } else {
1008 *cumulative_negative.get(trait_path).unwrap_or(&0.0)
1009 };
1010
1011 let modified = apply_formative_modifiers(
1013 *raw_amount,
1014 *trait_path,
1015 age_at_event,
1016 existing,
1017 entity.species(),
1018 );
1019
1020 if modified.abs() < f32::EPSILON {
1022 continue;
1023 }
1024
1025 let record = BaseShiftRecord::new(event_duration, *trait_path, modified);
1027
1028 if modified > 0.0 {
1030 *cumulative_positive.entry(*trait_path).or_insert(0.0) += modified.abs();
1031 } else {
1032 *cumulative_negative.entry(*trait_path).or_insert(0.0) += modified.abs();
1033 }
1034
1035 records.push(record);
1036 }
1037 }
1038
1039 records
1040}
1041
1042fn apply_base_shifts_to_state(
1047 mut state: IndividualState,
1048 shift_records: &[BaseShiftRecord],
1049 query_timestamp: Timestamp,
1050) -> IndividualState {
1051 if shift_records.is_empty() {
1053 return state;
1054 }
1055
1056 let reference = Timestamp::from_ymd_hms(1970, 1, 1, 0, 0, 0);
1059 let query_duration = if query_timestamp >= reference {
1060 query_timestamp - reference
1061 } else {
1062 Duration::zero()
1063 };
1064
1065 for trait_path in HexacoPath::all() {
1067 let trait_records: Vec<_> = shift_records
1069 .iter()
1070 .filter(|r| r.trait_path() == trait_path)
1071 .cloned()
1072 .collect();
1073
1074 if trait_records.is_empty() {
1076 continue;
1077 }
1078
1079 let current_base = match trait_path {
1081 HexacoPath::Openness => state.hexaco().openness(),
1082 HexacoPath::Conscientiousness => state.hexaco().conscientiousness(),
1083 HexacoPath::Extraversion => state.hexaco().extraversion(),
1084 HexacoPath::Agreeableness => state.hexaco().agreeableness(),
1085 HexacoPath::Neuroticism => state.hexaco().neuroticism(),
1086 HexacoPath::HonestyHumility => state.hexaco().honesty_humility(),
1087 };
1088
1089 let effective = effective_base_at(current_base, &trait_records, query_duration);
1091
1092 match trait_path {
1094 HexacoPath::Openness => state.hexaco_mut().set_openness(effective),
1095 HexacoPath::Conscientiousness => state.hexaco_mut().set_conscientiousness(effective),
1096 HexacoPath::Extraversion => state.hexaco_mut().set_extraversion(effective),
1097 HexacoPath::Agreeableness => state.hexaco_mut().set_agreeableness(effective),
1098 HexacoPath::Neuroticism => state.hexaco_mut().set_neuroticism(effective),
1099 HexacoPath::HonestyHumility => state.hexaco_mut().set_honesty_humility(effective),
1100 }
1101 }
1102
1103 state
1104}
1105
1106#[cfg(test)]
1107mod tests {
1108 use super::*;
1109 use crate::entity::EntityBuilder;
1110 use crate::enums::{EventType, RelationshipSchema, Species};
1111 use crate::event::EventBuilder;
1112 use crate::memory::{MemoryEntry, MemoryLayer, MemoryLayers};
1113
1114 fn create_simulation() -> Simulation {
1115 let reference = Timestamp::from_ymd_hms(2024, 1, 1, 0, 0, 0);
1116 Simulation::new(reference)
1117 }
1118
1119 fn create_human(id: &str) -> crate::entity::Entity {
1120 EntityBuilder::new()
1121 .id(id)
1122 .species(Species::Human)
1123 .age(crate::types::Duration::years(30))
1124 .build()
1125 .unwrap()
1126 }
1127
1128 #[test]
1129 fn entity_query_handle_entity_id() {
1130 let sim = create_simulation();
1131 let handle = EntityQueryHandle::new(&sim, EntityId::new("test").unwrap());
1132 assert_eq!(handle.entity_id().as_str(), "test");
1133 }
1134
1135 #[test]
1136 fn entity_query_handle_anchor_timestamp() {
1137 let mut sim = create_simulation();
1138 let entity = create_human("person_001");
1139 let anchor = Timestamp::from_ymd_hms(2024, 1, 15, 12, 0, 0);
1140 sim.add_entity(entity, anchor);
1141
1142 let handle = sim.entity(&EntityId::new("person_001").unwrap()).unwrap();
1143 assert_eq!(handle.anchor_timestamp(), Some(anchor));
1144 }
1145
1146 #[test]
1147 fn state_at_anchor_returns_original_state() {
1148 let mut sim = create_simulation();
1149 let entity = create_human("person_001");
1150 let anchor = sim.reference_date();
1151 sim.add_entity(entity, anchor);
1152
1153 let handle = sim.entity(&EntityId::new("person_001").unwrap()).unwrap();
1154 let _state = handle.state_at(anchor);
1155 }
1157
1158 #[test]
1159 fn state_at_forward_applies_decay() {
1160 let mut sim = create_simulation();
1161 let mut entity = create_human("person_001");
1162
1163 entity
1165 .individual_state_mut()
1166 .mood_mut()
1167 .add_valence_delta(0.5);
1168
1169 let anchor = sim.reference_date();
1170 sim.add_entity(entity, anchor);
1171
1172 let future = anchor + Duration::weeks(1);
1174 let handle = sim.entity(&EntityId::new("person_001").unwrap()).unwrap();
1175 let computed = handle.state_at(future);
1176
1177 let valence = computed.get_effective(StatePath::Mood(crate::enums::MoodPath::Valence));
1179 assert!(valence < 0.1); }
1181
1182 #[test]
1183 fn trust_decision_helpers_wire_propensity_and_relationship() {
1184 let mut sim = create_simulation();
1185 let reference = sim.reference_date();
1186
1187 let alice = create_human("alice");
1188 let bob = create_human("bob");
1189 sim.add_entity(alice, reference);
1190 sim.add_entity(bob, reference);
1191
1192 let alice_id = EntityId::new("alice").unwrap();
1193 let bob_id = EntityId::new("bob").unwrap();
1194
1195 let formed = reference;
1196 let rel_id = sim.add_relationship(
1197 alice_id.clone(),
1198 bob_id.clone(),
1199 RelationshipSchema::Peer,
1200 formed,
1201 );
1202
1203 let support_time = reference + Duration::days(3);
1204 let support = EventBuilder::new(EventType::ReceiveSupportEmotional)
1205 .source(bob_id.clone())
1206 .target(alice_id.clone())
1207 .severity(0.7)
1208 .build()
1209 .unwrap();
1210 sim.add_event(support, support_time);
1211
1212 let context = TrustContext::new();
1213 let life_domain = LifeDomain::Relationship;
1214 let handle = sim.entity(&alice_id).unwrap();
1215 let decision_from_handle = handle
1216 .trust_decision_for(&bob_id, support_time, &context, life_domain, None)
1217 .unwrap();
1218 let decision_from_sim = sim
1219 .trust_decision_at(&alice_id, &bob_id, support_time, &context, life_domain, None)
1220 .unwrap();
1221
1222 assert_eq!(decision_from_handle, decision_from_sim);
1223 let support = decision_from_handle.support_willingness();
1224 assert!(support.is_finite());
1225 assert!((0.0..=1.0).contains(&support));
1226
1227 let rel = sim.get_relationship(&rel_id).unwrap().relationship();
1228 let direct = rel.compute_trust_decision(
1229 Direction::AToB,
1230 handle
1231 .state_at(support_time)
1232 .individual_state()
1233 .disposition()
1234 .trustor_propensity_effective(),
1235 &context,
1236 life_domain,
1237 None,
1238 );
1239 assert_eq!(decision_from_handle, direct);
1240 }
1241
1242 #[test]
1243 fn trust_decision_for_returns_none_without_relationship() {
1244 let mut sim = create_simulation();
1245 let reference = sim.reference_date();
1246
1247 let alice = create_human("alice");
1248 let bob = create_human("bob");
1249 sim.add_entity(alice, reference);
1250 sim.add_entity(bob, reference);
1251
1252 let alice_id = EntityId::new("alice").unwrap();
1253 let bob_id = EntityId::new("bob").unwrap();
1254
1255 let handle = sim.entity(&alice_id).unwrap();
1256 let decision = handle.trust_decision_for(
1257 &bob_id,
1258 reference,
1259 &TrustContext::new(),
1260 LifeDomain::Relationship,
1261 None,
1262 );
1263
1264 assert!(decision.is_none());
1265 }
1266
1267 #[test]
1268 fn trust_decision_for_uses_b_to_a_direction() {
1269 let mut sim = create_simulation();
1270 let reference = sim.reference_date();
1271
1272 let alice = create_human("alice");
1273 let bob = create_human("bob");
1274 sim.add_entity(alice, reference);
1275 sim.add_entity(bob, reference);
1276
1277 let alice_id = EntityId::new("alice").unwrap();
1278 let bob_id = EntityId::new("bob").unwrap();
1279 sim.add_relationship(
1280 alice_id.clone(),
1281 bob_id.clone(),
1282 RelationshipSchema::Peer,
1283 reference,
1284 );
1285
1286 let handle = sim.entity(&bob_id).unwrap();
1287 let decision = handle.trust_decision_for(
1288 &alice_id,
1289 reference,
1290 &TrustContext::new(),
1291 LifeDomain::Relationship,
1292 None,
1293 );
1294
1295 assert!(decision.is_some());
1296 }
1297
1298 #[test]
1299 fn merge_memory_layers_copies_anchor_and_computed_memories() {
1300 let mut anchor = MemoryLayers::new();
1301 let mut computed = MemoryLayers::new();
1302
1303 anchor.add(
1304 MemoryLayer::Immediate,
1305 MemoryEntry::new(Duration::days(1), "anchor_immediate"),
1306 );
1307 anchor.add(
1308 MemoryLayer::ShortTerm,
1309 MemoryEntry::new(Duration::days(2), "anchor_short_term"),
1310 );
1311 anchor.add(
1312 MemoryLayer::LongTerm,
1313 MemoryEntry::new(Duration::days(3), "anchor_long_term"),
1314 );
1315 anchor.add(
1316 MemoryLayer::Legacy,
1317 MemoryEntry::new(Duration::days(4), "anchor_legacy"),
1318 );
1319
1320 computed.add(
1321 MemoryLayer::Immediate,
1322 MemoryEntry::new(Duration::days(5), "computed_immediate"),
1323 );
1324 computed.add(
1325 MemoryLayer::ShortTerm,
1326 MemoryEntry::new(Duration::days(6), "computed_short_term"),
1327 );
1328 computed.add(
1329 MemoryLayer::LongTerm,
1330 MemoryEntry::new(Duration::days(7), "computed_long_term"),
1331 );
1332 computed.add(
1333 MemoryLayer::Legacy,
1334 MemoryEntry::new(Duration::days(8), "computed_legacy"),
1335 );
1336
1337 let merged = merge_memory_layers(&anchor, &computed);
1338
1339 assert_eq!(merged.immediate_count(), 2);
1340 assert_eq!(merged.short_term_count(), 2);
1341 assert_eq!(merged.long_term_count(), 2);
1342 assert_eq!(merged.legacy_count(), 2);
1343 assert_eq!(merged.total_count(), 8);
1344 assert!(merged
1345 .immediate()
1346 .iter()
1347 .any(|memory| memory.summary() == "anchor_immediate"));
1348 assert!(merged
1349 .immediate()
1350 .iter()
1351 .any(|memory| memory.summary() == "computed_immediate"));
1352 }
1353
1354 #[test]
1355 fn state_at_backward_regresses_state() {
1356 let mut sim = create_simulation();
1357 let entity = create_human("person_001");
1358 let anchor = Timestamp::from_ymd_hms(2024, 6, 1, 0, 0, 0);
1359 sim.add_entity(entity, anchor);
1360
1361 let past = Timestamp::from_ymd_hms(2024, 5, 1, 0, 0, 0);
1363 let handle = sim.entity(&EntityId::new("person_001").unwrap()).unwrap();
1364 let _state = handle.state_at(past);
1365 }
1367
1368 #[test]
1369 fn computed_state_accessors() {
1370 let mut sim = create_simulation();
1371 let entity = create_human("person_001");
1372 let anchor = sim.reference_date();
1373 sim.add_entity(entity, anchor);
1374
1375 let handle = sim.entity(&EntityId::new("person_001").unwrap()).unwrap();
1376 let state = handle.state_at(anchor);
1377
1378 let _ = state.individual_state();
1380 let _ = state.age_at_timestamp();
1381 let _ = state.life_stage();
1382 let _ = state.regression_quality();
1383 }
1384
1385 #[test]
1386 fn estimate_relationship_quality_increases_with_attachments() {
1387 use crate::types::RelationshipId;
1388
1389 let mut entity = create_human("person_001");
1390 let relationship_id = RelationshipId::new("rel_001_002").unwrap();
1391 entity.relationship_slots_mut()[0].attach_for_test(relationship_id);
1392
1393 let quality = estimate_relationship_quality(&entity);
1394 assert!(quality > 0.3);
1395 }
1396
1397 #[test]
1398 fn computed_state_alerts_lazy() {
1399 let mut sim = create_simulation();
1400 let entity = create_human("person_001");
1401 let anchor = sim.reference_date();
1402 sim.add_entity(entity, anchor);
1403
1404 let handle = sim.entity(&EntityId::new("person_001").unwrap()).unwrap();
1405 let state = handle.state_at(anchor);
1406
1407 let alerts = state.alerts();
1409 assert!(alerts.is_empty()); let alerts2 = state.alerts();
1413 assert!(alerts2.is_empty());
1414 }
1415
1416 fn computed_state_with(individual_state: IndividualState) -> ComputedState {
1417 ComputedState {
1418 individual_state,
1419 age_at_timestamp: Duration::days(1),
1420 life_stage: LifeStage::Adult,
1421 regression_quality: RegressionQuality::Exact,
1422 alerts: std::cell::OnceCell::new(),
1423 interpretations: HashMap::new(),
1424 summary: String::new(),
1425 delta_summary: None,
1426 }
1427 }
1428
1429 #[test]
1430 fn computed_state_alerts_high_risk_paths() {
1431 let social = crate::state::SocialCognition::new()
1432 .with_loneliness_base(1.0)
1433 .with_perceived_reciprocal_caring_base(0.0)
1434 .with_perceived_liability_base(1.0)
1435 .with_self_hate_base(1.0);
1436 let mental = crate::state::MentalHealth::new()
1437 .with_interpersonal_hopelessness_base(1.0)
1438 .with_acquired_capability_base(1.0)
1439 .with_depression_base(0.2);
1440 let individual_state = IndividualState::new()
1441 .with_social_cognition(social)
1442 .with_mental_health(mental);
1443
1444 let computed = computed_state_with(individual_state);
1445 let alerts = computed.alerts();
1446
1447 assert!(alerts.iter().any(|a| matches!(
1448 a.trigger(),
1449 AlertTrigger::ThresholdExceeded(StatePath::MentalHealth(crate::enums::MentalHealthPath::SuicidalDesire), _)
1450 )));
1451 assert!(alerts.iter().any(|a| matches!(
1452 a.trigger(),
1453 AlertTrigger::ThresholdExceeded(StatePath::MentalHealth(crate::enums::MentalHealthPath::AttemptRisk), _)
1454 )));
1455 }
1456
1457 #[test]
1458 fn computed_state_alerts_warning_paths() {
1459 let social = crate::state::SocialCognition::new()
1460 .with_loneliness_base(0.9)
1461 .with_perceived_reciprocal_caring_base(0.1)
1462 .with_perceived_liability_base(0.9)
1463 .with_self_hate_base(0.9);
1464 let mental = crate::state::MentalHealth::new()
1465 .with_interpersonal_hopelessness_base(0.8)
1466 .with_acquired_capability_base(0.8)
1467 .with_depression_base(0.2);
1468 let individual_state = IndividualState::new()
1469 .with_social_cognition(social)
1470 .with_mental_health(mental);
1471
1472 let computed = computed_state_with(individual_state);
1473 let alerts = computed.alerts();
1474
1475 assert!(alerts.iter().any(|a| matches!(
1476 a.trigger(),
1477 AlertTrigger::ThresholdExceeded(StatePath::MentalHealth(crate::enums::MentalHealthPath::SuicidalDesire), _)
1478 )));
1479 assert!(alerts.iter().any(|a| matches!(
1480 a.trigger(),
1481 AlertTrigger::ThresholdExceeded(StatePath::MentalHealth(crate::enums::MentalHealthPath::AttemptRisk), _)
1482 )));
1483 }
1484
1485 #[test]
1486 fn computed_state_alerts_detect_spirals() {
1487 let needs = crate::state::Needs::new()
1488 .with_stress_base(0.9)
1489 .with_fatigue_base(0.8);
1490 let disposition = crate::state::Disposition::new().with_impulse_control_base(0.2);
1491 let social = crate::state::SocialCognition::new().with_loneliness_base(0.8);
1492 let mental = crate::state::MentalHealth::new().with_depression_base(0.8);
1493 let individual_state = IndividualState::new()
1494 .with_needs(needs)
1495 .with_disposition(disposition)
1496 .with_social_cognition(social)
1497 .with_mental_health(mental);
1498
1499 let computed = computed_state_with(individual_state);
1500 let alerts = computed.alerts();
1501
1502 assert!(alerts.iter().any(|a| matches!(
1503 a.trigger(),
1504 AlertTrigger::SpiralDetected(crate::enums::SpiralType::Stress)
1505 )));
1506 assert!(alerts.iter().any(|a| matches!(
1507 a.trigger(),
1508 AlertTrigger::SpiralDetected(crate::enums::SpiralType::Depression)
1509 )));
1510 }
1511
1512 #[test]
1513 fn build_trust_context_uses_event_microsystem() {
1514 use crate::event::EventBuilder;
1515 use crate::enums::EventType;
1516
1517 let mut sim = create_simulation();
1518 let mut entity = create_human("person_001");
1519 let anchor = sim.reference_date();
1520
1521 let micro_id = crate::types::MicrosystemId::new("work_ctx").unwrap();
1522 let mut work = crate::context::WorkContext::default();
1523 work.warmth = 0.8;
1524 work.hostility = 0.1;
1525 work.interaction_profile.interaction_frequency = 0.9;
1526 work.interaction_profile.interaction_complexity = 0.9;
1527 entity
1528 .context_mut()
1529 .add_microsystem(micro_id.clone(), crate::context::Microsystem::new_work(work));
1530
1531 sim.add_entity(entity.clone(), anchor);
1532 let handle = sim.entity(&entity.id()).unwrap();
1533
1534 let event = EventBuilder::new(EventType::ReceiveSupportEmotional)
1535 .context(micro_id)
1536 .build()
1537 .unwrap();
1538
1539 let context = handle.build_trust_context(&entity, &event);
1540 assert!(context.time_pressure() > 0.5);
1541 assert!(context.social_norms() > 0.5);
1542 }
1543
1544 #[test]
1545 fn build_trust_context_missing_microsystem_keeps_defaults() {
1546 use crate::event::EventBuilder;
1547 use crate::enums::EventType;
1548
1549 let mut sim = create_simulation();
1550 let entity = create_human("person_001");
1551 let anchor = sim.reference_date();
1552 sim.add_entity(entity.clone(), anchor);
1553 let handle = sim.entity(&entity.id()).unwrap();
1554
1555 let micro_id = crate::types::MicrosystemId::new("unknown_ctx").unwrap();
1556 let event = EventBuilder::new(EventType::ReceiveSupportEmotional)
1557 .context(micro_id)
1558 .build()
1559 .unwrap();
1560
1561 let context = handle.build_trust_context(&entity, &event);
1562 assert!((context.time_pressure() - 0.5).abs() < f32::EPSILON);
1563 }
1564
1565 #[test]
1566 fn computed_state_get_effective_mood() {
1567 let mut sim = create_simulation();
1568 let entity = create_human("person_001");
1569 let anchor = sim.reference_date();
1570 sim.add_entity(entity, anchor);
1571
1572 let handle = sim.entity(&EntityId::new("person_001").unwrap()).unwrap();
1573 let state = handle.state_at(anchor);
1574
1575 let valence = state.get_effective(StatePath::Mood(crate::enums::MoodPath::Valence));
1576 assert!(valence >= -1.0 && valence <= 1.0);
1577
1578 let arousal = state.get_effective(StatePath::Mood(crate::enums::MoodPath::Arousal));
1579 assert!(arousal >= -1.0 && arousal <= 1.0);
1580
1581 let dominance = state.get_effective(StatePath::Mood(crate::enums::MoodPath::Dominance));
1582 assert!(dominance >= -1.0 && dominance <= 1.0);
1583 }
1584
1585 #[test]
1586 fn computed_state_emotion_membership_sums_to_one() {
1587 let mut sim = create_simulation();
1588 let entity = create_human("person_001");
1589 let anchor = sim.reference_date();
1590 sim.add_entity(entity, anchor);
1591
1592 let handle = sim.entity(&EntityId::new("person_001").unwrap()).unwrap();
1593 let state = handle.state_at(anchor);
1594
1595 let membership = state.emotion_membership();
1596 let total: f64 = membership.values().copied().sum();
1597 assert!((total - 1.0).abs() < 1e-6);
1598 }
1599
1600 #[test]
1601 fn computed_state_get_effective_needs() {
1602 let mut sim = create_simulation();
1603 let entity = create_human("person_001");
1604 let anchor = sim.reference_date();
1605 sim.add_entity(entity, anchor);
1606
1607 let handle = sim.entity(&EntityId::new("person_001").unwrap()).unwrap();
1608 let state = handle.state_at(anchor);
1609
1610 use crate::enums::{NeedsPath, SocialCognitionPath};
1611
1612 let loneliness =
1614 state.get_effective(StatePath::SocialCognition(SocialCognitionPath::Loneliness));
1615 assert!(loneliness >= 0.0 && loneliness <= 1.0);
1616
1617 let prc = state.get_effective(StatePath::SocialCognition(
1618 SocialCognitionPath::PerceivedReciprocalCaring,
1619 ));
1620 assert!(prc >= 0.0 && prc <= 1.0);
1621
1622 let liability = state.get_effective(StatePath::SocialCognition(
1623 SocialCognitionPath::PerceivedLiability,
1624 ));
1625 assert!(liability >= 0.0 && liability <= 1.0);
1626
1627 let self_hate =
1628 state.get_effective(StatePath::SocialCognition(SocialCognitionPath::SelfHate));
1629 assert!(self_hate >= 0.0 && self_hate <= 1.0);
1630
1631 let perceived_competence = state.get_effective(StatePath::SocialCognition(
1632 SocialCognitionPath::PerceivedCompetence,
1633 ));
1634 assert!(perceived_competence >= 0.0 && perceived_competence <= 1.0);
1635
1636 let stress = state.get_effective(StatePath::Needs(NeedsPath::Stress));
1637 assert!(stress >= 0.0 && stress <= 1.0);
1638
1639 let fatigue = state.get_effective(StatePath::Needs(NeedsPath::Fatigue));
1640 assert!(fatigue >= 0.0 && fatigue <= 1.0);
1641
1642 let purpose = state.get_effective(StatePath::Needs(NeedsPath::Purpose));
1643 assert!(purpose >= 0.0 && purpose <= 1.0);
1644 }
1645
1646 #[test]
1647 fn computed_state_get_effective_mental_health() {
1648 let mut sim = create_simulation();
1649 let entity = create_human("person_001");
1650 let anchor = sim.reference_date();
1651 sim.add_entity(entity, anchor);
1652
1653 let handle = sim.entity(&EntityId::new("person_001").unwrap()).unwrap();
1654 let state = handle.state_at(anchor);
1655
1656 use crate::enums::MentalHealthPath;
1657
1658 let depression = state.get_effective(StatePath::MentalHealth(MentalHealthPath::Depression));
1660 assert!(depression >= 0.0 && depression <= 1.0);
1661
1662 let ac = state.get_effective(StatePath::MentalHealth(
1663 MentalHealthPath::AcquiredCapability,
1664 ));
1665 assert!(ac >= 0.0 && ac <= 1.0);
1666
1667 let ih = state.get_effective(StatePath::MentalHealth(
1668 MentalHealthPath::InterpersonalHopelessness,
1669 ));
1670 assert!(ih >= 0.0 && ih <= 1.0);
1671
1672 let tb = state.get_effective(StatePath::MentalHealth(
1673 MentalHealthPath::ThwartedBelongingness,
1674 ));
1675 assert!(tb >= 0.0 && tb <= 1.0);
1676
1677 let pb = state.get_effective(StatePath::MentalHealth(
1678 MentalHealthPath::PerceivedBurdensomeness,
1679 ));
1680 assert!(pb >= 0.0 && pb <= 1.0);
1681
1682 let desire = state.get_effective(StatePath::MentalHealth(MentalHealthPath::SuicidalDesire));
1683 assert!(desire >= 0.0 && desire <= 1.0);
1684
1685 let risk = state.get_effective(StatePath::MentalHealth(MentalHealthPath::AttemptRisk));
1686 assert!(risk >= 0.0 && risk <= 1.0);
1687 }
1688
1689 #[test]
1690 fn computed_state_get_effective_hexaco() {
1691 let mut sim = create_simulation();
1692 let entity = create_human("person_001");
1693 let anchor = sim.reference_date();
1694 sim.add_entity(entity, anchor);
1695
1696 let handle = sim.entity(&EntityId::new("person_001").unwrap()).unwrap();
1697 let state = handle.state_at(anchor);
1698
1699 use crate::enums::HexacoPath;
1700
1701 let hh = state.get_effective(StatePath::Hexaco(HexacoPath::HonestyHumility));
1703 assert!(hh >= 0.0 && hh <= 1.0);
1704
1705 let n = state.get_effective(StatePath::Hexaco(HexacoPath::Neuroticism));
1706 assert!(n >= 0.0 && n <= 1.0);
1707
1708 let e = state.get_effective(StatePath::Hexaco(HexacoPath::Extraversion));
1709 assert!(e >= 0.0 && e <= 1.0);
1710
1711 let a = state.get_effective(StatePath::Hexaco(HexacoPath::Agreeableness));
1712 assert!(a >= 0.0 && a <= 1.0);
1713
1714 let c = state.get_effective(StatePath::Hexaco(HexacoPath::Conscientiousness));
1715 assert!(c >= 0.0 && c <= 1.0);
1716
1717 let o = state.get_effective(StatePath::Hexaco(HexacoPath::Openness));
1718 assert!(o >= 0.0 && o <= 1.0);
1719 }
1720
1721 #[test]
1722 fn computed_state_get_effective_disposition() {
1723 let mut sim = create_simulation();
1724 let entity = create_human("person_001");
1725 let anchor = sim.reference_date();
1726 sim.add_entity(entity, anchor);
1727
1728 let handle = sim.entity(&EntityId::new("person_001").unwrap()).unwrap();
1729 let state = handle.state_at(anchor);
1730
1731 use crate::enums::DispositionPath;
1732
1733 let empathy = state.get_effective(StatePath::Disposition(DispositionPath::Empathy));
1735 assert!(empathy >= 0.0 && empathy <= 1.0);
1736
1737 let aggression = state.get_effective(StatePath::Disposition(DispositionPath::Aggression));
1738 assert!(aggression >= 0.0 && aggression <= 1.0);
1739
1740 let grievance = state.get_effective(StatePath::Disposition(DispositionPath::Grievance));
1741 assert!(grievance >= 0.0 && grievance <= 1.0);
1742 }
1743
1744 #[test]
1745 fn computed_state_get_effective_person_characteristics() {
1746 let mut sim = create_simulation();
1747 let entity = create_human("person_001");
1748 let anchor = sim.reference_date();
1749 sim.add_entity(entity, anchor);
1750
1751 let handle = sim.entity(&EntityId::new("person_001").unwrap()).unwrap();
1752 let state = handle.state_at(anchor);
1753
1754 use crate::enums::PersonCharacteristicsPath;
1755
1756 let sc = state.get_effective(StatePath::PersonCharacteristics(
1758 PersonCharacteristicsPath::SocialCapital,
1759 ));
1760 assert!(sc >= 0.0 && sc <= 1.0);
1761
1762 let ca = state.get_effective(StatePath::PersonCharacteristics(
1763 PersonCharacteristicsPath::CognitiveAbility,
1764 ));
1765 assert!(ca >= 0.0 && ca <= 1.0);
1766
1767 let ms = state.get_effective(StatePath::PersonCharacteristics(
1768 PersonCharacteristicsPath::MaterialSecurity,
1769 ));
1770 assert!(ms >= 0.0 && ms <= 1.0);
1771 }
1772
1773 #[test]
1774 fn state_at_with_event_applies_event() {
1775 let mut sim = create_simulation();
1776 let entity = create_human("person_001");
1777 let entity_id = EntityId::new("person_001").unwrap();
1778 let anchor = sim.reference_date();
1779 sim.add_entity(entity, anchor);
1780
1781 let event = EventBuilder::new(EventType::EndRelationshipRomantic)
1783 .target(entity_id.clone())
1784 .severity(0.7)
1785 .build()
1786 .unwrap();
1787 let event_time = anchor + Duration::days(1);
1788 sim.add_event(event, event_time);
1789
1790 let query_time = anchor + Duration::days(2);
1792 let handle = sim.entity(&entity_id).unwrap();
1793 let _state = handle.state_at(query_time);
1794 }
1796
1797 #[test]
1798 fn age_at_timestamp_forward_with_birth_date() {
1799 let mut sim = create_simulation();
1800 let anchor = sim.reference_date();
1801 let birth_date = anchor - Duration::years(25);
1803 let entity = EntityBuilder::new()
1804 .id("person_001")
1805 .species(Species::Human)
1806 .birth_date(birth_date)
1807 .age(Duration::years(25))
1808 .build()
1809 .unwrap();
1810 sim.add_entity(entity, anchor);
1811
1812 let future = anchor + Duration::years(10);
1814 let handle = sim.entity(&EntityId::new("person_001").unwrap()).unwrap();
1815 let state = handle.state_at(future);
1816
1817 assert_eq!(state.age_at_timestamp().as_years(), 35);
1818 }
1819
1820 #[test]
1821 fn age_at_timestamp_forward_without_birth_date_is_constant() {
1822 let mut sim = create_simulation();
1823 let entity = EntityBuilder::new()
1824 .id("person_001")
1825 .species(Species::Human)
1826 .age(Duration::years(25))
1827 .build()
1828 .unwrap();
1829 let anchor = sim.reference_date();
1830 sim.add_entity(entity, anchor);
1831
1832 let future = anchor + Duration::years(10);
1834 let handle = sim.entity(&EntityId::new("person_001").unwrap()).unwrap();
1835 let state = handle.state_at(future);
1836
1837 assert_eq!(state.age_at_timestamp().as_years(), 25);
1838 }
1839
1840 #[test]
1841 fn age_at_timestamp_backward_with_birth_date() {
1842 let mut sim = create_simulation();
1843 let anchor = sim.reference_date();
1844 let birth_date = anchor - Duration::years(25);
1846 let entity = EntityBuilder::new()
1847 .id("person_001")
1848 .species(Species::Human)
1849 .birth_date(birth_date)
1850 .age(Duration::years(25))
1851 .build()
1852 .unwrap();
1853 sim.add_entity(entity, anchor);
1854
1855 let past = anchor - Duration::years(10);
1857 let handle = sim.entity(&EntityId::new("person_001").unwrap()).unwrap();
1858 let state = handle.state_at(past);
1859
1860 assert_eq!(state.age_at_timestamp().as_years(), 15);
1861 }
1862
1863 #[test]
1864 fn age_at_timestamp_backward_without_birth_date_is_constant() {
1865 let mut sim = create_simulation();
1866 let entity = EntityBuilder::new()
1867 .id("person_001")
1868 .species(Species::Human)
1869 .age(Duration::years(25))
1870 .build()
1871 .unwrap();
1872 let anchor = sim.reference_date();
1873 sim.add_entity(entity, anchor);
1874
1875 let past = anchor - Duration::years(10);
1877 let handle = sim.entity(&EntityId::new("person_001").unwrap()).unwrap();
1878 let state = handle.state_at(past);
1879
1880 assert_eq!(state.age_at_timestamp().as_years(), 25);
1881 }
1882
1883 #[test]
1884 fn life_stage_at_timestamp_with_birth_date() {
1885 let mut sim = create_simulation();
1886 let anchor = sim.reference_date();
1887 let birth_date = anchor - Duration::years(10);
1889 let entity = EntityBuilder::new()
1890 .id("person_001")
1891 .species(Species::Human)
1892 .birth_date(birth_date)
1893 .age(Duration::years(10))
1894 .build()
1895 .unwrap();
1896 sim.add_entity(entity, anchor);
1897
1898 let handle = sim.entity(&EntityId::new("person_001").unwrap()).unwrap();
1899
1900 let state = handle.state_at(anchor);
1902 assert_eq!(state.life_stage(), LifeStage::Child);
1903
1904 let future = anchor + Duration::years(10);
1906 let state2 = handle.state_at(future);
1907 assert_eq!(state2.life_stage(), LifeStage::YoungAdult);
1908 }
1909
1910 #[test]
1911 fn life_stage_at_timestamp_without_birth_date_is_constant() {
1912 let mut sim = create_simulation();
1913 let entity = EntityBuilder::new()
1914 .id("person_001")
1915 .species(Species::Human)
1916 .age(Duration::years(10))
1917 .build()
1918 .unwrap();
1919 let anchor = sim.reference_date();
1920 sim.add_entity(entity, anchor);
1921
1922 let handle = sim.entity(&EntityId::new("person_001").unwrap()).unwrap();
1923
1924 let state = handle.state_at(anchor);
1926 assert_eq!(state.life_stage(), LifeStage::Child);
1927
1928 let future = anchor + Duration::years(10);
1930 let state2 = handle.state_at(future);
1931 assert_eq!(state2.life_stage(), LifeStage::Child);
1932 }
1933
1934 #[test]
1935 fn regression_quality_forward_is_exact() {
1936 let mut sim = create_simulation();
1937 let entity = create_human("person_001");
1938 let anchor = sim.reference_date();
1939 sim.add_entity(entity, anchor);
1940
1941 let future = anchor + Duration::days(30);
1942 let handle = sim.entity(&EntityId::new("person_001").unwrap()).unwrap();
1943 let state = handle.state_at(future);
1944
1945 assert!(state.regression_quality().is_exact());
1946 }
1947
1948 #[test]
1949 fn computed_state_debug() {
1950 let mut sim = create_simulation();
1951 let entity = create_human("person_001");
1952 let anchor = sim.reference_date();
1953 sim.add_entity(entity, anchor);
1954
1955 let handle = sim.entity(&EntityId::new("person_001").unwrap()).unwrap();
1956 let state = handle.state_at(anchor);
1957
1958 let debug = format!("{:?}", state);
1959 assert!(debug.contains("ComputedState"));
1960 }
1961
1962 #[test]
1963 fn computed_state_clone() {
1964 let mut sim = create_simulation();
1965 let entity = create_human("person_001");
1966 let anchor = sim.reference_date();
1967 sim.add_entity(entity, anchor);
1968
1969 let handle = sim.entity(&EntityId::new("person_001").unwrap()).unwrap();
1970 let state = handle.state_at(anchor);
1971 let cloned = state.clone();
1972
1973 assert_eq!(state.age_at_timestamp(), cloned.age_at_timestamp());
1974 }
1975
1976 #[test]
1977 #[should_panic(expected = "EntityQueryHandle created for non-existent entity")]
1978 fn state_at_nonexistent_entity_panics() {
1979 let sim = create_simulation();
1980 let unknown = EntityId::new("unknown").unwrap();
1981 let handle = EntityQueryHandle::new(&sim, unknown);
1982
1983 let _state = handle.state_at(sim.reference_date());
1985 }
1986
1987 #[test]
1988 fn anchor_timestamp_nonexistent_entity() {
1989 let sim = create_simulation();
1990 let unknown = EntityId::new("unknown").unwrap();
1991 let handle = EntityQueryHandle::new(&sim, unknown);
1992
1993 assert!(handle.anchor_timestamp().is_none());
1994 }
1995
1996 #[test]
1997 fn state_at_backward_with_events() {
1998 let mut sim = create_simulation();
1999 let entity = create_human("person_001");
2000 let entity_id = EntityId::new("person_001").unwrap();
2001
2002 let anchor = Timestamp::from_ymd_hms(2024, 6, 1, 0, 0, 0);
2004 sim.add_entity(entity, anchor);
2005
2006 let event = EventBuilder::new(EventType::EndRelationshipRomantic)
2008 .target(entity_id.clone())
2009 .severity(0.7)
2010 .build()
2011 .unwrap();
2012 let event_time = Timestamp::from_ymd_hms(2024, 3, 1, 0, 0, 0);
2013 sim.add_event(event, event_time);
2014
2015 let past = Timestamp::from_ymd_hms(2024, 1, 1, 0, 0, 0);
2017 let handle = sim.entity(&entity_id).unwrap();
2018 let computed = handle.state_at(past);
2019
2020 assert!(computed.regression_quality().is_exact());
2022 }
2023
2024 #[test]
2025 fn computed_state_get_effective_mental_health_all_paths() {
2026 let mut sim = create_simulation();
2027 let entity = create_human("person_001");
2028 let anchor = sim.reference_date();
2029 sim.add_entity(entity, anchor);
2030
2031 let handle = sim.entity(&EntityId::new("person_001").unwrap()).unwrap();
2032 let state = handle.state_at(anchor);
2033
2034 use crate::enums::MentalHealthPath;
2035
2036 let self_worth = state.get_effective(StatePath::MentalHealth(MentalHealthPath::SelfWorth));
2039 assert!(self_worth >= 0.0 && self_worth <= 1.0);
2040
2041 let hopelessness =
2042 state.get_effective(StatePath::MentalHealth(MentalHealthPath::Hopelessness));
2043 assert!(hopelessness >= 0.0 && hopelessness <= 1.0);
2044 }
2045
2046 #[test]
2047 fn computed_state_get_effective_disposition_all_paths() {
2048 let mut sim = create_simulation();
2049 let entity = create_human("person_001");
2050 let anchor = sim.reference_date();
2051 sim.add_entity(entity, anchor);
2052
2053 let handle = sim.entity(&EntityId::new("person_001").unwrap()).unwrap();
2054 let state = handle.state_at(anchor);
2055
2056 use crate::enums::DispositionPath;
2057
2058 let impulse = state.get_effective(StatePath::Disposition(DispositionPath::ImpulseControl));
2060 assert!(impulse >= 0.0 && impulse <= 1.0);
2061
2062 let reactance = state.get_effective(StatePath::Disposition(DispositionPath::Reactance));
2063 assert!(reactance >= 0.0 && reactance <= 1.0);
2064
2065 let trust = state.get_effective(StatePath::Disposition(DispositionPath::TrustorPropensity));
2066 assert!(trust >= 0.0 && trust <= 1.0);
2067 }
2068
2069 #[test]
2070 fn computed_state_get_effective_person_characteristics_all_paths() {
2071 let mut sim = create_simulation();
2072 let entity = create_human("person_001");
2073 let anchor = sim.reference_date();
2074 sim.add_entity(entity, anchor);
2075
2076 let handle = sim.entity(&EntityId::new("person_001").unwrap()).unwrap();
2077 let state = handle.state_at(anchor);
2078
2079 use crate::enums::PersonCharacteristicsPath;
2080
2081 let era = state.get_effective(StatePath::PersonCharacteristics(
2083 PersonCharacteristicsPath::EmotionalRegulationAssets,
2084 ));
2085 assert!(era >= 0.0 && era <= 1.0);
2086
2087 let ed = state.get_effective(StatePath::PersonCharacteristics(
2088 PersonCharacteristicsPath::ExperienceDiversity,
2089 ));
2090 assert!(ed >= 0.0 && ed <= 1.0);
2091
2092 let bm = state.get_effective(StatePath::PersonCharacteristics(
2093 PersonCharacteristicsPath::BaselineMotivation,
2094 ));
2095 assert!(bm >= 0.0 && bm <= 1.0);
2096
2097 let pt = state.get_effective(StatePath::PersonCharacteristics(
2098 PersonCharacteristicsPath::PersistenceTendency,
2099 ));
2100 assert!(pt >= 0.0 && pt <= 1.0);
2101
2102 let ct = state.get_effective(StatePath::PersonCharacteristics(
2103 PersonCharacteristicsPath::CuriosityTendency,
2104 ));
2105 assert!(ct >= 0.0 && ct <= 1.0);
2106
2107 let _resource = state.get_effective(StatePath::PersonCharacteristics(
2109 PersonCharacteristicsPath::Resource,
2110 ));
2111
2112 let _force = state.get_effective(StatePath::PersonCharacteristics(
2113 PersonCharacteristicsPath::Force,
2114 ));
2115 }
2116
2117 #[test]
2118 fn memories_at_returns_empty_for_new_entity() {
2119 let mut sim = create_simulation();
2120 let entity = create_human("person_001");
2121 let anchor = sim.reference_date();
2122 sim.add_entity(entity, anchor);
2123
2124 let handle = sim.entity(&EntityId::new("person_001").unwrap()).unwrap();
2125 let memories = handle.memories_at(anchor);
2126
2127 assert!(memories.is_empty());
2128 }
2129
2130 #[test]
2131 fn memories_at_returns_empty_for_nonexistent_entity() {
2132 let sim = create_simulation();
2133 let unknown = EntityId::new("unknown").unwrap();
2134 let handle = EntityQueryHandle::new(&sim, unknown);
2135
2136 let memories = handle.memories_at(sim.reference_date());
2137 assert!(memories.is_empty());
2138 }
2139
2140 #[test]
2141 fn memories_at_filters_by_timestamp() {
2142 use crate::memory::MemoryTag;
2143
2144 let mut sim = create_simulation();
2145
2146 let mut entity = EntityBuilder::new()
2148 .id("person_001")
2149 .species(Species::Human)
2150 .age(Duration::years(25))
2151 .build()
2152 .unwrap();
2153
2154 entity.create_memory(
2156 "First memory at age 25",
2157 vec![],
2158 vec![MemoryTag::Personal],
2159 0.5,
2160 None,
2161 );
2162
2163 let anchor = sim.reference_date();
2164 sim.add_entity(entity, anchor);
2165
2166 let handle = sim.entity(&EntityId::new("person_001").unwrap()).unwrap();
2168 let memories_at_anchor = handle.memories_at(anchor);
2169 assert_eq!(memories_at_anchor.len(), 1);
2170
2171 let past = anchor - Duration::years(10);
2175 let memories_past = handle.memories_at(past);
2176 assert!(memories_past.is_empty());
2177 }
2178
2179 #[test]
2180 fn memories_at_forward_in_time() {
2181 use crate::memory::MemoryTag;
2183
2184 let mut sim = create_simulation();
2185
2186 let mut entity = EntityBuilder::new()
2188 .id("person_001")
2189 .species(Species::Human)
2190 .age(Duration::years(25))
2191 .build()
2192 .unwrap();
2193
2194 entity.create_memory(
2196 "Memory at age 25",
2197 vec![],
2198 vec![MemoryTag::Personal],
2199 0.5,
2200 None,
2201 );
2202
2203 let anchor = sim.reference_date();
2204 sim.add_entity(entity, anchor);
2205
2206 let future = anchor + Duration::years(10);
2209 let handle = sim.entity(&EntityId::new("person_001").unwrap()).unwrap();
2210 let memories_future = handle.memories_at(future);
2211
2212 assert_eq!(memories_future.len(), 1);
2214 }
2215
2216 #[test]
2217 fn age_computed_from_birth_date() {
2218 let mut sim = create_simulation();
2219
2220 let birth = Timestamp::from_ymd_hms(1990, 6, 15, 0, 0, 0);
2222 let entity = EntityBuilder::new()
2223 .id("person_001")
2224 .species(Species::Human)
2225 .age(crate::types::Duration::years(30))
2226 .birth_date(birth)
2227 .build()
2228 .unwrap();
2229
2230 let anchor = sim.reference_date(); sim.add_entity(entity, anchor);
2232
2233 let handle = sim.entity(&EntityId::new("person_001").unwrap()).unwrap();
2234
2235 let state = handle.state_at(anchor);
2237 let age_at_anchor = state.age_at_timestamp();
2238
2239 assert!(age_at_anchor.as_years() >= 33);
2241 assert!(age_at_anchor.as_years() <= 34);
2242
2243 let future = anchor + Duration::years(10);
2245 let future_state = handle.state_at(future);
2246 let age_at_future = future_state.age_at_timestamp();
2247
2248 assert!(age_at_future.as_years() >= 43);
2250 assert!(age_at_future.as_years() <= 44);
2251 }
2252
2253 #[test]
2254 fn age_before_birth_returns_zero() {
2255 let birth = Timestamp::from_ymd_hms(2000, 1, 1, 0, 0, 0);
2257 let entity = EntityBuilder::new()
2258 .id("person_001")
2259 .species(Species::Human)
2260 .age(crate::types::Duration::years(30))
2261 .birth_date(birth)
2262 .build()
2263 .unwrap();
2264
2265 let anchor = Timestamp::from_ymd_hms(2024, 1, 1, 0, 0, 0);
2266 let mut sim = Simulation::new(Timestamp::from_ymd_hms(2024, 1, 1, 0, 0, 0));
2267 sim.add_entity(entity, anchor);
2268
2269 let handle = sim.entity(&EntityId::new("person_001").unwrap()).unwrap();
2270
2271 let before_birth = Timestamp::from_ymd_hms(1990, 1, 1, 0, 0, 0);
2273 let state = handle.state_at(before_birth);
2274
2275 assert!(state.age_at_timestamp().is_zero());
2277 }
2278
2279 #[test]
2280 fn regression_through_trauma_is_approximate() {
2281 let mut sim = create_simulation();
2282 let entity = create_human("person_001");
2283 let entity_id = EntityId::new("person_001").unwrap();
2284
2285 let anchor = Timestamp::from_ymd_hms(2024, 6, 1, 0, 0, 0);
2287 sim.add_entity(entity, anchor);
2288
2289 let trauma_event = EventBuilder::new(EventType::ExperienceCombatMilitary)
2291 .target(entity_id.clone())
2292 .severity(0.8)
2293 .build()
2294 .unwrap();
2295 let event_time = Timestamp::from_ymd_hms(2024, 3, 1, 0, 0, 0);
2296 sim.add_event(trauma_event, event_time);
2297
2298 let past = Timestamp::from_ymd_hms(2024, 1, 1, 0, 0, 0);
2300 let handle = sim.entity(&entity_id).unwrap();
2301 let state = handle.state_at(past);
2302
2303 assert!(state.regression_quality().is_approximate());
2305 }
2306
2307 #[test]
2308 fn regression_without_trauma_is_exact() {
2309 let mut sim = create_simulation();
2310 let entity = create_human("person_001");
2311 let entity_id = EntityId::new("person_001").unwrap();
2312
2313 let anchor = Timestamp::from_ymd_hms(2024, 6, 1, 0, 0, 0);
2315 sim.add_entity(entity, anchor);
2316
2317 let social_event = EventBuilder::new(EventType::EndRelationshipRomantic)
2319 .target(entity_id.clone())
2320 .severity(0.5)
2321 .build()
2322 .unwrap();
2323 let event_time = Timestamp::from_ymd_hms(2024, 3, 1, 0, 0, 0);
2324 sim.add_event(social_event, event_time);
2325
2326 let past = Timestamp::from_ymd_hms(2024, 1, 1, 0, 0, 0);
2328 let handle = sim.entity(&entity_id).unwrap();
2329 let state = handle.state_at(past);
2330
2331 assert!(state.regression_quality().is_exact());
2333 }
2334
2335 #[test]
2336 fn estimate_relationship_quality_baseline() {
2337 let entity = create_human("person_001");
2338 let baseline = estimate_relationship_quality(&entity);
2339 assert!((baseline - 0.3).abs() < f64::EPSILON);
2340 }
2341
2342 #[test]
2343 fn computed_state_clone_with_cached_alerts() {
2344 let mut sim = create_simulation();
2345 let entity = create_human("person_001");
2346 let anchor = sim.reference_date();
2347 sim.add_entity(entity, anchor);
2348
2349 let handle = sim.entity(&EntityId::new("person_001").unwrap()).unwrap();
2350 let state = handle.state_at(anchor);
2351
2352 let alerts1 = state.alerts();
2354 assert!(alerts1.is_empty());
2355
2356 let cloned = state.clone();
2358
2359 let alerts2 = cloned.alerts();
2361 assert!(alerts2.is_empty());
2362 }
2363
2364 #[test]
2365 fn computed_state_clone_without_cached_alerts() {
2366 let mut sim = create_simulation();
2368 let entity = create_human("person_001");
2369 let anchor = sim.reference_date();
2370 sim.add_entity(entity, anchor);
2371
2372 let handle = sim.entity(&EntityId::new("person_001").unwrap()).unwrap();
2373 let state = handle.state_at(anchor);
2374
2375 let cloned = state.clone();
2377
2378 let alerts1 = state.alerts();
2380 let alerts2 = cloned.alerts();
2381 assert!(alerts1.is_empty());
2382 assert!(alerts2.is_empty());
2383 }
2384
2385 #[test]
2386 fn memories_at_before_birth_returns_empty() {
2387 use crate::memory::MemoryTag;
2390
2391 let mut sim = create_simulation();
2392
2393 let mut entity = EntityBuilder::new()
2395 .id("child_001")
2396 .species(Species::Human)
2397 .age(Duration::years(5))
2398 .build()
2399 .unwrap();
2400
2401 entity.create_memory(
2403 "Memory at age 5",
2404 vec![],
2405 vec![MemoryTag::Personal],
2406 0.5,
2407 None,
2408 );
2409
2410 let anchor = sim.reference_date(); sim.add_entity(entity, anchor);
2412
2413 let past = anchor - Duration::years(10);
2416 let handle = sim.entity(&EntityId::new("child_001").unwrap()).unwrap();
2417 let memories = handle.memories_at(past);
2418
2419 assert!(memories.is_empty());
2421 }
2422
2423 #[test]
2424 fn developmental_effects_entity_without_birth_date_uses_anchor_age() {
2425 let mut sim = create_simulation();
2427
2428 let entity = EntityBuilder::new()
2430 .id("person_001")
2431 .species(Species::Human)
2432 .age(Duration::years(30))
2433 .build()
2434 .unwrap();
2435
2436 let entity_id = EntityId::new("person_001").unwrap();
2437 let anchor = sim.reference_date();
2438 sim.add_entity(entity, anchor);
2439
2440 let event = EventBuilder::new(EventType::ExperienceCombatMilitary)
2442 .target(entity_id.clone())
2443 .severity(0.5)
2444 .build()
2445 .unwrap();
2446 let event_time = anchor + Duration::days(10);
2447 sim.add_event(event, event_time);
2448
2449 let query_time = anchor + Duration::days(20);
2451 let handle = sim.entity(&entity_id).unwrap();
2452 let state = handle.state_at(query_time);
2453
2454 let valence = state.get_effective(StatePath::Mood(crate::enums::MoodPath::Valence));
2456 assert!(valence >= -1.0 && valence <= 1.0);
2457 }
2458
2459 #[test]
2460 fn developmental_effects_event_after_birth_date_uses_birth_age() {
2461 let birth_date = Timestamp::from_ymd_hms(2000, 1, 1, 0, 0, 0);
2463 let entity = EntityBuilder::new()
2464 .id("person_001")
2465 .species(Species::Human)
2466 .age(crate::types::Duration::years(30))
2467 .birth_date(birth_date)
2468 .build()
2469 .unwrap();
2470
2471 let entity_id = EntityId::new("person_001").unwrap();
2472 let anchor = Timestamp::from_ymd_hms(2024, 1, 1, 0, 0, 0);
2473 let mut sim = Simulation::new(anchor);
2474 sim.add_entity(entity, anchor);
2475
2476 let event = EventBuilder::new(EventType::AchieveGoalMajor)
2477 .target(entity_id.clone())
2478 .severity(0.5)
2479 .build()
2480 .unwrap();
2481 let event_time = anchor + Duration::days(10);
2482 sim.add_event(event, event_time);
2483
2484 let query_time = anchor + Duration::days(20);
2485 let handle = sim.entity(&entity_id).unwrap();
2486 assert_eq!(
2487 handle
2488 .get_sorted_events_for_range(anchor, query_time, true)
2489 .len(),
2490 1
2491 );
2492 let state = handle.state_at(query_time);
2493
2494 assert!(!state.age_at_timestamp().is_zero());
2495 }
2496
2497 #[test]
2498 fn developmental_effects_event_before_birth_date() {
2499 let birth_date = Timestamp::from_ymd_hms(2000, 1, 1, 0, 0, 0);
2504 let entity = EntityBuilder::new()
2505 .id("person_001")
2506 .species(Species::Human)
2507 .age(crate::types::Duration::years(30))
2508 .birth_date(birth_date)
2509 .build()
2510 .unwrap();
2511
2512 let entity_id = EntityId::new("person_001").unwrap();
2513 let anchor = Timestamp::from_ymd_hms(2024, 1, 1, 0, 0, 0);
2514 let mut sim = Simulation::new(anchor);
2515 sim.add_entity(entity, anchor);
2516
2517 let event = EventBuilder::new(EventType::AchieveGoalMajor)
2520 .target(entity_id.clone())
2521 .severity(0.5)
2522 .build()
2523 .unwrap();
2524 let event_time = Timestamp::from_ymd_hms(1990, 1, 1, 0, 0, 0); sim.add_event(event, event_time);
2526
2527 let query_time = Timestamp::from_ymd_hms(1980, 1, 1, 0, 0, 0);
2529 let handle = sim.entity(&entity_id).unwrap();
2530 assert_eq!(
2531 handle
2532 .get_sorted_events_for_range(anchor, query_time, false)
2533 .len(),
2534 1
2535 );
2536 let state = handle.state_at(query_time);
2537
2538 assert!(state.age_at_timestamp().is_zero());
2541 }
2542
2543 #[test]
2546 fn formative_event_shifts_personality() {
2547 let mut sim = create_simulation();
2548 let reference = sim.reference_date();
2549
2550 let entity = EntityBuilder::new()
2552 .id("person_001")
2553 .species(Species::Human)
2554 .age(crate::types::Duration::years(30))
2555 .birth_date(reference - Duration::years(25))
2556 .build()
2557 .unwrap();
2558
2559 let entity_id = entity.id().clone();
2560 let anchor = reference;
2561 sim.add_entity(entity, anchor);
2562
2563 let handle = sim.entity(&entity_id).unwrap();
2565 let baseline_state = handle.state_at(anchor);
2566 let baseline_agreeableness =
2567 baseline_state.get_effective(StatePath::Hexaco(HexacoPath::Agreeableness));
2568
2569 let event = EventBuilder::new(EventType::ExperienceCombatMilitary)
2571 .target(entity_id.clone())
2572 .severity(0.9)
2573 .with_base_shift(HexacoPath::Agreeableness, -0.15)
2574 .build()
2575 .unwrap();
2576 let event_time = anchor + Duration::days(1);
2577 sim.add_event(event, event_time);
2578
2579 let later = anchor + Duration::days(2);
2581 let handle = sim.entity(&entity_id).unwrap();
2582 let later_state = handle.state_at(later);
2583 let later_agreeableness =
2584 later_state.get_effective(StatePath::Hexaco(HexacoPath::Agreeableness));
2585
2586 assert!(later_agreeableness < baseline_agreeableness);
2588 }
2589
2590 #[test]
2591 fn formative_event_backward_query_no_shift() {
2592 let mut sim = create_simulation();
2593 let reference = sim.reference_date();
2594
2595 let entity = EntityBuilder::new()
2597 .id("person_001")
2598 .species(Species::Human)
2599 .age(crate::types::Duration::years(30))
2600 .birth_date(reference - Duration::years(25))
2601 .build()
2602 .unwrap();
2603
2604 let entity_id = entity.id().clone();
2605 let anchor = reference + Duration::days(10);
2606 sim.add_entity(entity, anchor);
2607
2608 let event = EventBuilder::new(EventType::ExperienceCombatMilitary)
2610 .target(entity_id.clone())
2611 .severity(0.9)
2612 .with_base_shift(HexacoPath::Agreeableness, -0.15)
2613 .build()
2614 .unwrap();
2615 let event_time = reference + Duration::days(5);
2616 sim.add_event(event, event_time);
2617
2618 let earlier = reference + Duration::days(1);
2620 let handle = sim.entity(&entity_id).unwrap();
2621 let earlier_state = handle.state_at(earlier);
2622 let earlier_agreeableness =
2623 earlier_state.get_effective(StatePath::Hexaco(HexacoPath::Agreeableness));
2624
2625 let anchor_state = handle.state_at(anchor);
2627 let anchor_agreeableness =
2628 anchor_state.get_effective(StatePath::Hexaco(HexacoPath::Agreeableness));
2629
2630 assert!((earlier_agreeableness - anchor_agreeableness).abs() < 0.1);
2634 }
2635
2636 #[test]
2637 fn formative_event_multiple_shifts_same_trait() {
2638 let mut sim = create_simulation();
2639 let reference = sim.reference_date();
2640
2641 let entity = EntityBuilder::new()
2643 .id("person_001")
2644 .species(Species::Human)
2645 .age(crate::types::Duration::years(30))
2646 .birth_date(reference - Duration::years(25))
2647 .build()
2648 .unwrap();
2649
2650 let entity_id = entity.id().clone();
2651 let anchor = reference;
2652 sim.add_entity(entity, anchor);
2653
2654 let handle = sim.entity(&entity_id).unwrap();
2656 let baseline_state = handle.state_at(anchor);
2657 let baseline_agreeableness =
2658 baseline_state.get_effective(StatePath::Hexaco(HexacoPath::Agreeableness));
2659
2660 let event1 = EventBuilder::new(EventType::ExperienceCombatMilitary)
2662 .target(entity_id.clone())
2663 .severity(0.8)
2664 .with_base_shift(HexacoPath::Agreeableness, -0.10)
2665 .build()
2666 .unwrap();
2667 sim.add_event(event1, anchor + Duration::days(1));
2668
2669 let event2 = EventBuilder::new(EventType::ExperienceCombatMilitary)
2671 .target(entity_id.clone())
2672 .severity(0.7)
2673 .with_base_shift(HexacoPath::Agreeableness, -0.08)
2674 .build()
2675 .unwrap();
2676 sim.add_event(event2, anchor + Duration::days(2));
2677
2678 let later = anchor + Duration::days(3);
2680 let handle = sim.entity(&entity_id).unwrap();
2681 let later_state = handle.state_at(later);
2682 let later_agreeableness =
2683 later_state.get_effective(StatePath::Hexaco(HexacoPath::Agreeableness));
2684
2685 assert!(later_agreeableness < baseline_agreeableness);
2687 }
2688
2689 #[test]
2690 fn formative_event_no_shift_leaves_trait_unchanged() {
2691 let mut sim = create_simulation();
2692 let reference = sim.reference_date();
2693
2694 let entity = EntityBuilder::new()
2695 .id("person_001")
2696 .species(Species::Human)
2697 .age(crate::types::Duration::years(30))
2698 .birth_date(reference - Duration::years(25))
2699 .build()
2700 .unwrap();
2701
2702 let entity_id = entity.id().clone();
2703 let anchor = reference;
2704 sim.add_entity(entity, anchor);
2705
2706 let handle = sim.entity(&entity_id).unwrap();
2708 let baseline_state = handle.state_at(anchor);
2709 let baseline_openness =
2710 baseline_state.get_effective(StatePath::Hexaco(HexacoPath::Openness));
2711
2712 let event = EventBuilder::new(EventType::ExperienceCombatMilitary)
2714 .target(entity_id.clone())
2715 .severity(0.8)
2716 .build()
2717 .unwrap();
2718 sim.add_event(event, anchor + Duration::days(1));
2719
2720 let later = anchor + Duration::days(2);
2722 let handle = sim.entity(&entity_id).unwrap();
2723 let later_state = handle.state_at(later);
2724 let later_openness = later_state.get_effective(StatePath::Hexaco(HexacoPath::Openness));
2725
2726 assert!((later_openness - baseline_openness).abs() < 0.01);
2728 }
2729
2730 #[test]
2731 fn collect_base_shifts_empty_for_backward_query() {
2732 let entity = EntityBuilder::new()
2733 .id("test")
2734 .species(Species::Human)
2735 .age(crate::types::Duration::years(30))
2736 .build()
2737 .unwrap();
2738
2739 let records = collect_base_shift_records(
2740 &[],
2741 &entity,
2742 Timestamp::from_ymd_hms(2024, 1, 1, 0, 0, 0),
2743 false, );
2745
2746 assert!(records.is_empty());
2747 }
2748
2749 #[test]
2750 fn apply_base_shifts_empty_records_returns_unchanged() {
2751 let state = IndividualState::new();
2752 let original_openness = state.hexaco().openness();
2753
2754 let result =
2755 apply_base_shifts_to_state(state, &[], Timestamp::from_ymd_hms(2024, 1, 1, 0, 0, 0));
2756
2757 assert!((result.hexaco().openness() - original_openness).abs() < f32::EPSILON);
2758 }
2759
2760 #[test]
2761 fn formative_event_all_hexaco_traits() {
2762 let mut sim = create_simulation();
2764 let reference = sim.reference_date();
2765
2766 let entity = EntityBuilder::new()
2767 .id("person_001")
2768 .species(Species::Human)
2769 .age(crate::types::Duration::years(30))
2770 .birth_date(reference - Duration::years(25))
2771 .build()
2772 .unwrap();
2773
2774 let entity_id = entity.id().clone();
2775 let anchor = reference;
2776 sim.add_entity(entity, anchor);
2777
2778 let event = EventBuilder::new(EventType::ExperienceCombatMilitary)
2780 .target(entity_id.clone())
2781 .severity(0.9)
2782 .with_base_shift(HexacoPath::Openness, 0.10)
2783 .with_base_shift(HexacoPath::Conscientiousness, -0.08)
2784 .with_base_shift(HexacoPath::Extraversion, 0.12)
2785 .with_base_shift(HexacoPath::Agreeableness, -0.15)
2786 .with_base_shift(HexacoPath::Neuroticism, 0.20)
2787 .with_base_shift(HexacoPath::HonestyHumility, -0.05)
2788 .build()
2789 .unwrap();
2790 sim.add_event(event, anchor + Duration::days(1));
2791
2792 let later = anchor + Duration::days(2);
2794 let handle = sim.entity(&entity_id).unwrap();
2795 let state = handle.state_at(later);
2796
2797 let _ = state.get_effective(StatePath::Hexaco(HexacoPath::Openness));
2799 let _ = state.get_effective(StatePath::Hexaco(HexacoPath::Conscientiousness));
2800 let _ = state.get_effective(StatePath::Hexaco(HexacoPath::Extraversion));
2801 let _ = state.get_effective(StatePath::Hexaco(HexacoPath::Agreeableness));
2802 let _ = state.get_effective(StatePath::Hexaco(HexacoPath::Neuroticism));
2803 let _ = state.get_effective(StatePath::Hexaco(HexacoPath::HonestyHumility));
2804 }
2805
2806 #[test]
2807 fn formative_event_positive_and_negative_shifts() {
2808 let mut sim = create_simulation();
2810 let reference = sim.reference_date();
2811
2812 let entity = EntityBuilder::new()
2813 .id("person_001")
2814 .species(Species::Human)
2815 .age(crate::types::Duration::years(30))
2816 .birth_date(reference - Duration::years(25))
2817 .build()
2818 .unwrap();
2819
2820 let entity_id = entity.id().clone();
2821 let anchor = reference;
2822 sim.add_entity(entity, anchor);
2823
2824 let handle = sim.entity(&entity_id).unwrap();
2826 let baseline_state = handle.state_at(anchor);
2827 let baseline_extraversion =
2828 baseline_state.get_effective(StatePath::Hexaco(HexacoPath::Extraversion));
2829
2830 let event = EventBuilder::new(EventType::AchieveGoalMajor)
2832 .target(entity_id.clone())
2833 .severity(0.7)
2834 .with_base_shift(HexacoPath::Extraversion, 0.15)
2835 .build()
2836 .unwrap();
2837 sim.add_event(event, anchor + Duration::days(1));
2838
2839 let later = anchor + Duration::days(2);
2841 let handle = sim.entity(&entity_id).unwrap();
2842 let later_state = handle.state_at(later);
2843 let later_extraversion =
2844 later_state.get_effective(StatePath::Hexaco(HexacoPath::Extraversion));
2845
2846 assert!(later_extraversion > baseline_extraversion);
2848 }
2849
2850 #[test]
2851 fn formative_event_entity_without_birth_date() {
2852 let mut sim = create_simulation();
2854 let reference = sim.reference_date();
2855
2856 let entity = EntityBuilder::new()
2858 .id("person_001")
2859 .species(Species::Human)
2860 .age(Duration::years(30))
2861 .build()
2862 .unwrap();
2863
2864 let entity_id = entity.id().clone();
2865 let anchor = reference;
2866 sim.add_entity(entity, anchor);
2867
2868 let event = EventBuilder::new(EventType::ExperienceCombatMilitary)
2870 .target(entity_id.clone())
2871 .severity(0.8)
2872 .with_base_shift(HexacoPath::Openness, -0.10)
2873 .build()
2874 .unwrap();
2875 sim.add_event(event, anchor + Duration::days(1));
2876
2877 let later = anchor + Duration::days(2);
2879 let handle = sim.entity(&entity_id).unwrap();
2880 let state = handle.state_at(later);
2881
2882 let openness = state.get_effective(StatePath::Hexaco(HexacoPath::Openness));
2884 assert!(openness >= -1.0 && openness <= 1.0);
2885 }
2886
2887 #[test]
2888 fn formative_event_after_query_timestamp_ignored() {
2889 let mut sim = create_simulation();
2891 let reference = sim.reference_date();
2892
2893 let entity = EntityBuilder::new()
2894 .id("person_001")
2895 .species(Species::Human)
2896 .age(crate::types::Duration::years(30))
2897 .birth_date(reference - Duration::years(25))
2898 .build()
2899 .unwrap();
2900
2901 let entity_id = entity.id().clone();
2902 let anchor = reference;
2903 sim.add_entity(entity, anchor);
2904
2905 let handle = sim.entity(&entity_id).unwrap();
2907 let baseline_state = handle.state_at(anchor);
2908 let baseline_agreeableness =
2909 baseline_state.get_effective(StatePath::Hexaco(HexacoPath::Agreeableness));
2910
2911 let event = EventBuilder::new(EventType::ExperienceCombatMilitary)
2913 .target(entity_id.clone())
2914 .severity(0.9)
2915 .with_base_shift(HexacoPath::Agreeableness, -0.30)
2916 .build()
2917 .unwrap();
2918 sim.add_event(event, anchor + Duration::days(10));
2919
2920 let before_event = anchor + Duration::days(5);
2922 let handle = sim.entity(&entity_id).unwrap();
2923 let state = handle.state_at(before_event);
2924 let agreeableness = state.get_effective(StatePath::Hexaco(HexacoPath::Agreeableness));
2925
2926 assert!((agreeableness - baseline_agreeableness).abs() < 0.01);
2928 }
2929
2930 #[test]
2931 fn collect_base_shifts_forward_query_with_events() {
2932 let entity = EntityBuilder::new()
2934 .id("test")
2935 .species(Species::Human)
2936 .age(crate::types::Duration::years(30))
2937 .birth_date(Timestamp::from_ymd_hms(1999, 1, 1, 0, 0, 0))
2938 .build()
2939 .unwrap();
2940
2941 let records = collect_base_shift_records(
2942 &[],
2943 &entity,
2944 Timestamp::from_ymd_hms(2024, 1, 1, 0, 0, 0),
2945 true, );
2947
2948 assert!(records.is_empty());
2950 }
2951
2952 #[test]
2953 fn apply_base_shifts_updates_all_traits() {
2954 use crate::state::BaseShiftRecord;
2955
2956 let state = IndividualState::new();
2957 let original_openness = state.hexaco().openness();
2958
2959 let records = vec![BaseShiftRecord::new(
2961 Duration::days(1),
2962 HexacoPath::Openness,
2963 0.15,
2964 )];
2965
2966 let result = apply_base_shifts_to_state(
2967 state.clone(),
2968 &records,
2969 Timestamp::from_ymd_hms(2024, 1, 1, 0, 0, 0),
2970 );
2971
2972 assert!((result.hexaco().openness() - original_openness).abs() > 0.01);
2974 }
2975
2976 #[test]
2977 fn apply_base_shifts_each_hexaco_trait() {
2978 use crate::state::BaseShiftRecord;
2979
2980 for trait_path in HexacoPath::all() {
2982 let state = IndividualState::new();
2983 let records = vec![BaseShiftRecord::new(Duration::days(1), trait_path, 0.10)];
2984
2985 let result = apply_base_shifts_to_state(
2986 state,
2987 &records,
2988 Timestamp::from_ymd_hms(2024, 1, 1, 0, 0, 0),
2989 );
2990
2991 let value = match trait_path {
2993 HexacoPath::Openness => result.hexaco().openness(),
2994 HexacoPath::Conscientiousness => result.hexaco().conscientiousness(),
2995 HexacoPath::Extraversion => result.hexaco().extraversion(),
2996 HexacoPath::Agreeableness => result.hexaco().agreeableness(),
2997 HexacoPath::Neuroticism => result.hexaco().neuroticism(),
2998 HexacoPath::HonestyHumility => result.hexaco().honesty_humility(),
2999 };
3000 assert!(value >= -1.0 && value <= 1.0);
3001 }
3002 }
3003
3004 #[test]
3005 fn collect_base_shifts_event_after_query_timestamp_skipped() {
3006 use crate::simulation::TimestampedEvent;
3008
3009 let entity = EntityBuilder::new()
3010 .id("test")
3011 .species(Species::Human)
3012 .age(crate::types::Duration::years(30))
3013 .birth_date(Timestamp::from_ymd_hms(1999, 1, 1, 0, 0, 0))
3014 .build()
3015 .unwrap();
3016
3017 let event = EventBuilder::new(EventType::ExperienceCombatMilitary)
3019 .with_base_shift(HexacoPath::Agreeableness, -0.20)
3020 .build()
3021 .unwrap();
3022 let te = TimestampedEvent::new(event, Timestamp::from_ymd_hms(2024, 4, 10, 0, 0, 0));
3023
3024 let query_ts = Timestamp::from_ymd_hms(2024, 1, 1, 0, 0, 0);
3026 let records = collect_base_shift_records(&[&te], &entity, query_ts, true);
3027
3028 assert!(records.is_empty());
3030 }
3031
3032 #[test]
3033 fn collect_base_shifts_event_before_birth_date_age_zero() {
3034 use crate::simulation::TimestampedEvent;
3036
3037 let entity = EntityBuilder::new()
3039 .id("test")
3040 .species(Species::Human)
3041 .age(crate::types::Duration::years(30))
3042 .birth_date(Timestamp::from_ymd_hms(2010, 1, 1, 0, 0, 0))
3043 .build()
3044 .unwrap();
3045
3046 let event = EventBuilder::new(EventType::ExperienceCombatMilitary)
3048 .with_base_shift(HexacoPath::Neuroticism, 0.25)
3049 .build()
3050 .unwrap();
3051 let te = TimestampedEvent::new(event, Timestamp::from_ymd_hms(2005, 6, 1, 0, 0, 0));
3052
3053 let query_ts = Timestamp::from_ymd_hms(2020, 1, 1, 0, 0, 0);
3055 let records = collect_base_shift_records(&[&te], &entity, query_ts, true);
3056
3057 assert_eq!(records.len(), 1);
3059 }
3060
3061 #[test]
3062 fn collect_base_shifts_entity_without_birth_date_uses_anchor_age() {
3063 use crate::simulation::TimestampedEvent;
3065
3066 let entity = EntityBuilder::new()
3068 .id("test")
3069 .species(Species::Human)
3070 .age(Duration::years(35))
3071 .build()
3072 .unwrap();
3073
3074 let event = EventBuilder::new(EventType::ExperienceCombatMilitary)
3076 .with_base_shift(HexacoPath::Conscientiousness, -0.15)
3077 .build()
3078 .unwrap();
3079 let te = TimestampedEvent::new(event, Timestamp::from_ymd_hms(2024, 1, 15, 0, 0, 0));
3080
3081 let query_ts = Timestamp::from_ymd_hms(2024, 6, 1, 0, 0, 0);
3083 let records = collect_base_shift_records(&[&te], &entity, query_ts, true);
3084
3085 assert_eq!(records.len(), 1);
3087 }
3088
3089 #[test]
3090 fn collect_base_shifts_event_before_1970_reference() {
3091 use crate::simulation::TimestampedEvent;
3093
3094 let entity = EntityBuilder::new()
3096 .id("test")
3097 .species(Species::Human)
3098 .age(crate::types::Duration::years(30))
3099 .birth_date(Timestamp::from_ymd_hms(1940, 1, 1, 0, 0, 0))
3100 .build()
3101 .unwrap();
3102
3103 let event = EventBuilder::new(EventType::ExperienceCombatMilitary)
3105 .with_base_shift(HexacoPath::Extraversion, -0.20)
3106 .build()
3107 .unwrap();
3108 let te = TimestampedEvent::new(event, Timestamp::from_ymd_hms(1960, 6, 1, 0, 0, 0));
3109
3110 let query_ts = Timestamp::from_ymd_hms(2000, 1, 1, 0, 0, 0);
3112 let records = collect_base_shift_records(&[&te], &entity, query_ts, true);
3113
3114 assert_eq!(records.len(), 1);
3116 }
3117
3118 #[test]
3119 fn collect_base_shifts_tiny_shift_rounds_to_zero() {
3120 use crate::simulation::TimestampedEvent;
3122
3123 let entity = EntityBuilder::new()
3127 .id("test")
3128 .species(Species::Human)
3129 .age(crate::types::Duration::years(30))
3130 .birth_date(Timestamp::from_ymd_hms(1940, 1, 1, 0, 0, 0))
3131 .build()
3132 .unwrap();
3133
3134 let event = EventBuilder::new(EventType::ExperienceCombatMilitary)
3137 .with_base_shift(HexacoPath::Extraversion, 1e-8)
3138 .build()
3139 .unwrap();
3140 let te = TimestampedEvent::new(event, Timestamp::from_ymd_hms(2020, 6, 1, 0, 0, 0));
3141
3142 let query_ts = Timestamp::from_ymd_hms(2024, 1, 1, 0, 0, 0);
3144 let records = collect_base_shift_records(&[&te], &entity, query_ts, true);
3145
3146 assert!(records.is_empty());
3148 }
3149
3150 #[test]
3151 fn collect_base_shifts_positive_cumulative_tracking() {
3152 use crate::simulation::TimestampedEvent;
3154
3155 let entity = EntityBuilder::new()
3157 .id("test")
3158 .species(Species::Human)
3159 .age(crate::types::Duration::years(30))
3160 .birth_date(Timestamp::from_ymd_hms(2000, 1, 1, 0, 0, 0))
3161 .build()
3162 .unwrap();
3163
3164 let event = EventBuilder::new(EventType::AchieveGoalMajor)
3166 .with_base_shift(HexacoPath::Agreeableness, 0.25)
3167 .build()
3168 .unwrap();
3169 let te = TimestampedEvent::new(event, Timestamp::from_ymd_hms(2024, 1, 15, 0, 0, 0));
3170
3171 let query_ts = Timestamp::from_ymd_hms(2024, 6, 1, 0, 0, 0);
3172 let records = collect_base_shift_records(&[&te], &entity, query_ts, true);
3173
3174 assert_eq!(records.len(), 1);
3176 assert!(records[0].immediate() > 0.0);
3177 }
3178
3179 #[test]
3180 fn collect_base_shifts_multiple_positive_shifts_diminishing() {
3181 use crate::simulation::TimestampedEvent;
3183
3184 let entity = EntityBuilder::new()
3185 .id("test")
3186 .species(Species::Human)
3187 .age(crate::types::Duration::years(30))
3188 .birth_date(Timestamp::from_ymd_hms(2000, 1, 1, 0, 0, 0))
3189 .build()
3190 .unwrap();
3191
3192 let event1 = EventBuilder::new(EventType::AchieveGoalMajor)
3194 .with_base_shift(HexacoPath::Agreeableness, 0.30)
3195 .build()
3196 .unwrap();
3197 let te1 = TimestampedEvent::new(event1, Timestamp::from_ymd_hms(2024, 1, 15, 0, 0, 0));
3198
3199 let event2 = EventBuilder::new(EventType::AchieveGoalMajor)
3201 .with_base_shift(HexacoPath::Agreeableness, 0.30)
3202 .build()
3203 .unwrap();
3204 let te2 = TimestampedEvent::new(event2, Timestamp::from_ymd_hms(2024, 2, 15, 0, 0, 0));
3205
3206 let query_ts = Timestamp::from_ymd_hms(2024, 6, 1, 0, 0, 0);
3207 let records = collect_base_shift_records(&[&te1, &te2], &entity, query_ts, true);
3208
3209 assert_eq!(records.len(), 2);
3211
3212 assert!(records[1].immediate() < records[0].immediate());
3214 }
3215
3216 #[test]
3217 fn apply_base_shifts_empty_returns_unchanged() {
3218 let state = IndividualState::new();
3220 let original_openness = state.hexaco().openness();
3221
3222 let result = apply_base_shifts_to_state(
3223 state,
3224 &[], Timestamp::from_ymd_hms(2024, 1, 1, 0, 0, 0),
3226 );
3227
3228 assert!((result.hexaco().openness() - original_openness).abs() < f32::EPSILON);
3229 }
3230
3231 #[test]
3232 fn apply_base_shifts_query_before_1970() {
3233 use crate::state::BaseShiftRecord;
3235
3236 let state = IndividualState::new();
3237
3238 let records = vec![BaseShiftRecord::new(
3239 Duration::days(1),
3240 HexacoPath::Openness,
3241 0.15,
3242 )];
3243
3244 let result = apply_base_shifts_to_state(
3246 state,
3247 &records,
3248 Timestamp::from_ymd_hms(1950, 1, 1, 0, 0, 0),
3249 );
3250
3251 assert!(result.hexaco().openness() >= -1.0 && result.hexaco().openness() <= 1.0);
3253 }
3254}