Skip to main content

microscope_memory/
daydream.rs

1//! Daydreaming — asszociatív drift külső prompt nélkül.
2//!
3//! Amikor a rendszer tétlen, nem áll le. A legutóbbi narratív blokkból
4//! indulva asszociatív láncot indít: seed → recall → legközelebbi blokk
5//! → ESR frissítés → új narratíva → ismétlés.
6//!
7//! Bináris formátum: no separate file, uses narrative.bin and append log.
8
9use crate::config::Config;
10use crate::reader::MicroscopeReader;
11use std::path::Path;
12
13// ─── Constants ──────────────────────────────────────
14
15/// Hány blokkot hozzunk fel minden drift lépésben.
16/// Minimális távolság a seed és az asszociált blokk között (hogy ne ugyanazt hozza).
17const MIN_ASSOC_DIST: f32 = 0.01;
18
19// ─── DaydreamResult ────────────────────────────────
20
21/// Egy daydream ciklus eredménye.
22pub struct DaydreamResult {
23    pub steps: Vec<DaydreamStep>,
24    pub final_narrative: String,
25    pub total_emotion_shift: f32,
26}
27
28/// Egyetlen asszociatív lépés.
29pub struct DaydreamStep {
30    pub step: usize,
31    pub seed_text: String,
32    pub associated_block: u32,
33    pub associated_text: String,
34    pub spatial_dist: f32,
35    pub emotion_shift: f32,
36}
37
38/// Daydreaming: asszociatív drift végrehajtása.
39/// 1. Seed: a legutóbbi narratíva (vagy egy query)
40/// 2. Recall a seed-re → top K eredmény
41/// 3. Válasszuk ki a legközelebbi asszociatív blokkot (ami nem a seed)
42/// 4. ESR frissítés a blokk emotion-jával
43/// 5. Új narratíva generálás
44/// 6. Ismétlés steps-szer
45pub fn daydream(config: &Config, seed: &str, steps: usize) -> Result<DaydreamResult, String> {
46    let output_dir = Path::new(&config.paths.output_dir);
47    let reader = MicroscopeReader::open(config).map_err(|e| format!("open reader: {}", e))?;
48    let sw = config.search.semantic_weight;
49
50    let mut current_seed = seed.to_string();
51    let mut visited = std::collections::HashSet::new();
52    let mut result = DaydreamResult {
53        steps: Vec::new(),
54        final_narrative: String::new(),
55        total_emotion_shift: 0.0,
56    };
57
58    let mut ring = crate::emotional_state::EmotionalStateRing::load_or_init(output_dir);
59    let mut narrative_state = crate::narrative::NarrativeState::load_or_init(output_dir);
60
61    for step in 0..steps {
62        // 1. Recall the seed
63        let (qx, qy, qz) = crate::content_coords_blended(&current_seed, "long_term", sw);
64        let (zoom_lo, zoom_hi) = (2, 4);
65
66        let mut best_dist = f32::MAX;
67        let mut best_idx = u32::MAX;
68        let mut best_text = String::new();
69
70        for zoom in zoom_lo..=zoom_hi {
71            let (start, count) = reader.depth_ranges[zoom as usize];
72            if count == 0 {
73                continue;
74            }
75
76            // Linear scan (acceptable for daydreaming — not latency-critical)
77            for bi in 0..count {
78                let i = start + bi;
79                let hdr = reader.header(i as usize);
80                let dx = hdr.x - qx;
81                let dy = hdr.y - qy;
82                let dz = hdr.z - qz;
83                let dist = dx * dx + dy * dy + dz * dz;
84                if dist < MIN_ASSOC_DIST {
85                    continue;
86                } // skip same
87                if visited.contains(&i) {
88                    continue;
89                } // skip already visited
90                if dist < best_dist {
91                    best_dist = dist;
92                    best_idx = i;
93                    best_text = reader.text(i as usize).to_string();
94                }
95            }
96        }
97
98        if best_idx == u32::MAX {
99            break; // nothing new to associate
100        }
101        visited.insert(best_idx);
102
103        // 2. Load emotions.bin for this block
104        let emotion_lookup = crate::reader::load_emotion_lookup(output_dir);
105        let block_emotion = emotion_lookup
106            .as_ref()
107            .and_then(|lookup| lookup(best_idx as usize))
108            .unwrap_or([0.0f32; 21]);
109
110        let emo_intensity: f32 = block_emotion.iter().map(|x| x * x).sum::<f32>().sqrt();
111        let shift_before = ring.intensity();
112
113        // 3. Update ESR with the associated block's emotion
114        if emo_intensity > 0.1 {
115            ring.update(&block_emotion, 5);
116            let _ = ring.save(output_dir);
117        }
118
119        let shift_after = ring.intensity();
120        let emotion_shift = (shift_after - shift_before).abs();
121
122        // 4. Generate new narrative
123        let _ = narrative_state.update(
124            output_dir,
125            Some(&ring),
126            None,
127            None,
128            None,
129            Some(&format!("daydream: {}", safe_truncate(&best_text, 40))),
130        );
131
132        result.steps.push(DaydreamStep {
133            step,
134            seed_text: safe_truncate(&current_seed, 40),
135            associated_block: best_idx,
136            associated_text: safe_truncate(&best_text, 60),
137            spatial_dist: best_dist.sqrt(),
138            emotion_shift,
139        });
140
141        result.total_emotion_shift += emotion_shift;
142
143        // 5. The associated text becomes the next seed
144        current_seed = best_text;
145    }
146
147    result.final_narrative = narrative_state.narrative.clone();
148    Ok(result)
149}
150
151fn safe_truncate(s: &str, max: usize) -> String {
152    if s.len() <= max {
153        s.to_string()
154    } else {
155        format!("{}...", &s[..max])
156    }
157}
158
159/// DaydreamResult formázása CLI kimenethez.
160pub fn format_daydream(result: &DaydreamResult, verbose: bool) -> String {
161    let mut out = format!(
162        "{} ({} steps)\n",
163        "DAYDREAM".cyan().bold(),
164        result.steps.len()
165    );
166    out.push_str(&format!(
167        "  Total emotion shift: {:.3}\n",
168        result.total_emotion_shift
169    ));
170    out.push_str(&format!(
171        "  Final narrative: \"{}\"\n",
172        result.final_narrative
173    ));
174
175    if verbose {
176        for step in &result.steps {
177            out.push_str(&format!(
178                "  [{}/{}] \"{}\" → block {} dist={:.3} emo_shift={:.3}\n             \"{}\"\n",
179                step.step + 1,
180                result.steps.len(),
181                step.seed_text,
182                step.associated_block,
183                step.spatial_dist,
184                step.emotion_shift,
185                step.associated_text,
186            ));
187        }
188    }
189
190    out
191}
192
193use colored::Colorize;
194
195#[cfg(test)]
196mod tests {
197    use super::*;
198
199    #[test]
200    fn test_safe_truncate_short() {
201        assert_eq!(safe_truncate("hello", 10), "hello");
202    }
203
204    #[test]
205    fn test_safe_truncate_long() {
206        let s = safe_truncate("hello world this is a long string", 10);
207        assert!(s.len() <= 13); // 10 + "..."
208        assert!(s.ends_with("..."));
209    }
210
211    #[test]
212    fn test_daydream_empty_steps() {
213        // Just validate that the result type works
214        let result = DaydreamResult {
215            steps: vec![],
216            final_narrative: "I am silent.".to_string(),
217            total_emotion_shift: 0.0,
218        };
219        assert_eq!(result.steps.len(), 0);
220    }
221}