use crate::config::Config;
use crate::reader::MicroscopeReader;
use std::path::Path;
const MIN_ASSOC_DIST: f32 = 0.01;
pub struct DaydreamResult {
pub steps: Vec<DaydreamStep>,
pub final_narrative: String,
pub total_emotion_shift: f32,
}
pub struct DaydreamStep {
pub step: usize,
pub seed_text: String,
pub associated_block: u32,
pub associated_text: String,
pub spatial_dist: f32,
pub emotion_shift: f32,
}
pub fn daydream(config: &Config, seed: &str, steps: usize) -> Result<DaydreamResult, String> {
let output_dir = Path::new(&config.paths.output_dir);
let reader = MicroscopeReader::open(config).map_err(|e| format!("open reader: {}", e))?;
let sw = config.search.semantic_weight;
let mut current_seed = seed.to_string();
let mut visited = std::collections::HashSet::new();
let mut result = DaydreamResult {
steps: Vec::new(),
final_narrative: String::new(),
total_emotion_shift: 0.0,
};
let mut ring = crate::emotional_state::EmotionalStateRing::load_or_init(output_dir);
let mut narrative_state = crate::narrative::NarrativeState::load_or_init(output_dir);
for step in 0..steps {
let (qx, qy, qz) = crate::content_coords_blended(¤t_seed, "long_term", sw);
let (zoom_lo, zoom_hi) = (2, 4);
let mut best_dist = f32::MAX;
let mut best_idx = u32::MAX;
let mut best_text = String::new();
for zoom in zoom_lo..=zoom_hi {
let (start, count) = reader.depth_ranges[zoom as usize];
if count == 0 {
continue;
}
for bi in 0..count {
let i = start + bi;
let hdr = reader.header(i as usize);
let dx = hdr.x - qx;
let dy = hdr.y - qy;
let dz = hdr.z - qz;
let dist = dx * dx + dy * dy + dz * dz;
if dist < MIN_ASSOC_DIST {
continue;
} if visited.contains(&i) {
continue;
} if dist < best_dist {
best_dist = dist;
best_idx = i;
best_text = reader.text(i as usize).to_string();
}
}
}
if best_idx == u32::MAX {
break; }
visited.insert(best_idx);
let emotion_lookup = crate::reader::load_emotion_lookup(output_dir);
let block_emotion = emotion_lookup
.as_ref()
.and_then(|lookup| lookup(best_idx as usize))
.unwrap_or([0.0f32; 21]);
let emo_intensity: f32 = block_emotion.iter().map(|x| x * x).sum::<f32>().sqrt();
let shift_before = ring.intensity();
if emo_intensity > 0.1 {
ring.update(&block_emotion, 5);
let _ = ring.save(output_dir);
}
let shift_after = ring.intensity();
let emotion_shift = (shift_after - shift_before).abs();
let _ = narrative_state.update(
output_dir,
Some(&ring),
None,
None,
None,
Some(&format!("daydream: {}", safe_truncate(&best_text, 40))),
);
result.steps.push(DaydreamStep {
step,
seed_text: safe_truncate(¤t_seed, 40),
associated_block: best_idx,
associated_text: safe_truncate(&best_text, 60),
spatial_dist: best_dist.sqrt(),
emotion_shift,
});
result.total_emotion_shift += emotion_shift;
current_seed = best_text;
}
result.final_narrative = narrative_state.narrative.clone();
Ok(result)
}
fn safe_truncate(s: &str, max: usize) -> String {
if s.len() <= max {
s.to_string()
} else {
format!("{}...", &s[..max])
}
}
pub fn format_daydream(result: &DaydreamResult, verbose: bool) -> String {
let mut out = format!(
"{} ({} steps)\n",
"DAYDREAM".cyan().bold(),
result.steps.len()
);
out.push_str(&format!(
" Total emotion shift: {:.3}\n",
result.total_emotion_shift
));
out.push_str(&format!(
" Final narrative: \"{}\"\n",
result.final_narrative
));
if verbose {
for step in &result.steps {
out.push_str(&format!(
" [{}/{}] \"{}\" → block {} dist={:.3} emo_shift={:.3}\n \"{}\"\n",
step.step + 1,
result.steps.len(),
step.seed_text,
step.associated_block,
step.spatial_dist,
step.emotion_shift,
step.associated_text,
));
}
}
out
}
use colored::Colorize;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_safe_truncate_short() {
assert_eq!(safe_truncate("hello", 10), "hello");
}
#[test]
fn test_safe_truncate_long() {
let s = safe_truncate("hello world this is a long string", 10);
assert!(s.len() <= 13); assert!(s.ends_with("..."));
}
#[test]
fn test_daydream_empty_steps() {
let result = DaydreamResult {
steps: vec![],
final_narrative: "I am silent.".to_string(),
total_emotion_shift: 0.0,
};
assert_eq!(result.steps.len(), 0);
}
}