use serde::{Deserialize, Serialize};
use crate::appraisal::AppraisedEmotion;
use crate::traits::{PersonalityProfile, TraitKind, TraitLevel};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GrowthLedger {
pressure: [f32; TraitKind::COUNT],
pub threshold: f32,
pub decay_rate: f32,
}
impl Default for GrowthLedger {
fn default() -> Self {
Self {
pressure: [0.0; TraitKind::COUNT],
threshold: 3.0,
decay_rate: 0.05,
}
}
}
impl GrowthLedger {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_params(threshold: f32, decay_rate: f32) -> Self {
Self {
pressure: [0.0; TraitKind::COUNT],
threshold: threshold.max(0.1),
decay_rate: decay_rate.clamp(0.0, 1.0),
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
pub fn apply_emotion(&mut self, emotion: AppraisedEmotion, intensity: f32) {
let mappings: &[(TraitKind, f32)] = match emotion {
AppraisedEmotion::Pride => &[(TraitKind::Confidence, 0.5), (TraitKind::Autonomy, 0.3)],
AppraisedEmotion::Shame => {
&[(TraitKind::Confidence, -0.5), (TraitKind::Directness, -0.3)]
}
AppraisedEmotion::Gratitude => &[(TraitKind::Warmth, 0.4), (TraitKind::Empathy, 0.3)],
AppraisedEmotion::Anger => &[
(TraitKind::Directness, 0.4),
(TraitKind::Patience, -0.4),
(TraitKind::Skepticism, 0.2),
],
AppraisedEmotion::Joy => &[(TraitKind::Warmth, 0.3), (TraitKind::Humor, 0.2)],
AppraisedEmotion::Distress => &[
(TraitKind::Patience, -0.2),
(TraitKind::RiskTolerance, -0.2),
],
AppraisedEmotion::Hope => {
&[(TraitKind::RiskTolerance, 0.2), (TraitKind::Curiosity, 0.2)]
}
AppraisedEmotion::Fear => &[
(TraitKind::RiskTolerance, -0.4),
(TraitKind::Confidence, -0.3),
],
AppraisedEmotion::Admiration => {
&[(TraitKind::Empathy, 0.3), (TraitKind::Curiosity, 0.2)]
}
AppraisedEmotion::Reproach => {
&[(TraitKind::Skepticism, 0.3), (TraitKind::Warmth, -0.2)]
}
_ => &[],
};
for &(kind, weight) in mappings {
self.pressure[kind.index()] += weight * intensity;
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
pub fn apply_pressure(&mut self, kind: TraitKind, amount: f32) {
self.pressure[kind.index()] += amount;
}
#[must_use]
pub fn get_pressure(&self, kind: TraitKind) -> f32 {
self.pressure[kind.index()]
}
#[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
pub fn decay(&mut self) {
for p in &mut self.pressure {
*p *= 1.0 - self.decay_rate;
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
pub fn apply_growth(&mut self, profile: &mut PersonalityProfile) -> usize {
let mut changed = 0;
for &kind in TraitKind::ALL {
let p = self.pressure[kind.index()];
if p.abs() >= self.threshold {
let current = profile.get_trait(kind).numeric();
let direction: i8 = if p > 0.0 { 1 } else { -1 };
let new_val = (current + direction).clamp(-2, 2);
if let Some(level) = TraitLevel::from_numeric(new_val)
.ok()
.filter(|&l| l != profile.get_trait(kind))
{
profile.set_trait(kind, level);
changed += 1;
}
self.pressure[kind.index()] = 0.0; }
}
changed
}
#[must_use]
pub fn total_pressure(&self) -> f32 {
self.pressure.iter().map(|p| p.abs()).sum()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new() {
let g = GrowthLedger::new();
assert!(g.total_pressure() < f32::EPSILON);
}
#[test]
fn test_apply_emotion_pride() {
let mut g = GrowthLedger::new();
g.apply_emotion(AppraisedEmotion::Pride, 1.0);
assert!(g.get_pressure(TraitKind::Confidence) > 0.0);
assert!(g.get_pressure(TraitKind::Autonomy) > 0.0);
}
#[test]
fn test_apply_emotion_shame() {
let mut g = GrowthLedger::new();
g.apply_emotion(AppraisedEmotion::Shame, 1.0);
assert!(g.get_pressure(TraitKind::Confidence) < 0.0);
}
#[test]
fn test_apply_pressure_direct() {
let mut g = GrowthLedger::new();
g.apply_pressure(TraitKind::Warmth, 0.5);
assert!((g.get_pressure(TraitKind::Warmth) - 0.5).abs() < f32::EPSILON);
}
#[test]
fn test_decay() {
let mut g = GrowthLedger::new();
g.apply_pressure(TraitKind::Humor, 1.0);
g.decay();
assert!(g.get_pressure(TraitKind::Humor) < 1.0);
}
#[test]
fn test_apply_growth_below_threshold() {
let mut g = GrowthLedger::new();
let mut p = PersonalityProfile::new("test");
g.apply_pressure(TraitKind::Warmth, 1.0); let changed = g.apply_growth(&mut p);
assert_eq!(changed, 0);
assert_eq!(p.get_trait(TraitKind::Warmth), TraitLevel::Balanced);
}
#[test]
fn test_apply_growth_above_threshold() {
let mut g = GrowthLedger::new();
let mut p = PersonalityProfile::new("test");
g.apply_pressure(TraitKind::Warmth, 4.0); let changed = g.apply_growth(&mut p);
assert_eq!(changed, 1);
assert_eq!(p.get_trait(TraitKind::Warmth), TraitLevel::High);
assert!(g.get_pressure(TraitKind::Warmth).abs() < f32::EPSILON);
}
#[test]
fn test_apply_growth_negative() {
let mut g = GrowthLedger::new();
let mut p = PersonalityProfile::new("test");
g.apply_pressure(TraitKind::Patience, -4.0);
g.apply_growth(&mut p);
assert_eq!(p.get_trait(TraitKind::Patience), TraitLevel::Low);
}
#[test]
fn test_accumulated_experiences() {
let mut g = GrowthLedger::with_params(2.0, 0.01);
let mut p = PersonalityProfile::new("test");
for _ in 0..10 {
g.apply_emotion(AppraisedEmotion::Pride, 0.5);
}
let changed = g.apply_growth(&mut p);
assert!(changed > 0);
assert!(p.get_trait(TraitKind::Confidence) > TraitLevel::Balanced);
}
#[test]
fn test_growth_respects_limits() {
let mut g = GrowthLedger::new();
let mut p = PersonalityProfile::new("test");
p.set_trait(TraitKind::Warmth, TraitLevel::Highest);
g.apply_pressure(TraitKind::Warmth, 5.0);
let changed = g.apply_growth(&mut p);
assert_eq!(changed, 0); }
#[test]
fn test_serde() {
let mut g = GrowthLedger::new();
g.apply_pressure(TraitKind::Humor, 1.5);
let json = serde_json::to_string(&g).unwrap();
let g2: GrowthLedger = serde_json::from_str(&json).unwrap();
assert!((g2.get_pressure(TraitKind::Humor) - 1.5).abs() < f32::EPSILON);
}
#[test]
fn test_total_pressure() {
let mut g = GrowthLedger::new();
g.apply_pressure(TraitKind::Humor, 1.0);
g.apply_pressure(TraitKind::Warmth, -0.5);
assert!((g.total_pressure() - 1.5).abs() < f32::EPSILON);
}
}