use crate::mood::MoodVector;
#[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
#[must_use]
#[inline]
pub fn mood_from_fatigue(capacity: f32) -> MoodVector {
let c = capacity.clamp(0.0, 1.0);
let fatigue_level = 1.0 - c;
MoodVector {
joy: (c * 0.3).clamp(0.0, 1.0),
arousal: (fatigue_level * -0.3).clamp(-1.0, 1.0),
dominance: (c * 0.2 - 0.1).clamp(-1.0, 1.0),
trust: 0.0,
interest: (c * 0.2 - 0.1).clamp(-1.0, 1.0),
frustration: (fatigue_level * 0.7).clamp(0.0, 1.0),
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
#[must_use]
#[inline]
pub fn energy_drain_from_fatigue(capacity: f32) -> f32 {
let c = capacity.clamp(0.01, 1.0);
(1.0 / c).clamp(1.0, 5.0)
}
#[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
#[must_use]
#[inline]
pub fn stress_from_violation(total_violation_rad: f32) -> f32 {
let v = total_violation_rad.max(0.0);
(1.0 - (-v * 0.7).exp()).clamp(0.0, 1.0)
}
#[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
#[must_use]
#[inline]
pub fn pain_intensity(total_violation_rad: f32) -> f32 {
let v = total_violation_rad.max(0.0);
(1.0 + v).ln() / (1.0 + 5.0_f32).ln()
}
#[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
#[must_use]
#[inline]
pub fn anxiety_from_balance(stability_margin: f32) -> MoodVector {
let m = stability_margin.clamp(-1.0, 1.0);
if m >= 0.0 {
MoodVector {
joy: 0.0,
arousal: (-m * 0.2).clamp(-1.0, 0.0),
dominance: (m * 0.3).clamp(0.0, 1.0),
trust: (m * 0.5).clamp(0.0, 1.0),
interest: 0.0,
frustration: 0.0,
}
} else {
let panic = (-m).clamp(0.0, 1.0);
MoodVector {
joy: 0.0,
arousal: (panic * 0.9).clamp(0.0, 1.0),
dominance: (-panic * 0.5).clamp(-1.0, 0.0),
trust: (-panic * 0.7).clamp(-1.0, 0.0),
interest: 0.0,
frustration: (panic * 0.4).clamp(0.0, 1.0),
}
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
#[must_use]
#[inline]
pub fn exertion_from_activation(avg_activation: f32) -> f32 {
let a = avg_activation.clamp(0.0, 1.0);
(a * a).clamp(0.0, 1.0)
}
#[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
#[must_use]
#[inline]
pub fn metabolic_load(mass_kg: f32) -> f64 {
sharira::bridge::body_mass_to_bmr(mass_kg)
}
#[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
#[must_use]
#[inline]
pub fn confidence_from_morphology(mass_factor: f32) -> f32 {
((mass_factor - 1.0) * 0.5).clamp(-0.3, 0.3)
}
#[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
#[must_use]
#[inline]
pub fn arousal_from_gait(speed_ms: f32) -> f32 {
let s = speed_ms.max(0.0);
(1.0 - (-s * 0.4).exp()).clamp(0.0, 1.0)
}
#[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
#[must_use]
#[inline]
pub fn gait_emotional_valence(gait_type: sharira::GaitType) -> f32 {
#[allow(unreachable_patterns)]
match gait_type {
sharira::GaitType::Walk => 0.0,
sharira::GaitType::Run => 0.3,
sharira::GaitType::Crawl => -0.3,
sharira::GaitType::Hop => 0.2,
sharira::GaitType::Fly => 0.5,
sharira::GaitType::Swim => 0.3,
sharira::GaitType::Trot => 0.1,
sharira::GaitType::Canter => 0.2,
sharira::GaitType::Gallop => 0.4,
sharira::GaitType::Slither => -0.1,
_ => 0.0,
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
#[must_use]
#[inline]
pub fn arousal_from_heart_rate(resting_bpm: f64) -> f32 {
let hr = resting_bpm.clamp(20.0, 1000.0);
((hr.ln() - 3.0) / 4.0) as f32
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fatigue_fresh_low_frustration() {
let mood = mood_from_fatigue(1.0);
assert!(mood.frustration < 0.1);
assert!(mood.joy > 0.0);
}
#[test]
fn fatigue_exhausted_high_frustration() {
let mood = mood_from_fatigue(0.1);
assert!(mood.frustration > 0.5);
}
#[test]
fn fatigue_clamps_input() {
let over = mood_from_fatigue(2.0);
let under = mood_from_fatigue(-1.0);
assert!(over.frustration < 0.1);
assert!(under.frustration > 0.5);
}
#[test]
fn energy_drain_increases_with_fatigue() {
let fresh = energy_drain_from_fatigue(1.0);
let tired = energy_drain_from_fatigue(0.3);
assert!(tired > fresh);
assert!(fresh >= 1.0);
}
#[test]
fn no_violation_no_stress() {
assert!((stress_from_violation(0.0) - 0.0).abs() < 0.01);
}
#[test]
fn violation_produces_stress() {
assert!(stress_from_violation(1.0) > 0.3);
assert!(stress_from_violation(2.0) > stress_from_violation(1.0));
}
#[test]
fn pain_zero_when_no_violation() {
assert!((pain_intensity(0.0) - 0.0).abs() < 0.01);
}
#[test]
fn pain_increases_with_violation() {
assert!(pain_intensity(2.0) > pain_intensity(1.0));
}
#[test]
fn pain_saturates() {
let mid = pain_intensity(3.0);
let high = pain_intensity(5.0);
assert!(high - mid < mid);
}
#[test]
fn stable_balance_calm() {
let mood = anxiety_from_balance(0.3);
assert!(mood.trust > 0.0);
assert!(mood.arousal <= 0.0);
}
#[test]
fn falling_balance_panic() {
let mood = anxiety_from_balance(-0.5);
assert!(mood.arousal > 0.3);
assert!(mood.trust < 0.0);
}
#[test]
fn zero_balance_neutral() {
let mood = anxiety_from_balance(0.0);
assert!(mood.arousal <= 0.0);
assert!(mood.trust >= 0.0);
}
#[test]
fn rest_low_exertion() {
assert!(exertion_from_activation(0.0) < 0.01);
}
#[test]
fn high_activation_high_exertion() {
assert!(exertion_from_activation(0.9) > 0.5);
}
#[test]
fn metabolic_load_human() {
let bmr = metabolic_load(70.0);
assert!(bmr > 50.0 && bmr < 150.0);
}
#[test]
fn heavy_build_positive_confidence() {
assert!(confidence_from_morphology(1.4) > 0.0);
}
#[test]
fn lean_build_negative_confidence() {
assert!(confidence_from_morphology(0.7) < 0.0);
}
#[test]
fn average_build_neutral() {
assert!(confidence_from_morphology(1.0).abs() < 0.01);
}
#[test]
fn faster_gait_higher_arousal() {
let walk = arousal_from_gait(1.4);
let run = arousal_from_gait(4.0);
assert!(run > walk);
}
#[test]
fn still_gait_low_arousal() {
assert!(arousal_from_gait(0.0) < 0.05);
}
#[test]
fn gait_valence_run_positive() {
let v = gait_emotional_valence(sharira::GaitType::Run);
assert!(v > 0.0);
}
#[test]
fn gait_valence_crawl_negative() {
let v = gait_emotional_valence(sharira::GaitType::Crawl);
assert!(v < 0.0);
}
#[test]
fn gait_valence_walk_neutral() {
let v = gait_emotional_valence(sharira::GaitType::Walk);
assert!((v - 0.0).abs() < 0.01);
}
#[test]
fn human_hr_low_arousal() {
let a = arousal_from_heart_rate(70.0);
assert!(a > 0.0 && a < 0.4);
}
#[test]
fn mouse_hr_high_arousal() {
let a = arousal_from_heart_rate(600.0);
assert!(a > 0.5);
}
#[test]
fn higher_hr_higher_arousal() {
let low = arousal_from_heart_rate(60.0);
let high = arousal_from_heart_rate(200.0);
assert!(high > low);
}
}