microscope_memory/
daydream.rs1use crate::config::Config;
10use crate::reader::MicroscopeReader;
11use std::path::Path;
12
13const MIN_ASSOC_DIST: f32 = 0.01;
18
19pub struct DaydreamResult {
23 pub steps: Vec<DaydreamStep>,
24 pub final_narrative: String,
25 pub total_emotion_shift: f32,
26}
27
28pub 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
38pub 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 let (qx, qy, qz) = crate::content_coords_blended(¤t_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 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 } if visited.contains(&i) {
88 continue;
89 } 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; }
101 visited.insert(best_idx);
102
103 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 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 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(¤t_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 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
159pub 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); assert!(s.ends_with("..."));
209 }
210
211 #[test]
212 fn test_daydream_empty_steps() {
213 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}