use serde::{Deserialize, Serialize};
use crate::error::{MastishkError, validate_dt};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InflammationState {
pub microglial_activation: f32,
pub cytokine_level: f32,
pub neuroinflammation: f32,
pub sickness_behavior: f32,
}
impl Default for InflammationState {
fn default() -> Self {
Self {
microglial_activation: 0.05,
cytokine_level: 0.05,
neuroinflammation: 0.0,
sickness_behavior: 0.0,
}
}
}
impl InflammationState {
#[inline]
pub fn infect(&mut self, intensity: f32) {
self.microglial_activation = (self.microglial_activation + intensity * 0.4).min(1.0);
self.cytokine_level = (self.cytokine_level + intensity * 0.3).min(1.0);
tracing::debug!(intensity, "infection/injury signal");
}
#[inline]
pub fn tick(&mut self, dt: f32) -> Result<(), MastishkError> {
validate_dt(dt)?;
let alpha = 1.0 - (-0.05 * dt).exp();
let cytokine_target = self.microglial_activation * 0.8;
self.cytokine_level += (cytokine_target - self.cytokine_level) * alpha;
self.neuroinflammation += (self.cytokine_level * 0.7 - self.neuroinflammation) * alpha;
self.sickness_behavior += (self.neuroinflammation * 0.9 - self.sickness_behavior) * alpha;
self.microglial_activation +=
(0.05 - self.microglial_activation) * (1.0 - (-0.02 * dt).exp());
self.cytokine_level = self.cytokine_level.clamp(0.0, 1.0);
self.neuroinflammation = self.neuroinflammation.clamp(0.0, 1.0);
self.sickness_behavior = self.sickness_behavior.clamp(0.0, 1.0);
tracing::trace!(cytokines = self.cytokine_level, "inflammation tick");
Ok(())
}
#[inline]
#[must_use]
pub fn tryptophan_depletion(&self) -> f32 {
(self.neuroinflammation * 0.6).clamp(0.0, 1.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_low_inflammation() {
let s = InflammationState::default();
assert!(s.neuroinflammation < 0.01);
assert!(s.sickness_behavior < 0.01);
}
#[test]
fn test_infection_raises_cytokines() {
let mut s = InflammationState::default();
s.infect(0.8);
for _ in 0..10 {
s.tick(1.0).unwrap();
}
assert!(s.cytokine_level > 0.1, "cytokines={}", s.cytokine_level);
assert!(s.sickness_behavior > 0.0);
}
#[test]
fn test_tryptophan_depletion() {
let mut s = InflammationState::default();
assert!(s.tryptophan_depletion() < 0.01);
s.neuroinflammation = 0.8;
assert!(s.tryptophan_depletion() > 0.3);
}
#[test]
fn test_inflammation_decays() {
let mut s = InflammationState::default();
s.infect(1.0);
for _ in 0..500 {
s.tick(1.0).unwrap();
}
assert!(s.microglial_activation < 0.2);
}
#[test]
fn test_serde_roundtrip() {
let s = InflammationState::default();
let json = serde_json::to_string(&s).unwrap();
let s2: InflammationState = serde_json::from_str(&json).unwrap();
assert!((s2.microglial_activation - s.microglial_activation).abs() < f32::EPSILON);
}
}