use crate::entity::Entity;
use crate::enums::{
Attribution, AttributionStability, AttributionType, Direction, EventType, LifeDomain, MoodPath,
StatePath,
};
use crate::event::{compute_arousal_modulated_salience, AppliedDeltas, Event};
use crate::relationship::{Relationship, TrustAntecedent, TrustContext, TrustDecision};
use crate::types::{EventId, Timestamp};
#[derive(Debug, Clone)]
pub struct InterpretedEvent {
pub event: Event,
pub original_event: EventId,
pub attribution: Attribution,
pub trust_attribution: Option<AttributionType>,
pub valence_delta: f32,
pub arousal_delta: f32,
pub dominance_delta: f32,
pub loneliness_delta: f32,
pub prc_delta: f32,
pub perceived_liability_delta: f32,
pub self_hate_delta: f32,
pub acquired_capability_delta: f32,
pub interpersonal_hopelessness_delta: f32,
pub salience: f32,
pub perceived_severity: f64,
pub memory_salience: f64,
pub state_deltas: Vec<(StatePath, f64)>,
pub spec_deltas: Option<AppliedDeltas>,
}
impl InterpretedEvent {
#[must_use]
pub fn scaled_by(&self, factor: f64) -> Self {
let factor_f32 = factor as f32;
InterpretedEvent {
event: self.event.clone(),
original_event: self.original_event.clone(),
attribution: self.attribution.clone(),
trust_attribution: self.trust_attribution,
valence_delta: self.valence_delta * factor_f32,
arousal_delta: self.arousal_delta * factor_f32,
dominance_delta: self.dominance_delta * factor_f32,
loneliness_delta: self.loneliness_delta * factor_f32,
prc_delta: self.prc_delta * factor_f32,
perceived_liability_delta: self.perceived_liability_delta * factor_f32,
self_hate_delta: self.self_hate_delta * factor_f32,
acquired_capability_delta: self.acquired_capability_delta * factor_f32,
interpersonal_hopelessness_delta: self.interpersonal_hopelessness_delta * factor_f32,
salience: self.salience, perceived_severity: self.perceived_severity * factor,
memory_salience: self.memory_salience, state_deltas: {
let mut scaled = Vec::with_capacity(self.state_deltas.len());
for (path, delta) in &self.state_deltas {
scaled.push((*path, delta * factor));
}
scaled
},
spec_deltas: self.spec_deltas.map(|deltas| AppliedDeltas {
permanent: deltas.permanent.scale(factor_f32),
acute: deltas.acute.scale(factor_f32),
chronic: deltas.chronic.scale(factor_f32),
}),
}
}
}
#[must_use]
pub(crate) fn interpret_event(event: &Event, entity: &Entity) -> InterpretedEvent {
let hexaco = entity.individual_state().hexaco();
let emotionality = hexaco.emotionality();
let honesty_humility = hexaco.honesty_humility();
let current_arousal = entity
.get_effective(StatePath::Mood(MoodPath::Arousal))
.unwrap_or(0.0) as f32;
let severity = event.severity() as f32;
let spec = event.spec();
let applied_deltas = spec.apply(severity);
let total_valence = applied_deltas.permanent.valence
+ applied_deltas.acute.valence
+ applied_deltas.chronic.valence;
let total_arousal = applied_deltas.permanent.arousal
+ applied_deltas.acute.arousal
+ applied_deltas.chronic.arousal;
let total_dominance = applied_deltas.permanent.dominance
+ applied_deltas.acute.dominance
+ applied_deltas.chronic.dominance;
let total_loneliness = applied_deltas.permanent.loneliness
+ applied_deltas.acute.loneliness
+ applied_deltas.chronic.loneliness;
let total_prc =
applied_deltas.permanent.prc + applied_deltas.acute.prc + applied_deltas.chronic.prc;
let total_perceived_competence = applied_deltas.permanent.perceived_competence
+ applied_deltas.acute.perceived_competence
+ applied_deltas.chronic.perceived_competence;
let total_perceived_liability = applied_deltas.permanent.perceived_liability
+ applied_deltas.acute.perceived_liability
+ applied_deltas.chronic.perceived_liability;
let total_self_hate = applied_deltas.permanent.self_hate
+ applied_deltas.acute.self_hate
+ applied_deltas.chronic.self_hate;
let total_ac = applied_deltas.permanent.acquired_capability
+ applied_deltas.acute.acquired_capability
+ applied_deltas.chronic.acquired_capability;
let total_interpersonal_hopelessness = applied_deltas.permanent.interpersonal_hopelessness
+ applied_deltas.acute.interpersonal_hopelessness
+ applied_deltas.chronic.interpersonal_hopelessness;
let total_trustor_propensity = applied_deltas.permanent.trustor_propensity
+ applied_deltas.acute.trustor_propensity
+ applied_deltas.chronic.trustor_propensity;
let emotionality_factor = 1.0 + (emotionality * 0.3);
let modulated_valence = total_valence * emotionality_factor;
let modulated_arousal = total_arousal * emotionality_factor;
let attribution = compute_attribution(event, honesty_humility);
let trust_attribution = compute_trust_attribution(
event,
total_perceived_competence,
total_prc,
total_trustor_propensity,
);
let is_trauma = total_ac > 0.0;
let base_salience = compute_base_salience(event);
let salience = compute_arousal_modulated_salience(
base_salience,
current_arousal + modulated_arousal,
modulated_valence,
is_trauma,
entity.species(),
);
let perceived_severity = (severity * emotionality_factor) as f64;
InterpretedEvent {
event: event.clone(),
original_event: event.id().clone(),
attribution,
trust_attribution,
valence_delta: modulated_valence,
arousal_delta: modulated_arousal,
dominance_delta: total_dominance,
loneliness_delta: total_loneliness,
prc_delta: total_prc,
perceived_liability_delta: total_perceived_liability,
self_hate_delta: total_self_hate,
acquired_capability_delta: total_ac,
interpersonal_hopelessness_delta: total_interpersonal_hopelessness,
salience,
perceived_severity,
memory_salience: salience as f64,
state_deltas: Vec::new(), spec_deltas: Some(applied_deltas),
}
}
fn compute_base_salience(event: &Event) -> f32 {
let severity = event.severity() as f32;
let spec = event.spec();
let ac_boost = if spec.impact.acquired_capability > 0.5 {
0.2
} else if spec.impact.acquired_capability > 0.0 {
0.1
} else {
0.0
};
let social_boost = if spec.impact.loneliness.abs() > 0.3 || spec.impact.prc.abs() > 0.2 {
0.1
} else {
0.0
};
(0.3 + severity * 0.5 + ac_boost + social_boost).clamp(0.0, 1.0)
}
fn compute_attribution(event: &Event, honesty_humility: f32) -> Attribution {
if let Some(source) = event.source() {
let stability = if event.severity() > 0.7 {
AttributionStability::Stable
} else {
AttributionStability::Unstable
};
return Attribution::Other(source.clone(), stability);
}
let stability = if event.severity() > 0.7 {
AttributionStability::Stable
} else {
AttributionStability::Unstable
};
if honesty_humility > 0.3 {
Attribution::SelfCaused(stability)
} else if honesty_humility < -0.3 {
Attribution::Situational(stability)
} else {
Attribution::Unknown
}
}
fn compute_trust_attribution(
event: &Event,
perceived_competence: f32,
perceived_reciprocal_caring: f32,
trustor_propensity: f32,
) -> Option<AttributionType> {
if let Some(override_attribution) = event.trust_attribution() {
return match override_attribution {
AttributionType::Unknown => None,
other => Some(other),
};
}
if perceived_competence.abs() > 0.1 {
Some(AttributionType::Ability)
} else if perceived_reciprocal_caring.abs() > 0.1 {
Some(AttributionType::Benevolence)
} else if trustor_propensity.abs() > 0.1 {
Some(AttributionType::Integrity)
} else {
None
}
}
#[cfg(test)]
pub(crate) fn apply_interpreted_event(interpreted: &InterpretedEvent, entity: &mut Entity) {
if let Some(deltas) = &interpreted.spec_deltas {
apply_permanent_deltas(deltas, entity);
apply_acute_deltas(deltas, entity);
apply_chronic_deltas(deltas, entity);
}
}
#[cfg(test)]
fn apply_permanent_deltas(deltas: &AppliedDeltas, entity: &mut Entity) {
let p = &deltas.permanent;
let state = entity.individual_state_mut();
if p.valence.abs() > f32::EPSILON {
state.mood_mut().shift_valence_base(p.valence);
}
if p.arousal.abs() > f32::EPSILON {
state.mood_mut().shift_arousal_base(p.arousal);
}
if p.dominance.abs() > f32::EPSILON {
state.mood_mut().shift_dominance_base(p.dominance);
}
if p.loneliness.abs() > f32::EPSILON {
state.social_cognition_mut().loneliness_mut().shift_base(p.loneliness);
}
if p.prc.abs() > f32::EPSILON {
state
.social_cognition_mut()
.perceived_reciprocal_caring_mut()
.shift_base(p.prc);
}
if p.perceived_liability.abs() > f32::EPSILON {
state
.social_cognition_mut()
.perceived_liability_mut()
.shift_base(p.perceived_liability);
}
if p.self_hate.abs() > f32::EPSILON {
state
.social_cognition_mut()
.self_hate_mut()
.shift_base(p.self_hate);
}
if p.purpose.abs() > f32::EPSILON {
state.needs_mut().purpose_mut().shift_base(p.purpose);
}
if p.acquired_capability.abs() > f32::EPSILON {
state
.mental_health_mut()
.shift_acquired_capability_base(p.acquired_capability);
}
if p.interpersonal_hopelessness.abs() > f32::EPSILON {
state
.mental_health_mut()
.shift_interpersonal_hopelessness_base(p.interpersonal_hopelessness);
}
if p.self_worth.abs() > f32::EPSILON {
state.mental_health_mut().shift_self_worth_base(p.self_worth);
}
if p.stress.abs() > f32::EPSILON {
state.needs_mut().stress_mut().shift_base(p.stress);
}
if p.fatigue.abs() > f32::EPSILON {
state.needs_mut().fatigue_mut().shift_base(p.fatigue);
}
if p.grievance.abs() > f32::EPSILON {
state.disposition_mut().grievance_mut().shift_base(p.grievance);
}
}
#[cfg(test)]
fn apply_acute_deltas(deltas: &AppliedDeltas, entity: &mut Entity) {
let a = &deltas.acute;
let state = entity.individual_state_mut();
if a.valence.abs() > f32::EPSILON {
state.mood_mut().add_valence_delta(a.valence);
}
if a.arousal.abs() > f32::EPSILON {
state.mood_mut().add_arousal_delta(a.arousal);
}
if a.dominance.abs() > f32::EPSILON {
state.mood_mut().add_dominance_delta(a.dominance);
}
if a.loneliness.abs() > f32::EPSILON {
state.social_cognition_mut().add_loneliness_delta(a.loneliness);
}
if a.prc.abs() > f32::EPSILON {
state
.social_cognition_mut()
.add_perceived_reciprocal_caring_delta(a.prc);
}
if a.perceived_liability.abs() > f32::EPSILON {
state
.social_cognition_mut()
.add_perceived_liability_delta(a.perceived_liability);
}
if a.self_hate.abs() > f32::EPSILON {
state.social_cognition_mut().add_self_hate_delta(a.self_hate);
}
if a.purpose.abs() > f32::EPSILON {
state.needs_mut().add_purpose_delta(a.purpose);
}
if a.interpersonal_hopelessness.abs() > f32::EPSILON {
state
.mental_health_mut()
.add_interpersonal_hopelessness_delta(a.interpersonal_hopelessness);
}
if a.self_worth.abs() > f32::EPSILON {
state.mental_health_mut().add_self_worth_delta(a.self_worth);
}
if a.stress.abs() > f32::EPSILON {
state.needs_mut().add_stress_delta(a.stress);
}
if a.fatigue.abs() > f32::EPSILON {
state.needs_mut().add_fatigue_delta(a.fatigue);
}
if a.grievance.abs() > f32::EPSILON {
state.disposition_mut().add_grievance_delta(a.grievance);
}
}
#[cfg(test)]
fn apply_chronic_deltas(deltas: &AppliedDeltas, entity: &mut Entity) {
let c = &deltas.chronic;
let state = entity.individual_state_mut();
if c.valence.abs() > f32::EPSILON {
state.mood_mut().add_valence_chronic_delta(c.valence);
}
if c.arousal.abs() > f32::EPSILON {
state.mood_mut().add_arousal_chronic_delta(c.arousal);
}
if c.dominance.abs() > f32::EPSILON {
state.mood_mut().add_dominance_chronic_delta(c.dominance);
}
if c.loneliness.abs() > f32::EPSILON {
state
.social_cognition_mut()
.loneliness_mut()
.add_chronic_delta(c.loneliness);
}
if c.prc.abs() > f32::EPSILON {
state
.social_cognition_mut()
.perceived_reciprocal_caring_mut()
.add_chronic_delta(c.prc);
}
if c.perceived_liability.abs() > f32::EPSILON {
state
.social_cognition_mut()
.perceived_liability_mut()
.add_chronic_delta(c.perceived_liability);
}
if c.self_hate.abs() > f32::EPSILON {
state
.social_cognition_mut()
.self_hate_mut()
.add_chronic_delta(c.self_hate);
}
if c.purpose.abs() > f32::EPSILON {
state.needs_mut().purpose_mut().add_chronic_delta(c.purpose);
}
if c.interpersonal_hopelessness.abs() > f32::EPSILON {
state
.mental_health_mut()
.add_interpersonal_hopelessness_chronic_delta(c.interpersonal_hopelessness);
}
if c.self_worth.abs() > f32::EPSILON {
state
.mental_health_mut()
.add_self_worth_chronic_delta(c.self_worth);
}
if c.stress.abs() > f32::EPSILON {
state.needs_mut().stress_mut().add_chronic_delta(c.stress);
}
if c.fatigue.abs() > f32::EPSILON {
state.needs_mut().fatigue_mut().add_chronic_delta(c.fatigue);
}
if c.grievance.abs() > f32::EPSILON {
state
.disposition_mut()
.grievance_mut()
.add_chronic_delta(c.grievance);
}
}
pub(crate) fn process_event_to_relationships(
event: &Event,
timestamp: Timestamp,
relationships: &mut [Relationship],
trust_attribution: Option<AttributionType>,
) {
let (Some(source), Some(target)) = (event.source(), event.target()) else {
return;
};
let spec = event.spec();
let severity = event.severity() as f32;
let trust_impact = spec.impact.trustor_propensity;
if trust_impact.abs() < f32::EPSILON {
return;
}
for relationship in relationships.iter_mut() {
let Some(direction) = direction_for_relationship(relationship, target, source) else {
continue;
};
let consistency = relationship.pattern().consistency.clamp(0.0, 1.0);
let consistency_weight = 0.5 + (consistency * 0.5);
let (antecedent_type, ant_direction) = if trust_impact < 0.0 {
use crate::relationship::{AntecedentDirection, AntecedentType};
if let Some(attribution) = trust_attribution {
let antecedent_type = match attribution {
AttributionType::Ability => AntecedentType::Ability,
AttributionType::Benevolence => AntecedentType::Benevolence,
AttributionType::Integrity => AntecedentType::Integrity,
AttributionType::Unknown => {
if spec.impact.prc < -0.1 {
AntecedentType::Benevolence
} else {
AntecedentType::Integrity
}
}
};
(antecedent_type, AntecedentDirection::Negative)
} else if spec.impact.prc < -0.1 {
(AntecedentType::Benevolence, AntecedentDirection::Negative)
} else {
(AntecedentType::Integrity, AntecedentDirection::Negative)
}
} else {
use crate::relationship::{AntecedentDirection, AntecedentType};
if let Some(attribution) = trust_attribution {
let antecedent_type = match attribution {
AttributionType::Ability => AntecedentType::Ability,
AttributionType::Benevolence => AntecedentType::Benevolence,
AttributionType::Integrity => AntecedentType::Integrity,
AttributionType::Unknown => {
if spec.impact.perceived_competence > 0.1 {
AntecedentType::Ability
} else {
AntecedentType::Benevolence
}
}
};
(antecedent_type, AntecedentDirection::Positive)
} else if spec.impact.perceived_competence > 0.1 {
(AntecedentType::Ability, AntecedentDirection::Positive)
} else {
(AntecedentType::Benevolence, AntecedentDirection::Positive)
}
};
let raw_magnitude = (trust_impact.abs() * severity).clamp(0.0, 1.0);
let magnitude = raw_magnitude * consistency_weight;
if magnitude <= 0.0 {
continue;
}
let context = event.event_type().name();
let mut antecedent = TrustAntecedent::new(
timestamp,
antecedent_type,
ant_direction,
magnitude,
context,
);
if let Some(domain) = event.payload().life_domain() {
antecedent = antecedent.with_life_domain(domain);
}
if let Some(attribution) = trust_attribution {
antecedent = antecedent.with_attribution(attribution);
}
relationship.append_antecedent(direction, antecedent);
let history = relationship.antecedent_history(direction).to_vec();
relationship
.trustworthiness_mut(direction)
.recompute_from_antecedents(&history);
}
}
fn direction_for_relationship(
relationship: &Relationship,
trustor: &crate::types::EntityId,
trustee: &crate::types::EntityId,
) -> Option<Direction> {
if relationship.entity_a() == trustor && relationship.entity_b() == trustee {
Some(Direction::AToB)
} else if relationship.entity_b() == trustor && relationship.entity_a() == trustee {
Some(Direction::BToA)
} else {
None
}
}
#[cfg(test)]
pub(crate) fn process_event(event: &Event, entity: &mut Entity) -> InterpretedEvent {
let interpreted = interpret_event(event, entity);
apply_interpreted_event(&interpreted, entity);
interpreted
}
pub(crate) fn compute_trust_modulation_factor(
event: &Event,
relationship: Option<&Relationship>,
direction: Direction,
trustor_propensity: f32,
context: &TrustContext,
life_domain: LifeDomain,
) -> f64 {
let Some(rel) = relationship else {
return 1.0;
};
let decision =
rel.compute_trust_decision(direction, trustor_propensity, context, life_domain, None);
let trust_level = trust_willingness_for_event(event.event_type(), &decision);
let spec = event.spec();
let is_negative = spec.impact.valence < 0.0
|| spec.impact.trustor_propensity < 0.0
|| spec.impact.prc < -0.1;
let modulation_strength = if is_negative { 0.5 } else { 0.3 };
let factor = 1.0 + (trust_level as f64 * modulation_strength);
factor.clamp(0.5, 2.0)
}
fn trust_willingness_for_event(event_type: EventType, decision: &TrustDecision) -> f32 {
match event_type {
EventType::ReceiveSupportEmotional
| EventType::ReceiveSupportFinancial
| EventType::ReceiveSupportPractical => decision.support_willingness(),
EventType::ExperienceBetrayalTrust => decision.disclosure_willingness(),
_ => {
(decision.task_willingness()
+ decision.support_willingness()
+ decision.disclosure_willingness())
/ 3.0
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::entity::EntityBuilder;
use crate::enums::{EventType, Species};
use crate::event::{AppliedDeltas, EventBuilder, EventImpact};
fn create_human() -> Entity {
EntityBuilder::new()
.species(Species::Human)
.age(crate::types::Duration::years(30))
.build()
.unwrap()
}
#[test]
fn interpret_event_returns_spec_deltas() {
let entity = create_human();
let event = EventBuilder::new(EventType::EndRelationshipRomantic)
.severity(0.8)
.build()
.unwrap();
let interpreted = interpret_event(&event, &entity);
assert!(interpreted.spec_deltas.is_some());
let deltas = interpreted.spec_deltas.unwrap();
let perm_negative = deltas.permanent.valence < 0.0;
let acute_negative = deltas.acute.valence < 0.0;
let chronic_negative = deltas.chronic.valence < 0.0;
let negative_count =
(perm_negative as u8) + (acute_negative as u8) + (chronic_negative as u8);
assert!(negative_count > 0);
}
#[test]
fn interpret_breakup_has_negative_valence() {
let entity = create_human();
let event = EventBuilder::new(EventType::EndRelationshipRomantic)
.severity(0.8)
.build()
.unwrap();
let interpreted = interpret_event(&event, &entity);
assert!(interpreted.valence_delta < 0.0);
}
#[test]
fn interpret_achievement_has_positive_valence() {
let entity = create_human();
let event = EventBuilder::new(EventType::AchieveGoalMajor)
.severity(0.8)
.build()
.unwrap();
let interpreted = interpret_event(&event, &entity);
assert!(interpreted.valence_delta > 0.0);
}
#[test]
fn interpret_combat_increases_ac() {
let entity = create_human();
let event = EventBuilder::new(EventType::ExperienceCombatMilitary)
.severity(0.9)
.build()
.unwrap();
let interpreted = interpret_event(&event, &entity);
assert!(interpreted.acquired_capability_delta > 0.0);
let deltas = interpreted.spec_deltas.unwrap();
assert!(deltas.permanent.acquired_capability > 0.0);
}
#[test]
fn interpret_betrayal_has_negative_prc() {
let entity = create_human();
let event = EventBuilder::new(EventType::ExperienceBetrayalTrust)
.severity(0.8)
.build()
.unwrap();
let interpreted = interpret_event(&event, &entity);
assert!(interpreted.prc_delta < 0.0);
}
#[test]
fn interpret_self_harm_increases_ac() {
let entity = create_human();
let event = EventBuilder::new(EventType::EngageSelfharmNonsuicidal)
.severity(0.7)
.build()
.unwrap();
let interpreted = interpret_event(&event, &entity);
assert!(interpreted.acquired_capability_delta > 0.0);
}
#[test]
fn interpret_chronic_illness_has_chronic_flags() {
let entity = create_human();
let event = EventBuilder::new(EventType::DevelopIllnessChronic)
.severity(0.8)
.build()
.unwrap();
let interpreted = interpret_event(&event, &entity);
let deltas = interpreted.spec_deltas.unwrap();
let chronic_stress = deltas.chronic.stress.abs() > f32::EPSILON;
let chronic_fatigue = deltas.chronic.fatigue.abs() > f32::EPSILON;
let chronic_self_worth = deltas.chronic.self_worth.abs() > f32::EPSILON;
let chronic_count =
(chronic_stress as u8) + (chronic_fatigue as u8) + (chronic_self_worth as u8);
assert!(chronic_count > 0);
}
#[test]
fn interpret_mortality_awareness_affects_hopelessness() {
let entity = create_human();
let event = EventBuilder::new(EventType::ExperienceAwarenessMortality)
.severity(0.9)
.build()
.unwrap();
let interpreted = interpret_event(&event, &entity);
let deltas = interpreted.spec_deltas.unwrap();
let perm_hopelessness = deltas.permanent.hopelessness.abs() > f32::EPSILON;
let acute_hopelessness = deltas.acute.hopelessness.abs() > f32::EPSILON;
let chronic_hopelessness = deltas.chronic.hopelessness.abs() > f32::EPSILON;
let perm_interpersonal = deltas.permanent.interpersonal_hopelessness.abs() > f32::EPSILON;
let acute_interpersonal = deltas.acute.interpersonal_hopelessness.abs() > f32::EPSILON;
let chronic_interpersonal = deltas.chronic.interpersonal_hopelessness.abs() > f32::EPSILON;
let impact_count = (perm_hopelessness as u8)
+ (acute_hopelessness as u8)
+ (chronic_hopelessness as u8)
+ (perm_interpersonal as u8)
+ (acute_interpersonal as u8)
+ (chronic_interpersonal as u8);
let has_hopelessness_impact = impact_count > 0;
let interpersonal_impact = interpreted.interpersonal_hopelessness_delta.abs() > 0.0;
let impact_total = (has_hopelessness_impact as u8) + (interpersonal_impact as u8);
assert!(impact_total > 0);
}
#[test]
fn scaled_by_scales_all_deltas() {
let entity = create_human();
let event = EventBuilder::new(EventType::EndRelationshipRomantic)
.severity(0.8)
.build()
.unwrap();
let interpreted = interpret_event(&event, &entity);
let scaled = interpreted.scaled_by(0.5);
let original_valence = interpreted.valence_delta.abs();
let scaled_valence = scaled.valence_delta.abs();
assert!((scaled_valence - original_valence * 0.5).abs() < 0.01);
let orig = interpreted
.spec_deltas
.expect("expected breakup to have spec deltas");
let sc = scaled
.spec_deltas
.expect("expected scaled breakup to have spec deltas");
let orig_perm_v = orig.permanent.valence.abs();
let sc_perm_v = sc.permanent.valence.abs();
assert!((sc_perm_v - orig_perm_v * 0.5).abs() < 0.01);
}
#[test]
fn process_event_applies_deltas_to_entity() {
let mut entity = create_human();
let initial_valence = entity.get_effective(StatePath::Mood(MoodPath::Valence)).unwrap_or(0.0);
let event = EventBuilder::new(EventType::EndRelationshipRomantic)
.severity(0.8)
.build()
.unwrap();
process_event(&event, &mut entity);
let final_valence = entity.get_effective(StatePath::Mood(MoodPath::Valence)).unwrap_or(0.0);
assert!(final_valence < initial_valence);
}
#[test]
fn process_combat_event_increases_ac_base() {
let mut entity = create_human();
let initial_ac = entity
.individual_state()
.mental_health()
.acquired_capability()
.base();
let event = EventBuilder::new(EventType::ExperienceCombatMilitary)
.severity(0.9)
.build()
.unwrap();
process_event(&event, &mut entity);
let final_ac = entity
.individual_state()
.mental_health()
.acquired_capability()
.base();
assert!(final_ac > initial_ac);
}
#[test]
fn attribution_with_source_attributes_to_other() {
let entity = create_human();
let source = crate::types::EntityId::new("perpetrator").unwrap();
let event = EventBuilder::new(EventType::ExperienceBetrayalTrust)
.source(source.clone())
.severity(0.8)
.build()
.unwrap();
let interpreted = interpret_event(&event, &entity);
assert_eq!(
interpreted.attribution,
Attribution::Other(source, AttributionStability::Stable)
);
}
#[test]
fn attribution_with_source_unstable_for_lower_severity() {
let entity = create_human();
let source = crate::types::EntityId::new("perpetrator").unwrap();
let event = EventBuilder::new(EventType::ExperienceBetrayalTrust)
.source(source.clone())
.severity(0.5)
.build()
.unwrap();
let interpreted = interpret_event(&event, &entity);
let expected =
std::mem::discriminant(&Attribution::Other(source, AttributionStability::Unstable));
let actual = std::mem::discriminant(&interpreted.attribution);
assert_eq!(actual, expected);
}
#[test]
fn attribution_without_source_self_caused_for_high_honesty() {
let mut entity = create_human();
entity
.individual_state_mut()
.hexaco_mut()
.set_honesty_humility(0.8);
let event = EventBuilder::new(EventType::EndRelationshipRomantic)
.severity(0.8)
.build()
.unwrap();
let interpreted = interpret_event(&event, &entity);
let expected = std::mem::discriminant(&Attribution::SelfCaused(AttributionStability::Stable));
let actual = std::mem::discriminant(&interpreted.attribution);
assert_eq!(actual, expected);
}
#[test]
fn attribution_without_source_situational_for_low_honesty() {
let mut entity = create_human();
entity
.individual_state_mut()
.hexaco_mut()
.set_honesty_humility(-0.8);
let event = EventBuilder::new(EventType::EndRelationshipRomantic)
.severity(0.8)
.build()
.unwrap();
let interpreted = interpret_event(&event, &entity);
let expected =
std::mem::discriminant(&Attribution::Situational(AttributionStability::Stable));
let actual = std::mem::discriminant(&interpreted.attribution);
assert_eq!(actual, expected);
}
#[test]
fn attribution_without_source_unknown_for_mid_honesty() {
let mut entity = create_human();
entity
.individual_state_mut()
.hexaco_mut()
.set_honesty_humility(0.0);
let event = EventBuilder::new(EventType::EndRelationshipRomantic)
.severity(0.6)
.build()
.unwrap();
let interpreted = interpret_event(&event, &entity);
assert!(interpreted.attribution.is_unknown());
}
#[test]
fn trust_attribution_none_when_signals_are_small() {
let event = EventBuilder::new(EventType::EndRelationshipRomantic)
.severity(0.4)
.build()
.unwrap();
let attribution = compute_trust_attribution(&event, 0.0, 0.0, 0.0);
assert!(attribution.is_none());
}
#[test]
fn process_event_to_relationships_skips_zero_trust_impact() {
use crate::enums::Direction;
use crate::event::event_spec::{ChronicFlags, EventImpact, EventSpec, PermanenceValues};
use crate::relationship::Relationship;
use crate::types::{EntityId, Timestamp};
let source = EntityId::new("source").unwrap();
let target = EntityId::new("target").unwrap();
let mut relationships = vec![Relationship::try_between(target.clone(), source.clone()).unwrap()];
let custom_spec = EventSpec {
impact: EventImpact {
trustor_propensity: 0.0,
prc: 0.0,
perceived_competence: 0.0,
..Default::default()
},
chronic: ChronicFlags::default(),
permanence: PermanenceValues::default(),
};
let event = EventBuilder::custom(custom_spec)
.source(source)
.target(target)
.severity(1.0)
.build()
.unwrap();
process_event_to_relationships(
&event,
Timestamp::from_ymd_hms(2024, 1, 1, 0, 0, 0),
&mut relationships,
None,
);
assert!(relationships[0]
.antecedent_history(Direction::AToB)
.is_empty());
}
#[test]
fn process_event_to_relationships_skips_without_source_or_target() {
use crate::enums::Direction;
use crate::relationship::Relationship;
use crate::types::{EntityId, Timestamp};
let source = EntityId::new("source").unwrap();
let target = EntityId::new("target").unwrap();
let mut relationships = vec![Relationship::try_between(target.clone(), source.clone()).unwrap()];
let event = EventBuilder::new(EventType::EndRelationshipRomantic)
.target(target)
.severity(0.8)
.build()
.unwrap();
process_event_to_relationships(
&event,
Timestamp::from_ymd_hms(2024, 1, 1, 0, 0, 0),
&mut relationships,
None,
);
assert!(relationships[0]
.antecedent_history(Direction::AToB)
.is_empty());
assert!(relationships[0]
.antecedent_history(Direction::BToA)
.is_empty());
}
#[test]
fn process_event_to_relationships_skips_unrelated_relationship() {
use crate::enums::Direction;
use crate::event::event_spec::{ChronicFlags, EventImpact, EventSpec, PermanenceValues};
use crate::relationship::Relationship;
use crate::types::{EntityId, Timestamp};
let source = EntityId::new("source").unwrap();
let target = EntityId::new("target").unwrap();
let other = EntityId::new("other").unwrap();
let mut relationships = vec![Relationship::try_between(other.clone(), source.clone()).unwrap()];
let custom_spec = EventSpec {
impact: EventImpact {
trustor_propensity: 0.4,
..Default::default()
},
chronic: ChronicFlags::default(),
permanence: PermanenceValues::default(),
};
let event = EventBuilder::custom(custom_spec)
.source(source)
.target(target)
.severity(1.0)
.build()
.unwrap();
process_event_to_relationships(
&event,
Timestamp::from_ymd_hms(2024, 1, 1, 0, 0, 0),
&mut relationships,
None,
);
assert!(relationships[0]
.antecedent_history(Direction::AToB)
.is_empty());
assert!(relationships[0]
.antecedent_history(Direction::BToA)
.is_empty());
}
#[test]
fn process_event_to_relationships_negative_integrity_branch() {
use crate::enums::Direction;
use crate::event::event_spec::{ChronicFlags, EventImpact, EventSpec, PermanenceValues};
use crate::relationship::{AntecedentDirection, AntecedentType, Relationship};
use crate::types::{EntityId, Timestamp};
let source = EntityId::new("source").unwrap();
let target = EntityId::new("target").unwrap();
let mut relationships = vec![Relationship::try_between(target.clone(), source.clone()).unwrap()];
let custom_spec = EventSpec {
impact: EventImpact {
trustor_propensity: -0.4,
prc: 0.0,
perceived_competence: 0.0,
..Default::default()
},
chronic: ChronicFlags::default(),
permanence: PermanenceValues::default(),
};
let event = EventBuilder::custom(custom_spec)
.source(source)
.target(target)
.severity(1.0)
.build()
.unwrap();
process_event_to_relationships(
&event,
Timestamp::from_ymd_hms(2024, 1, 1, 0, 0, 0),
&mut relationships,
None,
);
let history = relationships[0].antecedent_history(Direction::AToB);
assert_eq!(history.len(), 1);
assert_eq!(history[0].antecedent_type(), AntecedentType::Integrity);
assert_eq!(history[0].direction(), AntecedentDirection::Negative);
}
#[test]
fn process_event_to_relationships_negative_benevolence_branch() {
use crate::enums::Direction;
use crate::event::event_spec::{ChronicFlags, EventImpact, EventSpec, PermanenceValues};
use crate::relationship::{AntecedentDirection, AntecedentType, Relationship};
use crate::types::{EntityId, Timestamp};
let source = EntityId::new("source").unwrap();
let target = EntityId::new("target").unwrap();
let mut relationships = vec![Relationship::try_between(target.clone(), source.clone()).unwrap()];
let custom_spec = EventSpec {
impact: EventImpact {
trustor_propensity: -0.4,
prc: -0.2,
..Default::default()
},
chronic: ChronicFlags::default(),
permanence: PermanenceValues::default(),
};
let event = EventBuilder::custom(custom_spec)
.source(source)
.target(target)
.severity(1.0)
.build()
.unwrap();
process_event_to_relationships(
&event,
Timestamp::from_ymd_hms(2024, 1, 1, 0, 0, 0),
&mut relationships,
None,
);
let history = relationships[0].antecedent_history(Direction::AToB);
assert_eq!(history.len(), 1);
assert_eq!(history[0].antecedent_type(), AntecedentType::Benevolence);
assert_eq!(history[0].direction(), AntecedentDirection::Negative);
}
#[test]
fn process_event_to_relationships_negative_integrity_with_attribution() {
use crate::enums::Direction;
use crate::event::event_spec::{ChronicFlags, EventImpact, EventSpec, PermanenceValues};
use crate::relationship::{AntecedentDirection, AntecedentType, Relationship};
use crate::types::{EntityId, Timestamp};
let source = EntityId::new("source").unwrap();
let target = EntityId::new("target").unwrap();
let mut relationships = vec![Relationship::try_between(target.clone(), source.clone()).unwrap()];
let custom_spec = EventSpec {
impact: EventImpact {
trustor_propensity: -0.4,
prc: 0.0,
..Default::default()
},
chronic: ChronicFlags::default(),
permanence: PermanenceValues::default(),
};
let event = EventBuilder::custom(custom_spec)
.source(source)
.target(target)
.severity(1.0)
.build()
.unwrap();
process_event_to_relationships(
&event,
Timestamp::from_ymd_hms(2024, 1, 1, 0, 0, 0),
&mut relationships,
Some(AttributionType::Integrity),
);
let history = relationships[0].antecedent_history(Direction::AToB);
assert_eq!(history.len(), 1);
assert_eq!(history[0].antecedent_type(), AntecedentType::Integrity);
assert_eq!(history[0].direction(), AntecedentDirection::Negative);
}
#[test]
fn process_event_to_relationships_negative_ability_with_attribution() {
use crate::enums::Direction;
use crate::event::event_spec::{ChronicFlags, EventImpact, EventSpec, PermanenceValues};
use crate::relationship::{AntecedentDirection, AntecedentType, Relationship};
use crate::types::{EntityId, Timestamp};
let source = EntityId::new("source").unwrap();
let target = EntityId::new("target").unwrap();
let mut relationships = vec![Relationship::try_between(target.clone(), source.clone()).unwrap()];
let custom_spec = EventSpec {
impact: EventImpact {
trustor_propensity: -0.4,
prc: 0.0,
..Default::default()
},
chronic: ChronicFlags::default(),
permanence: PermanenceValues::default(),
};
let event = EventBuilder::custom(custom_spec)
.source(source)
.target(target)
.severity(1.0)
.build()
.unwrap();
process_event_to_relationships(
&event,
Timestamp::from_ymd_hms(2024, 1, 1, 0, 0, 0),
&mut relationships,
Some(AttributionType::Ability),
);
let history = relationships[0].antecedent_history(Direction::AToB);
assert_eq!(history.len(), 1);
assert_eq!(history[0].antecedent_type(), AntecedentType::Ability);
assert_eq!(history[0].direction(), AntecedentDirection::Negative);
}
#[test]
fn process_event_to_relationships_negative_benevolence_with_attribution() {
use crate::enums::Direction;
use crate::event::event_spec::{ChronicFlags, EventImpact, EventSpec, PermanenceValues};
use crate::relationship::{AntecedentDirection, AntecedentType, Relationship};
use crate::types::{EntityId, Timestamp};
let source = EntityId::new("source").unwrap();
let target = EntityId::new("target").unwrap();
let mut relationships = vec![Relationship::try_between(target.clone(), source.clone()).unwrap()];
let custom_spec = EventSpec {
impact: EventImpact {
trustor_propensity: -0.4,
prc: 0.0,
..Default::default()
},
chronic: ChronicFlags::default(),
permanence: PermanenceValues::default(),
};
let event = EventBuilder::custom(custom_spec)
.source(source)
.target(target)
.severity(1.0)
.build()
.unwrap();
process_event_to_relationships(
&event,
Timestamp::from_ymd_hms(2024, 1, 1, 0, 0, 0),
&mut relationships,
Some(AttributionType::Benevolence),
);
let history = relationships[0].antecedent_history(Direction::AToB);
assert_eq!(history.len(), 1);
assert_eq!(history[0].antecedent_type(), AntecedentType::Benevolence);
assert_eq!(history[0].direction(), AntecedentDirection::Negative);
}
#[test]
fn process_event_to_relationships_negative_unknown_prefers_benevolence_on_prc() {
use crate::enums::Direction;
use crate::event::event_spec::{ChronicFlags, EventImpact, EventSpec, PermanenceValues};
use crate::relationship::{AntecedentDirection, AntecedentType, Relationship};
use crate::types::{EntityId, Timestamp};
let source = EntityId::new("source").unwrap();
let target = EntityId::new("target").unwrap();
let mut relationships = vec![Relationship::try_between(target.clone(), source.clone()).unwrap()];
let custom_spec = EventSpec {
impact: EventImpact {
trustor_propensity: -0.4,
prc: -0.2,
..Default::default()
},
chronic: ChronicFlags::default(),
permanence: PermanenceValues::default(),
};
let event = EventBuilder::custom(custom_spec)
.source(source)
.target(target)
.severity(1.0)
.build()
.unwrap();
process_event_to_relationships(
&event,
Timestamp::from_ymd_hms(2024, 1, 1, 0, 0, 0),
&mut relationships,
Some(AttributionType::Unknown),
);
let history = relationships[0].antecedent_history(Direction::AToB);
assert_eq!(history.len(), 1);
assert_eq!(history[0].antecedent_type(), AntecedentType::Benevolence);
assert_eq!(history[0].direction(), AntecedentDirection::Negative);
}
#[test]
fn process_event_to_relationships_negative_unknown_prefers_integrity_without_prc_drop() {
use crate::enums::Direction;
use crate::event::event_spec::{ChronicFlags, EventImpact, EventSpec, PermanenceValues};
use crate::relationship::{AntecedentDirection, AntecedentType, Relationship};
use crate::types::{EntityId, Timestamp};
let source = EntityId::new("source").unwrap();
let target = EntityId::new("target").unwrap();
let mut relationships = vec![Relationship::try_between(target.clone(), source.clone()).unwrap()];
let custom_spec = EventSpec {
impact: EventImpact {
trustor_propensity: -0.4,
prc: 0.0,
..Default::default()
},
chronic: ChronicFlags::default(),
permanence: PermanenceValues::default(),
};
let event = EventBuilder::custom(custom_spec)
.source(source)
.target(target)
.severity(1.0)
.build()
.unwrap();
process_event_to_relationships(
&event,
Timestamp::from_ymd_hms(2024, 1, 1, 0, 0, 0),
&mut relationships,
Some(AttributionType::Unknown),
);
let history = relationships[0].antecedent_history(Direction::AToB);
assert_eq!(history.len(), 1);
assert_eq!(history[0].antecedent_type(), AntecedentType::Integrity);
assert_eq!(history[0].direction(), AntecedentDirection::Negative);
}
#[test]
fn process_event_to_relationships_positive_integrity_with_attribution() {
use crate::enums::Direction;
use crate::event::event_spec::{ChronicFlags, EventImpact, EventSpec, PermanenceValues};
use crate::relationship::{AntecedentDirection, AntecedentType, Relationship};
use crate::types::{EntityId, Timestamp};
let source = EntityId::new("source").unwrap();
let target = EntityId::new("target").unwrap();
let mut relationships = vec![Relationship::try_between(target.clone(), source.clone()).unwrap()];
let custom_spec = EventSpec {
impact: EventImpact {
trustor_propensity: 0.4,
..Default::default()
},
chronic: ChronicFlags::default(),
permanence: PermanenceValues::default(),
};
let event = EventBuilder::custom(custom_spec)
.source(source)
.target(target)
.severity(1.0)
.build()
.unwrap();
process_event_to_relationships(
&event,
Timestamp::from_ymd_hms(2024, 1, 1, 0, 0, 0),
&mut relationships,
Some(AttributionType::Integrity),
);
let history = relationships[0].antecedent_history(Direction::AToB);
assert_eq!(history.len(), 1);
assert_eq!(history[0].antecedent_type(), AntecedentType::Integrity);
assert_eq!(history[0].direction(), AntecedentDirection::Positive);
}
#[test]
fn process_event_to_relationships_positive_unknown_prefers_ability() {
use crate::enums::Direction;
use crate::event::event_spec::{ChronicFlags, EventImpact, EventSpec, PermanenceValues};
use crate::relationship::{AntecedentDirection, AntecedentType, Relationship};
use crate::types::{EntityId, Timestamp};
let source = EntityId::new("source").unwrap();
let target = EntityId::new("target").unwrap();
let mut relationships = vec![Relationship::try_between(target.clone(), source.clone()).unwrap()];
let custom_spec = EventSpec {
impact: EventImpact {
trustor_propensity: 0.4,
perceived_competence: 0.2,
..Default::default()
},
chronic: ChronicFlags::default(),
permanence: PermanenceValues::default(),
};
let event = EventBuilder::custom(custom_spec)
.source(source)
.target(target)
.severity(1.0)
.build()
.unwrap();
process_event_to_relationships(
&event,
Timestamp::from_ymd_hms(2024, 1, 1, 0, 0, 0),
&mut relationships,
Some(AttributionType::Unknown),
);
let history = relationships[0].antecedent_history(Direction::AToB);
assert_eq!(history.len(), 1);
assert_eq!(history[0].antecedent_type(), AntecedentType::Ability);
assert_eq!(history[0].direction(), AntecedentDirection::Positive);
}
#[test]
fn process_event_to_relationships_positive_unknown_prefers_benevolence() {
use crate::enums::Direction;
use crate::event::event_spec::{ChronicFlags, EventImpact, EventSpec, PermanenceValues};
use crate::relationship::{AntecedentDirection, AntecedentType, Relationship};
use crate::types::{EntityId, Timestamp};
let source = EntityId::new("source").unwrap();
let target = EntityId::new("target").unwrap();
let mut relationships = vec![Relationship::try_between(target.clone(), source.clone()).unwrap()];
let custom_spec = EventSpec {
impact: EventImpact {
trustor_propensity: 0.4,
perceived_competence: 0.0,
..Default::default()
},
chronic: ChronicFlags::default(),
permanence: PermanenceValues::default(),
};
let event = EventBuilder::custom(custom_spec)
.source(source)
.target(target)
.severity(1.0)
.build()
.unwrap();
process_event_to_relationships(
&event,
Timestamp::from_ymd_hms(2024, 1, 1, 0, 0, 0),
&mut relationships,
Some(AttributionType::Unknown),
);
let history = relationships[0].antecedent_history(Direction::AToB);
assert_eq!(history.len(), 1);
assert_eq!(history[0].antecedent_type(), AntecedentType::Benevolence);
assert_eq!(history[0].direction(), AntecedentDirection::Positive);
}
#[test]
fn process_event_to_relationships_skips_zero_magnitude() {
use crate::enums::Direction;
use crate::event::event_spec::{ChronicFlags, EventImpact, EventSpec, PermanenceValues};
use crate::relationship::Relationship;
use crate::types::{EntityId, Timestamp};
let source = EntityId::new("source").unwrap();
let target = EntityId::new("target").unwrap();
let mut relationships = vec![Relationship::try_between(target.clone(), source.clone()).unwrap()];
let custom_spec = EventSpec {
impact: EventImpact {
trustor_propensity: 0.5,
..Default::default()
},
chronic: ChronicFlags::default(),
permanence: PermanenceValues::default(),
};
let event = EventBuilder::custom(custom_spec)
.source(source)
.target(target)
.severity(0.0)
.build()
.unwrap();
process_event_to_relationships(
&event,
Timestamp::from_ymd_hms(2024, 1, 1, 0, 0, 0),
&mut relationships,
None,
);
assert!(relationships[0]
.antecedent_history(Direction::AToB)
.is_empty());
}
#[test]
fn apply_interpreted_event_skips_when_no_spec_deltas() {
let mut entity = create_human();
let event = EventBuilder::new(EventType::EndRelationshipRomantic)
.severity(0.8)
.build()
.unwrap();
let mut interpreted = interpret_event(&event, &entity);
interpreted.spec_deltas = None;
let before = entity
.get_effective(StatePath::Mood(MoodPath::Valence))
.unwrap_or(0.0);
apply_interpreted_event(&interpreted, &mut entity);
let after = entity
.get_effective(StatePath::Mood(MoodPath::Valence))
.unwrap_or(0.0);
assert!((before - after).abs() < f64::EPSILON);
}
#[test]
fn apply_interpreted_event_applies_all_delta_buckets() {
let mut entity = create_human();
let event = EventBuilder::new(EventType::EndRelationshipRomantic)
.severity(0.5)
.build()
.unwrap();
let mut interpreted = interpret_event(&event, &entity);
let permanent = EventImpact {
valence: 0.1,
arousal: 0.1,
dominance: -0.1,
loneliness: 0.1,
prc: -0.1,
perceived_liability: 0.1,
self_hate: 0.1,
purpose: 0.1,
interpersonal_hopelessness: 0.1,
self_worth: -0.1,
stress: 0.1,
fatigue: 0.1,
grievance: 0.1,
acquired_capability: 0.1,
..Default::default()
};
let acute = EventImpact {
valence: 0.05,
arousal: 0.05,
dominance: 0.05,
loneliness: 0.05,
prc: 0.05,
perceived_liability: 0.05,
self_hate: 0.05,
purpose: 0.05,
interpersonal_hopelessness: 0.05,
self_worth: 0.05,
stress: 0.05,
fatigue: 0.05,
grievance: 0.05,
..Default::default()
};
let chronic = EventImpact {
valence: 0.02,
arousal: 0.02,
dominance: 0.02,
loneliness: 0.02,
prc: 0.02,
perceived_liability: 0.02,
self_hate: 0.02,
purpose: 0.02,
interpersonal_hopelessness: 0.02,
self_worth: 0.02,
stress: 0.02,
fatigue: 0.02,
grievance: 0.02,
..Default::default()
};
interpreted.spec_deltas = Some(AppliedDeltas {
permanent,
acute,
chronic,
});
let before_valence = entity.individual_state().mood().valence().base();
let before_stress = entity.individual_state().needs().stress().delta();
let before_grievance = entity
.individual_state()
.disposition()
.grievance()
.chronic_delta();
apply_interpreted_event(&interpreted, &mut entity);
assert!(entity.individual_state().mood().valence().base() > before_valence);
assert!(entity.individual_state().needs().stress().delta() > before_stress);
assert!(
entity
.individual_state()
.disposition()
.grievance()
.chronic_delta()
> before_grievance
);
}
#[test]
fn base_salience_increases_with_severity() {
let low_event = EventBuilder::new(EventType::EndRelationshipRomantic)
.severity(0.3)
.build()
.unwrap();
let high_event = EventBuilder::new(EventType::EndRelationshipRomantic)
.severity(0.9)
.build()
.unwrap();
let low_salience = compute_base_salience(&low_event);
let high_salience = compute_base_salience(&high_event);
assert!(high_salience > low_salience);
}
#[test]
fn base_salience_boosts_for_ac_and_social_impact() {
use crate::event::event_spec::{ChronicFlags, EventImpact, EventSpec, PermanenceValues};
let base_spec = EventSpec {
impact: EventImpact {
acquired_capability: 0.0,
loneliness: 0.0,
prc: 0.0,
..Default::default()
},
chronic: ChronicFlags::default(),
permanence: PermanenceValues::default(),
};
let boosted_spec = EventSpec {
impact: EventImpact {
acquired_capability: 0.6,
loneliness: 0.4,
prc: -0.3,
..Default::default()
},
chronic: ChronicFlags::default(),
permanence: PermanenceValues::default(),
};
let base_event = EventBuilder::custom(base_spec).severity(0.4).build().unwrap();
let boosted_event = EventBuilder::custom(boosted_spec).severity(0.4).build().unwrap();
let base_salience = compute_base_salience(&base_event);
let boosted_salience = compute_base_salience(&boosted_event);
assert!(boosted_salience > base_salience);
}
#[test]
fn base_salience_boosts_when_loneliness_alone_exceeds_threshold() {
use crate::event::event_spec::{ChronicFlags, EventImpact, EventSpec, PermanenceValues};
let base_spec = EventSpec {
impact: EventImpact {
acquired_capability: 0.0,
loneliness: 0.0,
prc: 0.0,
..Default::default()
},
chronic: ChronicFlags::default(),
permanence: PermanenceValues::default(),
};
let loneliness_spec = EventSpec {
impact: EventImpact {
acquired_capability: 0.0,
loneliness: 0.4,
prc: 0.0,
..Default::default()
},
chronic: ChronicFlags::default(),
permanence: PermanenceValues::default(),
};
let base_event = EventBuilder::custom(base_spec).severity(0.4).build().unwrap();
let loneliness_event = EventBuilder::custom(loneliness_spec)
.severity(0.4)
.build()
.unwrap();
let base_salience = compute_base_salience(&base_event);
let loneliness_salience = compute_base_salience(&loneliness_event);
assert!(loneliness_salience > base_salience);
}
#[test]
fn base_salience_boosts_for_mid_ac_and_prc_only_social_signal() {
use crate::event::event_spec::{ChronicFlags, EventImpact, EventSpec, PermanenceValues};
let base_spec = EventSpec {
impact: EventImpact {
acquired_capability: 0.0,
loneliness: 0.0,
prc: 0.0,
..Default::default()
},
chronic: ChronicFlags::default(),
permanence: PermanenceValues::default(),
};
let mid_spec = EventSpec {
impact: EventImpact {
acquired_capability: 0.2,
loneliness: 0.0,
prc: -0.3,
..Default::default()
},
chronic: ChronicFlags::default(),
permanence: PermanenceValues::default(),
};
let base_event = EventBuilder::custom(base_spec).severity(0.4).build().unwrap();
let mid_event = EventBuilder::custom(mid_spec).severity(0.4).build().unwrap();
let base_salience = compute_base_salience(&base_event);
let mid_salience = compute_base_salience(&mid_event);
assert!(mid_salience > base_salience);
}
#[test]
fn direction_for_relationship_requires_trustee_match_for_entity_a() {
use crate::relationship::Relationship;
use crate::types::EntityId;
let trustor = EntityId::new("trustor_a").unwrap();
let trustee = EntityId::new("trustee_b").unwrap();
let unrelated = EntityId::new("unrelated").unwrap();
let relationship = Relationship::try_between(trustor.clone(), trustee.clone()).unwrap();
let direction = direction_for_relationship(&relationship, &trustor, &unrelated);
assert!(direction.is_none());
}
#[test]
fn direction_for_relationship_requires_trustee_match_for_entity_b() {
use crate::relationship::Relationship;
use crate::types::EntityId;
let trustor = EntityId::new("trustor_b").unwrap();
let trustee = EntityId::new("trustee_a").unwrap();
let unrelated = EntityId::new("unrelated").unwrap();
let relationship = Relationship::try_between(trustee.clone(), trustor.clone()).unwrap();
let direction = direction_for_relationship(&relationship, &trustor, &unrelated);
assert!(direction.is_none());
}
#[test]
fn trauma_events_have_higher_salience() {
let trauma_event = EventBuilder::new(EventType::ExperienceCombatMilitary)
.severity(0.7)
.build()
.unwrap();
let non_trauma_event = EventBuilder::new(EventType::AchieveGoalMajor)
.severity(0.7)
.build()
.unwrap();
let trauma_salience = compute_base_salience(&trauma_event);
let non_trauma_salience = compute_base_salience(&non_trauma_event);
assert!(trauma_salience > non_trauma_salience);
}
#[test]
fn custom_event_with_spec_processes_correctly() {
use crate::event::event_spec::{ChronicFlags, EventImpact, EventSpec, PermanenceValues};
let entity = create_human();
let custom_spec = EventSpec {
impact: EventImpact {
valence: 0.5,
arousal: 0.3,
..Default::default()
},
chronic: ChronicFlags::default(),
permanence: PermanenceValues::default(),
};
let event = EventBuilder::custom(custom_spec).severity(1.0).build().unwrap();
let interpreted = interpret_event(&event, &entity);
assert!(interpreted.valence_delta > 0.0);
assert!(interpreted.arousal_delta > 0.0);
}
#[test]
fn all_event_types_can_be_interpreted() {
let entity = create_human();
for event_type in EventType::all() {
let event = EventBuilder::new(event_type).severity(0.5).build().unwrap();
let interpreted = interpret_event(&event, &entity);
assert!(interpreted.spec_deltas.is_some());
}
}
#[test]
fn direction_for_relationship_returns_correct_direction() {
use crate::types::EntityId;
let a = EntityId::new("entity_a").unwrap();
let b = EntityId::new("entity_b").unwrap();
let c = EntityId::new("entity_c").unwrap();
let rel = Relationship::try_between(a.clone(), b.clone()).unwrap();
assert_eq!(direction_for_relationship(&rel, &a, &b), Some(Direction::AToB));
assert_eq!(direction_for_relationship(&rel, &b, &a), Some(Direction::BToA));
assert_eq!(direction_for_relationship(&rel, &c, &a), None);
}
#[test]
fn trust_attribution_override_unknown_returns_none() {
let event = EventBuilder::new(EventType::ReceiveSupportEmotional)
.trust_attribution(AttributionType::Unknown)
.severity(0.6)
.build()
.unwrap();
let attribution = compute_trust_attribution(&event, 0.5, 0.5, 0.5);
assert!(attribution.is_none());
}
#[test]
fn trust_attribution_override_specific_returns_some() {
let event = EventBuilder::new(EventType::ReceiveSupportEmotional)
.trust_attribution(AttributionType::Benevolence)
.severity(0.6)
.build()
.unwrap();
let attribution = compute_trust_attribution(&event, 0.0, 0.0, 0.0);
assert_eq!(attribution, Some(AttributionType::Benevolence));
}
#[test]
fn trust_attribution_falls_back_to_integrity_on_propensity() {
let event = EventBuilder::new(EventType::ReceiveSupportEmotional)
.severity(0.6)
.build()
.unwrap();
let attribution = compute_trust_attribution(&event, 0.0, 0.0, 0.2);
assert_eq!(attribution, Some(AttributionType::Integrity));
}
#[test]
fn trust_attribution_prefers_competence_then_benevolence() {
let event = EventBuilder::new(EventType::ReceiveSupportPractical)
.severity(0.6)
.build()
.unwrap();
let competence = compute_trust_attribution(&event, 0.2, 0.2, 0.2);
assert_eq!(competence, Some(AttributionType::Ability));
let benevolence = compute_trust_attribution(&event, 0.0, 0.2, 0.0);
assert_eq!(benevolence, Some(AttributionType::Benevolence));
}
#[test]
fn process_event_to_relationships_applies_domain_and_attribution() {
use crate::enums::Direction;
use crate::enums::EventPayload;
use crate::event::event_spec::{ChronicFlags, EventImpact, EventSpec, PermanenceValues};
use crate::relationship::Relationship;
use crate::types::{EntityId, Timestamp};
let source = EntityId::new("mentor").unwrap();
let target = EntityId::new("student").unwrap();
let mut relationships = vec![Relationship::try_between(target.clone(), source.clone()).unwrap()];
let custom_spec = EventSpec {
impact: EventImpact {
trustor_propensity: 0.4,
perceived_competence: 0.2,
..Default::default()
},
chronic: ChronicFlags::default(),
permanence: PermanenceValues::default(),
};
let event = EventBuilder::custom(custom_spec)
.source(source)
.target(target)
.payload(EventPayload::Achievement {
domain: LifeDomain::Work,
magnitude: 0.6,
})
.severity(1.0)
.build()
.unwrap();
process_event_to_relationships(
&event,
Timestamp::from_ymd_hms(2024, 1, 1, 0, 0, 0),
&mut relationships,
Some(AttributionType::Ability),
);
let history = relationships[0].antecedent_history(Direction::AToB);
assert_eq!(history.len(), 1);
assert_eq!(history[0].life_domain(), Some(LifeDomain::Work));
assert_eq!(history[0].attribution(), Some(AttributionType::Ability));
}
#[test]
fn process_event_to_relationships_handles_missing_domain_and_attribution() {
use crate::enums::Direction;
use crate::enums::EventPayload;
use crate::event::event_spec::{ChronicFlags, EventImpact, EventSpec, PermanenceValues};
use crate::relationship::Relationship;
use crate::types::{EntityId, Timestamp};
let source = EntityId::new("helper").unwrap();
let target = EntityId::new("learner").unwrap();
let mut relationships = vec![Relationship::try_between(target.clone(), source.clone()).unwrap()];
let custom_spec = EventSpec {
impact: EventImpact {
trustor_propensity: 0.4,
perceived_competence: 0.0,
..Default::default()
},
chronic: ChronicFlags::default(),
permanence: PermanenceValues::default(),
};
let event = EventBuilder::custom(custom_spec)
.source(source)
.target(target)
.payload(EventPayload::Empty)
.severity(1.0)
.build()
.unwrap();
process_event_to_relationships(
&event,
Timestamp::from_ymd_hms(2024, 1, 1, 0, 0, 0),
&mut relationships,
None,
);
let history = relationships[0].antecedent_history(Direction::AToB);
assert_eq!(history.len(), 1);
assert_eq!(history[0].life_domain(), None);
assert_eq!(history[0].attribution(), None);
}
#[test]
fn trust_willingness_defaults_to_average_for_other_events() {
let decision = TrustDecision::new(0.2, 0.6, 0.4, 0.5, 0.7);
let avg = (0.2 + 0.6 + 0.4) / 3.0;
let willingness = trust_willingness_for_event(EventType::AchieveGoalMajor, &decision);
assert!((willingness - avg).abs() < f32::EPSILON);
}
#[test]
fn trust_willingness_uses_support_for_support_events() {
let decision = TrustDecision::new(0.2, 0.6, 0.4, 0.5, 0.7);
let support = decision.support_willingness();
let willingness =
trust_willingness_for_event(EventType::ReceiveSupportEmotional, &decision);
assert!((willingness - support).abs() < f32::EPSILON);
}
#[test]
fn trust_willingness_uses_disclosure_for_betrayal_events() {
let decision = TrustDecision::new(0.2, 0.6, 0.4, 0.5, 0.7);
let disclosure = decision.disclosure_willingness();
let willingness =
trust_willingness_for_event(EventType::ExperienceBetrayalTrust, &decision);
assert!((willingness - disclosure).abs() < f32::EPSILON);
}
#[test]
fn apply_all_permanent_delta_branches() {
use crate::event::event_spec::{ChronicFlags, EventImpact, EventSpec, PermanenceValues};
let mut entity = create_human();
let custom_spec = EventSpec {
impact: EventImpact {
valence: 0.1,
arousal: 0.1,
dominance: 0.1,
loneliness: 0.1,
prc: 0.1,
perceived_liability: 0.1,
self_hate: 0.1,
purpose: 0.1,
acquired_capability: 0.1, interpersonal_hopelessness: 0.1,
self_worth: 0.1,
stress: 0.1,
fatigue: 0.1,
grievance: 0.1,
..Default::default()
},
chronic: ChronicFlags::default(),
permanence: PermanenceValues {
valence: 1.0,
arousal: 1.0,
dominance: 1.0,
loneliness: 1.0,
prc: 1.0,
perceived_liability: 1.0,
self_hate: 1.0,
purpose: 1.0,
interpersonal_hopelessness: 1.0,
self_worth: 1.0,
stress: 1.0,
fatigue: 1.0,
grievance: 1.0,
..Default::default()
},
};
let event = EventBuilder::custom(custom_spec).severity(1.0).build().unwrap();
process_event(&event, &mut entity);
let state = entity.individual_state();
assert!(state.mood().valence_base().abs() > f32::EPSILON);
assert!(state.mental_health().acquired_capability().base().abs() > f32::EPSILON);
}
#[test]
fn apply_all_acute_delta_branches() {
use crate::event::event_spec::{ChronicFlags, EventImpact, EventSpec, PermanenceValues};
let mut entity = create_human();
let custom_spec = EventSpec {
impact: EventImpact {
valence: 0.2,
arousal: 0.2,
dominance: 0.2,
loneliness: 0.2,
prc: 0.2,
perceived_liability: 0.2,
self_hate: 0.2,
purpose: 0.2,
interpersonal_hopelessness: 0.2,
self_worth: 0.2,
stress: 0.2,
fatigue: 0.2,
grievance: 0.2,
..Default::default()
},
chronic: ChronicFlags::default(),
permanence: PermanenceValues::default(), };
let event = EventBuilder::custom(custom_spec).severity(1.0).build().unwrap();
process_event(&event, &mut entity);
let state = entity.individual_state();
assert!(state.mood().valence_delta().abs() > f32::EPSILON);
}
#[test]
fn apply_all_chronic_delta_branches() {
use crate::event::event_spec::{ChronicFlags, EventImpact, EventSpec, PermanenceValues};
let mut entity = create_human();
let custom_spec = EventSpec {
impact: EventImpact {
valence: 0.3,
arousal: 0.3,
dominance: 0.3,
loneliness: 0.3,
prc: 0.3,
perceived_liability: 0.3,
self_hate: 0.3,
purpose: 0.3,
interpersonal_hopelessness: 0.3,
self_worth: 0.3,
stress: 0.3,
fatigue: 0.3,
grievance: 0.3,
..Default::default()
},
chronic: ChronicFlags {
valence: true,
arousal: true,
dominance: true,
loneliness: true,
prc: true,
perceived_liability: true,
self_hate: true,
purpose: true,
interpersonal_hopelessness: true,
self_worth: true,
stress: true,
fatigue: true,
grievance: true,
..Default::default()
},
permanence: PermanenceValues::default(),
};
let event = EventBuilder::custom(custom_spec).severity(1.0).build().unwrap();
process_event(&event, &mut entity);
let state = entity.individual_state();
let chronic_stress = state.needs().stress().chronic_delta().abs() > f32::EPSILON;
let chronic_valence = state.mood().valence().chronic_delta().abs() > f32::EPSILON;
let chronic_count = (chronic_stress as u8) + (chronic_valence as u8);
assert!(chronic_count > 0);
}
#[test]
fn scaled_by_with_state_deltas() {
let event = EventBuilder::new(EventType::EndRelationshipRomantic)
.severity(0.8)
.build()
.unwrap();
let interpreted = InterpretedEvent {
event: event.clone(),
original_event: event.id().clone(),
attribution: Attribution::Unknown,
trust_attribution: None,
valence_delta: 0.0,
arousal_delta: 0.0,
dominance_delta: 0.0,
loneliness_delta: 0.0,
prc_delta: 0.0,
perceived_liability_delta: 0.0,
self_hate_delta: 0.0,
acquired_capability_delta: 0.0,
interpersonal_hopelessness_delta: 0.0,
salience: 0.0,
perceived_severity: 0.0,
memory_salience: 0.0,
state_deltas: vec![(StatePath::Mood(MoodPath::Valence), -0.5)],
spec_deltas: None,
};
let scaled = interpreted.scaled_by(0.5);
assert_eq!(scaled.state_deltas.len(), 1);
assert!((scaled.state_deltas[0].1 - (-0.25)).abs() < 0.01);
}
#[test]
fn trust_modulation_no_relationship_returns_one() {
let event = EventBuilder::new(EventType::ExperienceBetrayalTrust)
.severity(0.8)
.build()
.unwrap();
let context = TrustContext::new();
let factor = compute_trust_modulation_factor(
&event,
None,
Direction::AToB,
0.5,
&context,
LifeDomain::Relationship,
);
assert!((factor - 1.0).abs() < f64::EPSILON);
}
#[test]
fn trust_modulation_high_trust_amplifies_negative_events() {
use crate::types::EntityId;
let source = EntityId::new("betrayer").unwrap();
let target = EntityId::new("victim").unwrap();
let mut rel = Relationship::try_between(target.clone(), source.clone()).unwrap();
let tw = rel.trustworthiness_mut(Direction::AToB);
tw.add_competence_delta(0.8);
tw.add_benevolence_delta(0.8);
tw.add_integrity_delta(0.8);
let event = EventBuilder::new(EventType::ExperienceBetrayalTrust)
.source(source)
.target(target)
.severity(0.8)
.build()
.unwrap();
let context = TrustContext::new();
let factor =
compute_trust_modulation_factor(
&event,
Some(&rel),
Direction::AToB,
0.7,
&context,
LifeDomain::Relationship,
);
assert!(factor > 1.0);
}
#[test]
fn trust_modulation_negative_when_trustor_propensity_negative() {
use crate::event::event_spec::{ChronicFlags, EventImpact, EventSpec, PermanenceValues};
use crate::types::EntityId;
let source = EntityId::new("source").unwrap();
let target = EntityId::new("target").unwrap();
let mut rel = Relationship::try_between(target.clone(), source.clone()).unwrap();
let tw = rel.trustworthiness_mut(Direction::AToB);
tw.add_competence_delta(0.6);
tw.add_benevolence_delta(0.6);
tw.add_integrity_delta(0.6);
let negative_spec = EventSpec {
impact: EventImpact {
valence: 0.2,
trustor_propensity: -0.2,
prc: 0.0,
..Default::default()
},
chronic: ChronicFlags::default(),
permanence: PermanenceValues::default(),
};
let positive_spec = EventSpec {
impact: EventImpact {
valence: 0.2,
trustor_propensity: 0.2,
prc: 0.0,
..Default::default()
},
chronic: ChronicFlags::default(),
permanence: PermanenceValues::default(),
};
let negative_event = EventBuilder::custom(negative_spec)
.source(source.clone())
.target(target.clone())
.severity(0.6)
.build()
.unwrap();
let positive_event = EventBuilder::custom(positive_spec)
.source(source)
.target(target)
.severity(0.6)
.build()
.unwrap();
let context = TrustContext::new();
let negative_factor = compute_trust_modulation_factor(
&negative_event,
Some(&rel),
Direction::AToB,
0.6,
&context,
LifeDomain::Relationship,
);
let positive_factor = compute_trust_modulation_factor(
&positive_event,
Some(&rel),
Direction::AToB,
0.6,
&context,
LifeDomain::Relationship,
);
assert!(negative_factor > positive_factor);
}
#[test]
fn trust_modulation_negative_when_prc_is_low() {
use crate::event::event_spec::{ChronicFlags, EventImpact, EventSpec, PermanenceValues};
use crate::types::EntityId;
let source = EntityId::new("source_prc").unwrap();
let target = EntityId::new("target_prc").unwrap();
let mut rel = Relationship::try_between(target.clone(), source.clone()).unwrap();
let tw = rel.trustworthiness_mut(Direction::AToB);
tw.add_competence_delta(0.6);
tw.add_benevolence_delta(0.6);
tw.add_integrity_delta(0.6);
let negative_spec = EventSpec {
impact: EventImpact {
valence: 0.2,
trustor_propensity: 0.0,
prc: -0.2,
..Default::default()
},
chronic: ChronicFlags::default(),
permanence: PermanenceValues::default(),
};
let positive_spec = EventSpec {
impact: EventImpact {
valence: 0.2,
trustor_propensity: 0.0,
prc: 0.0,
..Default::default()
},
chronic: ChronicFlags::default(),
permanence: PermanenceValues::default(),
};
let negative_event = EventBuilder::custom(negative_spec)
.source(source.clone())
.target(target.clone())
.severity(0.6)
.build()
.unwrap();
let positive_event = EventBuilder::custom(positive_spec)
.source(source)
.target(target)
.severity(0.6)
.build()
.unwrap();
let context = TrustContext::new();
let negative_factor = compute_trust_modulation_factor(
&negative_event,
Some(&rel),
Direction::AToB,
0.6,
&context,
LifeDomain::Relationship,
);
let positive_factor = compute_trust_modulation_factor(
&positive_event,
Some(&rel),
Direction::AToB,
0.6,
&context,
LifeDomain::Relationship,
);
assert!(negative_factor > positive_factor);
}
#[test]
fn trust_modulation_negative_when_valence_is_negative() {
use crate::event::event_spec::{ChronicFlags, EventImpact, EventSpec, PermanenceValues};
use crate::types::EntityId;
let source = EntityId::new("source_valence").unwrap();
let target = EntityId::new("target_valence").unwrap();
let mut rel = Relationship::try_between(target.clone(), source.clone()).unwrap();
let tw = rel.trustworthiness_mut(Direction::AToB);
tw.add_competence_delta(0.6);
tw.add_benevolence_delta(0.6);
tw.add_integrity_delta(0.6);
let negative_spec = EventSpec {
impact: EventImpact {
valence: -0.2,
trustor_propensity: 0.0,
prc: 0.0,
..Default::default()
},
chronic: ChronicFlags::default(),
permanence: PermanenceValues::default(),
};
let positive_spec = EventSpec {
impact: EventImpact {
valence: 0.2,
trustor_propensity: 0.0,
prc: 0.0,
..Default::default()
},
chronic: ChronicFlags::default(),
permanence: PermanenceValues::default(),
};
let negative_event = EventBuilder::custom(negative_spec)
.source(source.clone())
.target(target.clone())
.severity(0.6)
.build()
.unwrap();
let positive_event = EventBuilder::custom(positive_spec)
.source(source)
.target(target)
.severity(0.6)
.build()
.unwrap();
let context = TrustContext::new();
let negative_factor = compute_trust_modulation_factor(
&negative_event,
Some(&rel),
Direction::AToB,
0.6,
&context,
LifeDomain::Relationship,
);
let positive_factor = compute_trust_modulation_factor(
&positive_event,
Some(&rel),
Direction::AToB,
0.6,
&context,
LifeDomain::Relationship,
);
assert!(negative_factor > positive_factor);
}
#[test]
fn trust_modulation_low_trust_gives_neutral_factor() {
use crate::types::EntityId;
let source = EntityId::new("stranger").unwrap();
let target = EntityId::new("person").unwrap();
let rel = Relationship::try_between(target.clone(), source.clone()).unwrap();
let event = EventBuilder::new(EventType::ExperienceBetrayalTrust)
.source(source)
.target(target)
.severity(0.8)
.build()
.unwrap();
let context = TrustContext::new();
let factor =
compute_trust_modulation_factor(
&event,
Some(&rel),
Direction::AToB,
0.5,
&context,
LifeDomain::Relationship,
);
assert!(factor < 1.3);
}
#[test]
fn trust_modulation_positive_event_from_trusted_source() {
use crate::types::EntityId;
let source = EntityId::new("friend").unwrap();
let target = EntityId::new("person").unwrap();
let mut rel = Relationship::try_between(target.clone(), source.clone()).unwrap();
let tw = rel.trustworthiness_mut(Direction::AToB);
tw.add_competence_delta(0.7);
tw.add_benevolence_delta(0.7);
tw.add_integrity_delta(0.7);
let event = EventBuilder::new(EventType::ReceiveSupportEmotional)
.source(source)
.target(target)
.severity(0.7)
.build()
.unwrap();
let context = TrustContext::new();
let factor =
compute_trust_modulation_factor(
&event,
Some(&rel),
Direction::AToB,
0.6,
&context,
LifeDomain::Relationship,
);
assert!(factor > 1.0);
}
#[test]
fn trust_modulation_factor_clamped_to_range() {
use crate::types::EntityId;
let source = EntityId::new("betrayer").unwrap();
let target = EntityId::new("victim").unwrap();
let mut rel = Relationship::try_between(target.clone(), source.clone()).unwrap();
let tw = rel.trustworthiness_mut(Direction::AToB);
tw.add_competence_delta(1.0);
tw.add_benevolence_delta(1.0);
tw.add_integrity_delta(1.0);
let event = EventBuilder::new(EventType::ExperienceBetrayalTrust)
.source(source)
.target(target)
.severity(1.0)
.build()
.unwrap();
let context = TrustContext::new();
let factor =
compute_trust_modulation_factor(
&event,
Some(&rel),
Direction::AToB,
0.7,
&context,
LifeDomain::Relationship,
);
assert!(factor >= 0.5);
assert!(factor <= 2.0);
}
}