use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum StressorType {
Predation,
ResourceScarcity,
SocialConflict,
Environmental,
Crowding,
Isolation,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StressState {
pub acute: f32,
pub chronic: f32,
pub resilience: f32,
}
impl StressState {
#[must_use]
pub fn new() -> Self {
Self {
acute: 0.0,
chronic: 0.0,
resilience: 1.0,
}
}
pub fn apply_stressor(&mut self, intensity: f32) {
let intensity = intensity.clamp(0.0, 1.0);
self.acute = (self.acute + intensity * 0.5).clamp(0.0, 1.0);
let chronic_gain = intensity * 0.05 * (2.0 - self.resilience);
self.chronic = (self.chronic + chronic_gain).clamp(0.0, 1.0);
self.resilience = (self.resilience - intensity * 0.02).clamp(0.0, 1.0);
}
pub fn recover(&mut self, dt: f32, safety: f32) {
let safety = safety.clamp(0.0, 1.0);
let acute_rate = 0.1 * (0.5 + safety * 0.5);
self.acute *= (-acute_rate * dt).exp();
if self.acute < 1e-6 {
self.acute = 0.0;
}
let chronic_rate = 0.005 * safety * self.resilience;
self.chronic *= (-chronic_rate * dt).exp();
if self.chronic < 1e-6 {
self.chronic = 0.0;
}
let resilience_gain = 0.01 * dt * safety * (1.0 - self.acute);
self.resilience = (self.resilience + resilience_gain).clamp(0.0, 1.0);
}
#[must_use]
#[inline]
pub fn is_distressed(&self) -> bool {
self.acute > 0.7
}
#[must_use]
#[inline]
pub fn is_chronically_stressed(&self) -> bool {
self.chronic > 0.6
}
#[must_use]
pub fn behavioral_impact(&self) -> f32 {
let acute_impact = self.acute * 0.6;
let chronic_impact = self.chronic * 0.4;
(acute_impact + chronic_impact).clamp(0.0, 1.0)
}
}
impl Default for StressState {
fn default() -> Self {
Self::new()
}
}
#[must_use]
pub fn stress_drive_modifier(chronic_stress: f32, is_anxiety_drive: bool) -> f32 {
let chronic_stress = chronic_stress.clamp(0.0, 1.0);
if is_anxiety_drive {
1.0 + chronic_stress * 0.5 } else {
1.0 - chronic_stress * 0.6 }
}
#[must_use]
pub fn immune_function(chronic_stress: f32, resilience: f32) -> f32 {
let chronic_stress = chronic_stress.clamp(0.0, 1.0);
let resilience = resilience.clamp(0.0, 1.0);
(1.0 - chronic_stress * 0.5) * (0.5 + resilience * 0.5)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fresh_state_no_stress() {
let s = StressState::new();
assert_eq!(s.acute, 0.0);
assert_eq!(s.chronic, 0.0);
assert!((s.resilience - 1.0).abs() < f32::EPSILON);
}
#[test]
fn stressor_raises_acute() {
let mut s = StressState::new();
s.apply_stressor(0.8);
assert!(s.acute > 0.3);
}
#[test]
fn repeated_stress_builds_chronic() {
let mut s = StressState::new();
for _ in 0..50 {
s.apply_stressor(0.5);
}
assert!(
s.chronic > 0.3,
"chronic should build over time: {}",
s.chronic
);
}
#[test]
fn resilience_erodes() {
let mut s = StressState::new();
for _ in 0..30 {
s.apply_stressor(0.6);
}
assert!(
s.resilience < 0.8,
"resilience should erode: {}",
s.resilience
);
}
#[test]
fn recovery_reduces_acute() {
let mut s = StressState::new();
s.apply_stressor(0.9);
let before = s.acute;
s.recover(10.0, 0.9);
assert!(s.acute < before, "acute should decrease with recovery");
}
#[test]
fn safe_environment_aids_recovery() {
let mut safe = StressState::new();
let mut dangerous = StressState::new();
safe.apply_stressor(0.8);
dangerous.apply_stressor(0.8);
safe.recover(5.0, 1.0);
dangerous.recover(5.0, 0.1);
assert!(
safe.acute < dangerous.acute,
"safe environment should recover faster"
);
}
#[test]
fn chronic_stress_amplifies_anxiety_drives() {
let mod_anxious = stress_drive_modifier(0.8, true);
let mod_luxury = stress_drive_modifier(0.8, false);
assert!(mod_anxious > 1.0, "anxiety drives should be amplified");
assert!(mod_luxury < 1.0, "luxury drives should be suppressed");
}
#[test]
fn no_stress_no_modifier() {
assert!((stress_drive_modifier(0.0, true) - 1.0).abs() < f32::EPSILON);
assert!((stress_drive_modifier(0.0, false) - 1.0).abs() < f32::EPSILON);
}
#[test]
fn chronic_stress_suppresses_immunity() {
let healthy = immune_function(0.0, 1.0);
let stressed = immune_function(0.8, 0.5);
assert!(healthy > stressed);
}
#[test]
fn behavioral_impact_bounded() {
let mut s = StressState::new();
for _ in 0..100 {
s.apply_stressor(1.0);
}
assert!((0.0..=1.0).contains(&s.behavioral_impact()));
}
#[test]
fn serde_roundtrip_stressor_type() {
for t in [
StressorType::Predation,
StressorType::ResourceScarcity,
StressorType::SocialConflict,
StressorType::Environmental,
StressorType::Crowding,
StressorType::Isolation,
] {
let json = serde_json::to_string(&t).unwrap();
let t2: StressorType = serde_json::from_str(&json).unwrap();
assert_eq!(t, t2);
}
}
#[test]
fn serde_roundtrip_stress_state() {
let mut s = StressState::new();
s.apply_stressor(0.5);
let json = serde_json::to_string(&s).unwrap();
let s2: StressState = serde_json::from_str(&json).unwrap();
assert!((s.acute - s2.acute).abs() < f32::EPSILON);
assert!((s.chronic - s2.chronic).abs() < f32::EPSILON);
assert!((s.resilience - s2.resilience).abs() < f32::EPSILON);
}
}