behaviorsim-rs 0.7.0

Domain-agnostic specification for modeling individual psychology and social dynamics
Documentation
//! Attachment style state.
//!
//! Tracks attachment anxiety and avoidance as stable, slow-changing traits.

use crate::state::StateValue;
use crate::types::Duration;
use serde::{Deserialize, Serialize};

/// Attachment style dimensions.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AttachmentStyle {
    /// Anxiety about abandonment.
    /// Range: 0 (low) to 1 (high)
    anxiety: StateValue,

    /// Avoidance of closeness.
    /// Range: 0 (low) to 1 (high)
    avoidance: StateValue,
}

impl AttachmentStyle {
    /// Default decay half-life for attachment traits (1 year).
    const ATTACHMENT_DECAY_HALF_LIFE: Duration = Duration::years(1);

    /// Creates a new AttachmentStyle with secure defaults.
    #[must_use]
    pub fn new() -> Self {
        AttachmentStyle {
            anxiety: StateValue::new(0.2)
                .with_bounds(0.0, 1.0)
                .with_decay_half_life(Self::ATTACHMENT_DECAY_HALF_LIFE),
            avoidance: StateValue::new(0.2)
                .with_bounds(0.0, 1.0)
                .with_decay_half_life(Self::ATTACHMENT_DECAY_HALF_LIFE),
        }
    }

    // Builder methods

    /// Sets the base anxiety.
    #[must_use]
    pub fn with_anxiety_base(mut self, value: f32) -> Self {
        self.anxiety.set_base(value);
        self
    }

    /// Sets the base avoidance.
    #[must_use]
    pub fn with_avoidance_base(mut self, value: f32) -> Self {
        self.avoidance.set_base(value);
        self
    }

    // Effective value accessors

    /// Returns effective anxiety (base + delta).
    #[must_use]
    pub fn anxiety_effective(&self) -> f32 {
        self.anxiety.effective()
    }

    /// Returns effective avoidance (base + delta).
    #[must_use]
    pub fn avoidance_effective(&self) -> f32 {
        self.avoidance.effective()
    }

    // StateValue references

    /// Returns a reference to the anxiety StateValue.
    #[must_use]
    pub fn anxiety(&self) -> &StateValue {
        &self.anxiety
    }

    /// Returns a mutable reference to the anxiety StateValue.
    pub fn anxiety_mut(&mut self) -> &mut StateValue {
        &mut self.anxiety
    }

    /// Returns a reference to the avoidance StateValue.
    #[must_use]
    pub fn avoidance(&self) -> &StateValue {
        &self.avoidance
    }

    /// Returns a mutable reference to the avoidance StateValue.
    pub fn avoidance_mut(&mut self) -> &mut StateValue {
        &mut self.avoidance
    }

    // Delta helpers

    /// Adds a delta to anxiety.
    pub fn add_anxiety_delta(&mut self, delta: f32) {
        self.anxiety.add_delta(delta);
    }

    /// Adds a delta to avoidance.
    pub fn add_avoidance_delta(&mut self, delta: f32) {
        self.avoidance.add_delta(delta);
    }

    // Unified operations

    /// Applies decay to attachment traits.
    pub fn apply_decay(&mut self, elapsed: Duration) {
        self.anxiety.apply_decay(elapsed);
        self.avoidance.apply_decay(elapsed);
    }

    /// Resets attachment deltas.
    pub fn reset_deltas(&mut self) {
        self.anxiety.reset_delta();
        self.avoidance.reset_delta();
    }
}

impl Default for AttachmentStyle {
    fn default() -> Self {
        AttachmentStyle::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn default_is_secure_low() {
        let attachment = AttachmentStyle::new();
        assert!(attachment.anxiety_effective() < 0.3);
        assert!(attachment.avoidance_effective() < 0.3);
    }

    #[test]
    fn builder_methods_work() {
        let attachment = AttachmentStyle::new()
            .with_anxiety_base(0.6)
            .with_avoidance_base(0.4);

        assert!((attachment.anxiety().base() - 0.6).abs() < f32::EPSILON);
        assert!((attachment.avoidance().base() - 0.4).abs() < f32::EPSILON);
    }

    #[test]
    fn apply_decay_reduces_deltas() {
        let mut attachment = AttachmentStyle::new();
        attachment.add_anxiety_delta(0.6);
        attachment.add_avoidance_delta(0.6);

        attachment.apply_decay(Duration::years(1));

        assert!(attachment.anxiety().delta() < 0.6);
        assert!(attachment.avoidance().delta() < 0.6);
    }

    #[test]
    fn reset_deltas_clears_values() {
        let mut attachment = AttachmentStyle::new();
        attachment.add_anxiety_delta(0.4);
        attachment.add_avoidance_delta(-0.2);

        attachment.reset_deltas();

        assert!(attachment.anxiety().delta().abs() < f32::EPSILON);
        assert!(attachment.avoidance().delta().abs() < f32::EPSILON);
    }

    #[test]
    fn clone_and_equality() {
        let attachment = AttachmentStyle::new();
        let cloned = attachment.clone();
        assert_eq!(attachment, cloned);
    }

    #[test]
    fn debug_format() {
        let attachment = AttachmentStyle::new();
        let debug = format!("{:?}", attachment);
        assert!(debug.contains("AttachmentStyle"));
    }

    #[test]
    fn mutable_accessors_update_state_values() {
        let mut attachment = AttachmentStyle::default();
        attachment.anxiety_mut().set_base(0.7);
        attachment.avoidance_mut().set_base(0.6);

        assert!((attachment.anxiety().base() - 0.7).abs() < f32::EPSILON);
        assert!((attachment.avoidance().base() - 0.6).abs() < f32::EPSILON);
    }
}