use serde::{Deserialize, Serialize};
use crate::error::{MastishkError, validate_dt};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GutBrainState {
pub gut_serotonin: f32,
pub vagal_tone: f32,
pub microbiome_diversity: f32,
pub interoceptive_signal: f32,
}
impl Default for GutBrainState {
fn default() -> Self {
Self {
gut_serotonin: 0.5,
vagal_tone: 0.5,
microbiome_diversity: 0.6,
interoceptive_signal: 0.3,
}
}
}
impl GutBrainState {
#[inline]
pub fn tick(&mut self, dt: f32) -> Result<(), MastishkError> {
validate_dt(dt)?;
let alpha = 1.0 - (-0.02 * dt).exp();
let serotonin_target = 0.3 + self.microbiome_diversity * 0.4;
self.gut_serotonin += (serotonin_target - self.gut_serotonin) * alpha;
self.vagal_tone += (0.5 - self.vagal_tone) * (1.0 - (-0.01 * dt).exp());
self.interoceptive_signal += (self.gut_serotonin * 0.5 - self.interoceptive_signal) * alpha;
self.gut_serotonin = self.gut_serotonin.clamp(0.0, 1.0);
self.vagal_tone = self.vagal_tone.clamp(0.0, 1.0);
tracing::trace!(
gut_5ht = self.gut_serotonin,
vagal = self.vagal_tone,
"gut-brain tick"
);
Ok(())
}
#[inline]
#[must_use]
pub fn central_serotonin_modifier(&self) -> f32 {
0.8 + self.gut_serotonin * 0.4
}
#[inline]
#[must_use]
pub fn vagal_stress_buffer(&self) -> f32 {
self.vagal_tone * 0.3
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_central_serotonin_modifier_range() {
let s = GutBrainState::default();
let m = s.central_serotonin_modifier();
assert!((0.8..=1.2).contains(&m));
}
#[test]
fn test_vagal_stress_buffer() {
let s = GutBrainState::default();
assert!(s.vagal_stress_buffer() > 0.0);
assert!(s.vagal_stress_buffer() <= 0.3);
}
#[test]
fn test_serde_roundtrip() {
let s = GutBrainState::default();
let json = serde_json::to_string(&s).unwrap();
let s2: GutBrainState = serde_json::from_str(&json).unwrap();
assert!((s2.gut_serotonin - s.gut_serotonin).abs() < f32::EPSILON);
}
}