use crate::state::StateValue;
use crate::types::Duration;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Disposition {
impulse_control: StateValue,
empathy: StateValue,
aggression: StateValue,
grievance: StateValue,
reactance: StateValue,
#[serde(alias = "trust_propensity")]
trustor_propensity: StateValue,
}
impl Disposition {
const IMPULSE_CONTROL_DECAY_HALF_LIFE: Duration = Duration::months(1);
const EMPATHY_DECAY_HALF_LIFE: Duration = Duration::months(1);
const AGGRESSION_DECAY_HALF_LIFE: Duration = Duration::months(1);
const GRIEVANCE_DECAY_HALF_LIFE: Duration = Duration::weeks(1);
const REACTANCE_DECAY_HALF_LIFE: Duration = Duration::weeks(1);
const TRUST_PROPENSITY_DECAY_HALF_LIFE: Duration = Duration::years(1);
#[must_use]
pub fn new() -> Self {
Disposition {
impulse_control: StateValue::new(0.6)
.with_bounds(0.0, 1.0)
.with_decay_half_life(Self::IMPULSE_CONTROL_DECAY_HALF_LIFE),
empathy: StateValue::new(0.7)
.with_bounds(0.0, 1.0)
.with_decay_half_life(Self::EMPATHY_DECAY_HALF_LIFE),
aggression: StateValue::new(0.2)
.with_bounds(0.0, 1.0)
.with_decay_half_life(Self::AGGRESSION_DECAY_HALF_LIFE),
grievance: StateValue::new(0.0)
.with_bounds(0.0, 1.0)
.with_decay_half_life(Self::GRIEVANCE_DECAY_HALF_LIFE),
reactance: StateValue::new(0.0)
.with_bounds(0.0, 1.0)
.with_decay_half_life(Self::REACTANCE_DECAY_HALF_LIFE),
trustor_propensity: StateValue::new(0.5)
.with_bounds(0.0, 1.0)
.with_decay_half_life(Self::TRUST_PROPENSITY_DECAY_HALF_LIFE),
}
}
#[must_use]
pub fn with_impulse_control_base(mut self, value: f32) -> Self {
self.impulse_control.set_base(value);
self
}
#[must_use]
pub fn with_empathy_base(mut self, value: f32) -> Self {
self.empathy.set_base(value);
self
}
#[must_use]
pub fn with_aggression_base(mut self, value: f32) -> Self {
self.aggression.set_base(value);
self
}
#[must_use]
pub fn with_grievance_base(mut self, value: f32) -> Self {
self.grievance.set_base(value);
self
}
#[must_use]
pub fn with_reactance_base(mut self, value: f32) -> Self {
self.reactance.set_base(value);
self
}
#[must_use]
pub fn with_trustor_propensity_base(mut self, value: f32) -> Self {
self.trustor_propensity.set_base(value);
self
}
#[must_use]
pub fn impulse_control_effective(&self) -> f32 {
self.impulse_control.effective()
}
#[must_use]
pub fn empathy_effective(&self) -> f32 {
self.empathy.effective()
}
#[must_use]
pub fn aggression_effective(&self) -> f32 {
self.aggression.effective()
}
#[must_use]
pub fn grievance_effective(&self) -> f32 {
self.grievance.effective()
}
#[must_use]
pub fn reactance_effective(&self) -> f32 {
self.reactance.effective()
}
#[must_use]
pub fn trustor_propensity_effective(&self) -> f32 {
self.trustor_propensity.effective()
}
#[must_use]
pub fn impulse_control(&self) -> &StateValue {
&self.impulse_control
}
#[must_use]
pub fn empathy(&self) -> &StateValue {
&self.empathy
}
#[must_use]
pub fn aggression(&self) -> &StateValue {
&self.aggression
}
#[must_use]
pub fn grievance(&self) -> &StateValue {
&self.grievance
}
#[must_use]
pub fn reactance(&self) -> &StateValue {
&self.reactance
}
#[must_use]
pub fn trustor_propensity(&self) -> &StateValue {
&self.trustor_propensity
}
pub fn impulse_control_mut(&mut self) -> &mut StateValue {
&mut self.impulse_control
}
pub fn empathy_mut(&mut self) -> &mut StateValue {
&mut self.empathy
}
pub fn aggression_mut(&mut self) -> &mut StateValue {
&mut self.aggression
}
pub fn grievance_mut(&mut self) -> &mut StateValue {
&mut self.grievance
}
pub fn reactance_mut(&mut self) -> &mut StateValue {
&mut self.reactance
}
pub fn trustor_propensity_mut(&mut self) -> &mut StateValue {
&mut self.trustor_propensity
}
pub fn add_impulse_control_delta(&mut self, amount: f32) {
self.impulse_control.add_delta(amount);
}
pub fn add_empathy_delta(&mut self, amount: f32) {
self.empathy.add_delta(amount);
}
pub fn add_aggression_delta(&mut self, amount: f32) {
self.aggression.add_delta(amount);
}
pub fn add_grievance_delta(&mut self, amount: f32) {
self.grievance.add_delta(amount);
}
pub fn add_reactance_delta(&mut self, amount: f32) {
self.reactance.add_delta(amount);
}
pub fn add_trustor_propensity_delta(&mut self, amount: f32) {
self.trustor_propensity.add_delta(amount);
}
pub fn apply_decay(&mut self, elapsed: Duration) {
self.impulse_control.apply_decay(elapsed);
self.empathy.apply_decay(elapsed);
self.aggression.apply_decay(elapsed);
self.grievance.apply_decay(elapsed);
self.reactance.apply_decay(elapsed);
self.trustor_propensity.apply_decay(elapsed);
}
pub fn reset_deltas(&mut self) {
self.impulse_control.reset_delta();
self.empathy.reset_delta();
self.aggression.reset_delta();
self.grievance.reset_delta();
self.reactance.reset_delta();
self.trustor_propensity.reset_delta();
}
}
impl Default for Disposition {
fn default() -> Self {
Disposition::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_creates_healthy_defaults() {
let disposition = Disposition::new();
assert!(disposition.impulse_control_effective() > 0.5);
assert!(disposition.empathy_effective() > 0.5);
assert!(disposition.trustor_propensity_effective() >= 0.5);
assert!(disposition.aggression_effective() < 0.5);
assert!(disposition.grievance_effective() < 0.5);
assert!(disposition.reactance_effective() < 0.5);
}
#[test]
fn builder_methods_work() {
let disposition = Disposition::new()
.with_impulse_control_base(0.3)
.with_empathy_base(0.4)
.with_aggression_base(0.5)
.with_grievance_base(0.6)
.with_reactance_base(0.7)
.with_trustor_propensity_base(0.8);
assert!((disposition.impulse_control().base() - 0.3).abs() < f32::EPSILON);
assert!((disposition.empathy().base() - 0.4).abs() < f32::EPSILON);
assert!((disposition.aggression().base() - 0.5).abs() < f32::EPSILON);
assert!((disposition.grievance().base() - 0.6).abs() < f32::EPSILON);
assert!((disposition.reactance().base() - 0.7).abs() < f32::EPSILON);
assert!((disposition.trustor_propensity().base() - 0.8).abs() < f32::EPSILON);
}
#[test]
fn delta_modifiers_work() {
let mut disposition = Disposition::new();
disposition.add_grievance_delta(0.3);
assert!((disposition.grievance().delta() - 0.3).abs() < f32::EPSILON);
disposition.add_trustor_propensity_delta(-0.2);
assert!((disposition.trustor_propensity().delta() - (-0.2)).abs() < f32::EPSILON);
}
#[test]
fn grievance_decays_weekly() {
let mut disposition = Disposition::new();
disposition.add_grievance_delta(0.8);
disposition.apply_decay(Duration::weeks(1));
assert!((disposition.grievance().delta() - 0.4).abs() < 0.01);
}
#[test]
fn trustor_propensity_decays_yearly() {
let mut disposition = Disposition::new();
disposition.add_trustor_propensity_delta(0.4);
disposition.apply_decay(Duration::years(1));
assert!((disposition.trustor_propensity().delta() - 0.2).abs() < 0.01);
}
#[test]
fn trustor_propensity_stable_over_months() {
let mut disposition = Disposition::new();
disposition.add_trustor_propensity_delta(0.4);
disposition.apply_decay(Duration::months(1));
assert!(disposition.trustor_propensity().delta() > 0.35);
}
#[test]
fn reset_deltas_clears_all() {
let mut disposition = Disposition::new();
disposition.add_grievance_delta(0.5);
disposition.add_reactance_delta(0.3);
disposition.reset_deltas();
assert!(disposition.grievance().delta().abs() < f32::EPSILON);
assert!(disposition.reactance().delta().abs() < f32::EPSILON);
}
#[test]
fn mutable_references_work() {
let mut disposition = Disposition::new();
disposition.aggression_mut().add_delta(0.2);
assert!((disposition.aggression().delta() - 0.2).abs() < f32::EPSILON);
}
#[test]
fn default_is_new() {
let disposition = Disposition::default();
assert!(disposition.empathy_effective() > 0.5);
}
#[test]
fn clone_and_equality() {
let d1 = Disposition::new().with_aggression_base(0.5);
let d2 = d1.clone();
assert_eq!(d1, d2);
}
#[test]
fn debug_format() {
let disposition = Disposition::new();
let debug = format!("{:?}", disposition);
assert!(debug.contains("Disposition"));
}
#[test]
fn trustor_propensity_is_dispositional() {
let disposition = Disposition::new().with_trustor_propensity_base(0.8);
assert!(disposition.trustor_propensity_effective() > 0.7);
}
#[test]
fn all_delta_modifiers_work() {
let mut d = Disposition::new();
d.add_impulse_control_delta(0.1);
assert!((d.impulse_control().delta() - 0.1).abs() < f32::EPSILON);
d.add_empathy_delta(0.2);
assert!((d.empathy().delta() - 0.2).abs() < f32::EPSILON);
d.add_aggression_delta(0.3);
assert!((d.aggression().delta() - 0.3).abs() < f32::EPSILON);
d.add_reactance_delta(0.4);
assert!((d.reactance().delta() - 0.4).abs() < f32::EPSILON);
}
#[test]
fn all_mutable_refs_work() {
let mut d = Disposition::new();
d.impulse_control_mut().add_delta(0.1);
assert!((d.impulse_control().delta() - 0.1).abs() < f32::EPSILON);
d.empathy_mut().add_delta(0.2);
assert!((d.empathy().delta() - 0.2).abs() < f32::EPSILON);
d.grievance_mut().add_delta(0.3);
assert!((d.grievance().delta() - 0.3).abs() < f32::EPSILON);
d.reactance_mut().add_delta(0.4);
assert!((d.reactance().delta() - 0.4).abs() < f32::EPSILON);
d.trustor_propensity_mut().add_delta(-0.1);
assert!((d.trustor_propensity().delta() - (-0.1)).abs() < f32::EPSILON);
}
}