Skip to main content

microscope_memory/
emotional_21d.rs

1//! 21D Emotional State — wave-based emotional bias for search coordinates.
2//!
3//! Provides a persistent 21D emotional state vector and a bias function
4//! that warps search coordinates based on the current emotional state.
5
6use std::fs;
7use std::path::Path;
8
9const MAGIC: &[u8; 4] = b"E21D";
10const STATE_SIZE: usize = 4 + 21 * 4; // magic + 21 f32 values
11
12/// Persistent 21D emotional state.
13pub struct EmotionalState21D {
14    pub vector: [f32; 21],
15}
16
17impl EmotionalState21D {
18    /// Load from file or create default (neutral).
19    pub fn load_or_init(path: &Path) -> Self {
20        if path.exists() {
21            if let Ok(data) = fs::read(path) {
22                if data.len() >= STATE_SIZE && &data[0..4] == MAGIC {
23                    let mut vector = [0.0f32; 21];
24                    for (i, v) in vector.iter_mut().enumerate() {
25                        let off = 4 + i * 4;
26                        *v = f32::from_le_bytes(data[off..off + 4].try_into().unwrap_or([0u8; 4]));
27                    }
28                    // Sanitize: replace any NaN with 0.0 to prevent NaN propagation
29                    for v in &mut vector {
30                        if v.is_nan() || v.is_infinite() {
31                            *v = 0.0;
32                        }
33                    }
34                    return Self { vector };
35                }
36            }
37        }
38        Self {
39            vector: [0.0f32; 21],
40        }
41    }
42
43    /// Save to file.
44    pub fn save(&self, path: &Path) -> Result<(), String> {
45        let mut buf = Vec::with_capacity(STATE_SIZE);
46        buf.extend_from_slice(MAGIC);
47        for &v in &self.vector {
48            buf.extend_from_slice(&v.to_le_bytes());
49        }
50        let tmp_path = path.with_extension("bin.tmp");
51        fs::write(&tmp_path, &buf).map_err(|e| format!("write emotion_21d.bin: {}", e))?;
52        fs::rename(&tmp_path, path).map_err(|e| format!("rename emotion_21d.bin: {}", e))
53    }
54}
55
56/// Compute a 3D bias vector from the 21D emotional state.
57/// The first 3 components of the emotion vector are used as the bias direction,
58/// scaled by the overall emotional intensity.
59pub fn emotion_21d_bias(state: &EmotionalState21D) -> (f32, f32, f32) {
60    let intensity: f32 = state
61        .vector
62        .iter()
63        .map(|x| x * x)
64        .sum::<f32>()
65        .sqrt()
66        .min(1.0);
67    // Defense-in-depth: if intensity is NaN, return zero bias
68    if intensity.is_nan() {
69        return (0.0, 0.0, 0.0);
70    }
71    let dx = state.vector[0].clamp(-1.0, 1.0) * intensity;
72    let dy = state.vector[1].clamp(-1.0, 1.0) * intensity;
73    let dz = state.vector[2].clamp(-1.0, 1.0) * intensity;
74    (dx * 0.1, dy * 0.1, dz * 0.1)
75}