use crate::enums::SharedPath;
use crate::state::StateValue;
use crate::types::Duration;
const AFFINITY_DECAY_HALF_LIFE: Duration = Duration::days(14);
const RESPECT_DECAY_HALF_LIFE: Duration = Duration::days(21);
const TENSION_DECAY_HALF_LIFE: Duration = Duration::days(7);
const INTIMACY_DECAY_HALF_LIFE: Duration = Duration::days(30);
#[derive(Debug, Clone, PartialEq)]
pub struct SharedDimensions {
affinity: StateValue,
respect: StateValue,
tension: StateValue,
intimacy: StateValue,
history: StateValue,
}
impl SharedDimensions {
#[must_use]
pub fn new() -> Self {
SharedDimensions {
affinity: StateValue::new(0.1)
.with_bounds(0.0, 1.0)
.with_decay_half_life(AFFINITY_DECAY_HALF_LIFE),
respect: StateValue::new(0.2)
.with_bounds(0.0, 1.0)
.with_decay_half_life(RESPECT_DECAY_HALF_LIFE),
tension: StateValue::new(0.0)
.with_bounds(0.0, 1.0)
.with_decay_half_life(TENSION_DECAY_HALF_LIFE),
intimacy: StateValue::new(0.0)
.with_bounds(0.0, 1.0)
.with_decay_half_life(INTIMACY_DECAY_HALF_LIFE),
history: StateValue::new_no_decay(0.0).with_bounds(0.0, 1.0),
}
}
#[must_use]
pub fn affinity_effective(&self) -> f32 {
self.affinity.effective()
}
#[must_use]
pub fn respect_effective(&self) -> f32 {
self.respect.effective()
}
#[must_use]
pub fn tension_effective(&self) -> f32 {
self.tension.effective()
}
#[must_use]
pub fn intimacy_effective(&self) -> f32 {
self.intimacy.effective()
}
#[must_use]
pub fn history_effective(&self) -> f32 {
self.history.effective()
}
#[must_use]
pub fn affinity(&self) -> &StateValue {
&self.affinity
}
#[must_use]
pub fn respect(&self) -> &StateValue {
&self.respect
}
#[must_use]
pub fn tension(&self) -> &StateValue {
&self.tension
}
#[must_use]
pub fn intimacy(&self) -> &StateValue {
&self.intimacy
}
#[must_use]
pub fn history(&self) -> &StateValue {
&self.history
}
pub fn affinity_mut(&mut self) -> &mut StateValue {
&mut self.affinity
}
pub fn respect_mut(&mut self) -> &mut StateValue {
&mut self.respect
}
pub fn tension_mut(&mut self) -> &mut StateValue {
&mut self.tension
}
pub fn intimacy_mut(&mut self) -> &mut StateValue {
&mut self.intimacy
}
pub fn history_mut(&mut self) -> &mut StateValue {
&mut self.history
}
#[must_use]
pub fn get(&self, path: SharedPath) -> &StateValue {
match path {
SharedPath::Affinity => &self.affinity,
SharedPath::Respect => &self.respect,
SharedPath::Tension => &self.tension,
SharedPath::Intimacy => &self.intimacy,
SharedPath::History => &self.history,
}
}
pub fn get_mut(&mut self, path: SharedPath) -> &mut StateValue {
match path {
SharedPath::Affinity => &mut self.affinity,
SharedPath::Respect => &mut self.respect,
SharedPath::Tension => &mut self.tension,
SharedPath::Intimacy => &mut self.intimacy,
SharedPath::History => &mut self.history,
}
}
pub fn add_affinity_delta(&mut self, amount: f32) {
self.affinity.add_delta(amount);
}
pub fn add_respect_delta(&mut self, amount: f32) {
self.respect.add_delta(amount);
}
pub fn add_tension_delta(&mut self, amount: f32) {
self.tension.add_delta(amount);
}
pub fn add_intimacy_delta(&mut self, amount: f32) {
self.intimacy.add_delta(amount);
}
pub fn add_history_delta(&mut self, amount: f32) {
if amount > 0.0 {
self.history.add_delta(amount);
}
}
pub fn add_delta(&mut self, path: SharedPath, amount: f32) {
match path {
SharedPath::Affinity => self.affinity.add_delta(amount),
SharedPath::Respect => self.respect.add_delta(amount),
SharedPath::Tension => self.tension.add_delta(amount),
SharedPath::Intimacy => self.intimacy.add_delta(amount),
SharedPath::History => {
if amount > 0.0 {
self.history.add_delta(amount);
}
}
}
}
pub fn apply_decay(&mut self, elapsed: Duration) {
let history = self.history_effective().clamp(0.0, 1.0) as f64;
let decay_scale = (1.0 - history).max(0.0);
let scaled_seconds = (elapsed.as_seconds() as f64 * decay_scale) as u64;
let scaled_elapsed = Duration::seconds(scaled_seconds);
self.affinity.apply_decay(scaled_elapsed);
self.respect.apply_decay(scaled_elapsed);
self.tension.apply_decay(scaled_elapsed);
self.intimacy.apply_decay(scaled_elapsed);
}
pub fn reset_deltas(&mut self) {
self.affinity.reset_delta();
self.respect.reset_delta();
self.tension.reset_delta();
self.intimacy.reset_delta();
}
}
impl Default for SharedDimensions {
fn default() -> Self {
SharedDimensions::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_creates_default_values() {
let shared = SharedDimensions::new();
assert!((shared.affinity_effective() - 0.1).abs() < f32::EPSILON);
assert!((shared.respect_effective() - 0.2).abs() < f32::EPSILON);
assert!(shared.tension_effective().abs() < f32::EPSILON);
assert!(shared.intimacy_effective().abs() < f32::EPSILON);
assert!(shared.history_effective().abs() < f32::EPSILON);
}
#[test]
fn add_affinity_delta() {
let mut shared = SharedDimensions::new();
shared.add_affinity_delta(0.3);
assert!((shared.affinity().delta() - 0.3).abs() < f32::EPSILON);
}
#[test]
fn add_respect_delta() {
let mut shared = SharedDimensions::new();
shared.add_respect_delta(0.2);
assert!((shared.respect().delta() - 0.2).abs() < f32::EPSILON);
}
#[test]
fn add_tension_delta() {
let mut shared = SharedDimensions::new();
shared.add_tension_delta(0.4);
assert!((shared.tension().delta() - 0.4).abs() < f32::EPSILON);
}
#[test]
fn add_intimacy_delta() {
let mut shared = SharedDimensions::new();
shared.add_intimacy_delta(0.2);
assert!((shared.intimacy().delta() - 0.2).abs() < f32::EPSILON);
}
#[test]
fn add_history_delta_positive() {
let mut shared = SharedDimensions::new();
shared.add_history_delta(0.1);
assert!((shared.history().delta() - 0.1).abs() < f32::EPSILON);
}
#[test]
fn add_history_delta_negative_ignored() {
let mut shared = SharedDimensions::new();
shared.add_history_delta(0.2);
shared.add_history_delta(-0.1); assert!((shared.history().delta() - 0.2).abs() < f32::EPSILON);
}
#[test]
fn add_delta_by_path() {
let mut shared = SharedDimensions::new();
shared.add_delta(SharedPath::Affinity, 0.1);
shared.add_delta(SharedPath::Respect, 0.2);
shared.add_delta(SharedPath::Tension, 0.3);
shared.add_delta(SharedPath::Intimacy, 0.4);
shared.add_delta(SharedPath::History, 0.5);
assert!((shared.affinity().delta() - 0.1).abs() < f32::EPSILON);
assert!((shared.respect().delta() - 0.2).abs() < f32::EPSILON);
assert!((shared.tension().delta() - 0.3).abs() < f32::EPSILON);
assert!((shared.intimacy().delta() - 0.4).abs() < f32::EPSILON);
assert!((shared.history().delta() - 0.5).abs() < f32::EPSILON);
}
#[test]
fn add_delta_history_negative_ignored() {
let mut shared = SharedDimensions::new();
shared.add_delta(SharedPath::History, -0.1);
assert!(shared.history().delta().abs() < f32::EPSILON);
}
#[test]
fn get_by_path() {
let shared = SharedDimensions::new();
assert!((shared.get(SharedPath::Affinity).effective() - 0.1).abs() < f32::EPSILON);
assert!((shared.get(SharedPath::Respect).effective() - 0.2).abs() < f32::EPSILON);
}
#[test]
fn get_mut_by_path() {
let mut shared = SharedDimensions::new();
shared.get_mut(SharedPath::Affinity).add_delta(0.1);
assert!((shared.affinity().delta() - 0.1).abs() < f32::EPSILON);
}
#[test]
fn get_mut_by_path_updates_all_dimensions() {
let mut shared = SharedDimensions::new();
shared.get_mut(SharedPath::Respect).add_delta(0.2);
shared.get_mut(SharedPath::Tension).add_delta(0.3);
shared.get_mut(SharedPath::Intimacy).add_delta(0.4);
shared.get_mut(SharedPath::History).add_delta(0.5);
assert!((shared.respect().delta() - 0.2).abs() < f32::EPSILON);
assert!((shared.tension().delta() - 0.3).abs() < f32::EPSILON);
assert!((shared.intimacy().delta() - 0.4).abs() < f32::EPSILON);
assert!((shared.history().delta() - 0.5).abs() < f32::EPSILON);
}
#[test]
fn affinity_decays_over_14_days() {
let mut shared = SharedDimensions::new();
shared.add_affinity_delta(0.4);
shared.apply_decay(Duration::days(14));
assert!((shared.affinity().delta() - 0.2).abs() < 0.01);
}
#[test]
fn tension_decays_over_7_days() {
let mut shared = SharedDimensions::new();
shared.add_tension_delta(0.4);
shared.apply_decay(Duration::days(7));
assert!((shared.tension().delta() - 0.2).abs() < 0.01);
}
#[test]
fn respect_decays_over_21_days() {
let mut shared = SharedDimensions::new();
shared.add_respect_delta(0.4);
shared.apply_decay(Duration::days(21));
assert!((shared.respect().delta() - 0.2).abs() < 0.01);
}
#[test]
fn intimacy_decays_over_30_days() {
let mut shared = SharedDimensions::new();
shared.add_intimacy_delta(0.4);
shared.apply_decay(Duration::days(30));
assert!((shared.intimacy().delta() - 0.2).abs() < 0.01);
}
#[test]
fn history_never_decays() {
let mut shared = SharedDimensions::new();
shared.add_history_delta(0.4);
shared.apply_decay(Duration::years(10));
assert!((shared.history().delta() - 0.4).abs() < f32::EPSILON);
}
#[test]
fn history_scales_decay() {
let mut shared = SharedDimensions::new();
shared.add_affinity_delta(1.0);
shared.history_mut().set_base(0.5);
shared.apply_decay(Duration::days(14));
assert!(shared.affinity().delta() > 0.6);
}
#[test]
fn history_at_one_stops_decay() {
let mut shared = SharedDimensions::new();
shared.add_tension_delta(0.5);
shared.history_mut().set_base(1.0);
shared.apply_decay(Duration::days(30));
assert!((shared.tension().delta() - 0.5).abs() < f32::EPSILON);
}
#[test]
fn reset_deltas_does_not_reset_history() {
let mut shared = SharedDimensions::new();
shared.add_affinity_delta(0.1);
shared.add_history_delta(0.2);
shared.reset_deltas();
assert!(shared.affinity().delta().abs() < f32::EPSILON);
assert!((shared.history().delta() - 0.2).abs() < f32::EPSILON);
}
#[test]
fn mutable_references_work() {
let mut shared = SharedDimensions::new();
shared.affinity_mut().add_delta(0.1);
shared.respect_mut().add_delta(0.2);
shared.tension_mut().add_delta(0.3);
shared.intimacy_mut().add_delta(0.4);
shared.history_mut().add_delta(0.5);
assert!((shared.affinity().delta() - 0.1).abs() < f32::EPSILON);
assert!((shared.respect().delta() - 0.2).abs() < f32::EPSILON);
assert!((shared.tension().delta() - 0.3).abs() < f32::EPSILON);
assert!((shared.intimacy().delta() - 0.4).abs() < f32::EPSILON);
assert!((shared.history().delta() - 0.5).abs() < f32::EPSILON);
}
#[test]
fn default_equals_new() {
let d = SharedDimensions::default();
let n = SharedDimensions::new();
assert_eq!(d, n);
}
#[test]
fn clone_and_equality() {
let s1 = SharedDimensions::new();
let s2 = s1.clone();
assert_eq!(s1, s2);
}
#[test]
fn debug_format() {
let shared = SharedDimensions::new();
let debug = format!("{:?}", shared);
assert!(debug.contains("SharedDimensions"));
}
#[test]
fn tension_decays_faster_than_affinity() {
let mut shared = SharedDimensions::new();
shared.add_tension_delta(0.4);
shared.add_affinity_delta(0.4);
shared.apply_decay(Duration::days(7));
assert!(shared.tension().delta() < shared.affinity().delta());
}
}