1use std::fs;
10use std::path::Path;
11use std::time::{SystemTime, UNIX_EPOCH};
12
13use crate::hebbian::HebbianState;
14use crate::predictive_cache::PredictiveCache;
15use crate::resonance::ResonanceState;
16use crate::thought_graph::ThoughtGraphState;
17
18const REPLAY_WINDOW_MS: u64 = 86_400_000;
22
23const COACTIVATION_PRUNE_THRESHOLD: u32 = 1;
25
26const PRUNE_AGE_MS: u64 = 172_800_000;
28
29const ACTIVATION_PRUNE_ENERGY: f32 = 0.001;
31
32const REPLAY_ENERGY: f32 = 0.3;
34
35const STRENGTHEN_MIN_APPEARANCES: usize = 3;
37
38const STRENGTHEN_MULTIPLIER: f32 = 1.5;
40
41const FIELD_DREAM_DECAY: f32 = 0.8;
43
44#[derive(Clone, Debug)]
48pub struct DreamCycle {
49 pub timestamp_ms: u64,
50 pub duration_ms: u32,
51 pub replayed_fingerprints: u32,
52 pub strengthened_pairs: u32,
53 pub pruned_pairs: u32,
54 pub pruned_activations: u32,
55 pub consolidated_patterns: u32,
56 pub energy_before: f32,
57 pub energy_after: f32,
58}
59
60pub struct DreamState {
62 pub cycles: Vec<DreamCycle>,
63 pub last_dream_ms: u64,
64}
65
66pub struct DreamStats {
67 pub total_cycles: usize,
68 pub last_dream_ms: u64,
69 pub total_pruned_pairs: u64,
70 pub total_pruned_activations: u64,
71 pub total_strengthened: u64,
72 pub total_replayed: u64,
73}
74
75const CYCLE_BYTES: usize = 40; impl DreamState {
80 pub fn load_or_init(output_dir: &Path) -> Self {
81 let path = output_dir.join("dream_log.bin");
82 if let Ok(data) = fs::read(&path) {
83 if data.len() >= 16 && &data[0..4] == b"DRM1" {
84 let cycle_count = read_u32(&data, 4) as usize;
85 let last_dream_ms = read_u64(&data, 8);
86 let mut cycles = Vec::with_capacity(cycle_count);
87 for i in 0..cycle_count {
88 let off = 16 + i * CYCLE_BYTES;
89 if off + CYCLE_BYTES > data.len() {
90 break;
91 }
92 cycles.push(DreamCycle {
93 timestamp_ms: read_u64(&data, off),
94 duration_ms: read_u32(&data, off + 8),
95 replayed_fingerprints: read_u32(&data, off + 12),
96 strengthened_pairs: read_u32(&data, off + 16),
97 pruned_pairs: read_u32(&data, off + 20),
98 pruned_activations: read_u32(&data, off + 24),
99 consolidated_patterns: read_u32(&data, off + 28),
100 energy_before: read_f32(&data, off + 32),
101 energy_after: read_f32(&data, off + 36),
102 });
103 }
104 return Self {
105 cycles,
106 last_dream_ms,
107 };
108 }
109 }
110 Self {
111 cycles: Vec::new(),
112 last_dream_ms: 0,
113 }
114 }
115
116 pub fn save(&self, output_dir: &Path) -> Result<(), String> {
117 let path = output_dir.join("dream_log.bin");
118 let mut buf = Vec::with_capacity(16 + self.cycles.len() * CYCLE_BYTES);
119 buf.extend_from_slice(b"DRM1");
120 buf.extend_from_slice(&(self.cycles.len() as u32).to_le_bytes());
121 buf.extend_from_slice(&self.last_dream_ms.to_le_bytes());
122 for c in &self.cycles {
123 buf.extend_from_slice(&c.timestamp_ms.to_le_bytes());
124 buf.extend_from_slice(&c.duration_ms.to_le_bytes());
125 buf.extend_from_slice(&c.replayed_fingerprints.to_le_bytes());
126 buf.extend_from_slice(&c.strengthened_pairs.to_le_bytes());
127 buf.extend_from_slice(&c.pruned_pairs.to_le_bytes());
128 buf.extend_from_slice(&c.pruned_activations.to_le_bytes());
129 buf.extend_from_slice(&c.consolidated_patterns.to_le_bytes());
130 buf.extend_from_slice(&c.energy_before.to_le_bytes());
131 buf.extend_from_slice(&c.energy_after.to_le_bytes());
132 }
133 fs::write(&path, &buf).map_err(|e| format!("write dream_log.bin: {}", e))
134 }
135
136 pub fn stats(&self) -> DreamStats {
137 DreamStats {
138 total_cycles: self.cycles.len(),
139 last_dream_ms: self.last_dream_ms,
140 total_pruned_pairs: self.cycles.iter().map(|c| c.pruned_pairs as u64).sum(),
141 total_pruned_activations: self
142 .cycles
143 .iter()
144 .map(|c| c.pruned_activations as u64)
145 .sum(),
146 total_strengthened: self
147 .cycles
148 .iter()
149 .map(|c| c.strengthened_pairs as u64)
150 .sum(),
151 total_replayed: self
152 .cycles
153 .iter()
154 .map(|c| c.replayed_fingerprints as u64)
155 .sum(),
156 }
157 }
158}
159
160pub fn dream_consolidate(output_dir: &Path, block_count: usize) -> Result<DreamCycle, String> {
171 let t0 = now_ms();
172
173 let mut hebb = HebbianState::load_or_init(output_dir, block_count);
174 let mut thought_graph = ThoughtGraphState::load_or_init(output_dir);
175 let mut pred_cache = PredictiveCache::load_or_init(output_dir);
176 let mut resonance = ResonanceState::load_or_init(output_dir);
177
178 let energy_before: f32 = hebb.activations.iter().map(|r| r.energy).sum();
180
181 let cutoff = t0.saturating_sub(REPLAY_WINDOW_MS);
183 let recent_fps: Vec<_> = hebb
184 .fingerprints
185 .iter()
186 .filter(|fp| fp.timestamp_ms >= cutoff)
187 .cloned()
188 .collect();
189 let replayed_count = recent_fps.len() as u32;
190
191 let mut pair_appearances: std::collections::HashMap<(u32, u32), usize> =
193 std::collections::HashMap::new();
194
195 for fp in &recent_fps {
196 for &(block_idx, _score) in &fp.activations {
198 let idx = block_idx as usize;
199 if idx < hebb.activations.len() {
200 let rec = &mut hebb.activations[idx];
201 rec.energy = (rec.energy + REPLAY_ENERGY).min(1.0);
203 }
204 }
205
206 for i in 0..fp.activations.len() {
208 for j in (i + 1)..fp.activations.len() {
209 let a = fp.activations[i].0.min(fp.activations[j].0);
210 let b = fp.activations[i].0.max(fp.activations[j].0);
211 *pair_appearances.entry((a, b)).or_insert(0) += 1;
212 }
213 }
214 }
215
216 let mut strengthened = 0u32;
218 for ((a, b), appearances) in &pair_appearances {
219 if *appearances >= STRENGTHEN_MIN_APPEARANCES {
220 if let Some(pair) = hebb.coactivations.get_mut(&(*a, *b)) {
221 pair.count = (pair.count as f32 * STRENGTHEN_MULTIPLIER) as u32;
222 strengthened += 1;
223 }
224 }
225 }
226
227 let mut pruned_pairs = 0u32;
229 hebb.coactivations.retain(|_, pair| {
230 if pair.count <= COACTIVATION_PRUNE_THRESHOLD && pair.last_ts_ms + PRUNE_AGE_MS < t0 {
231 pruned_pairs += 1;
232 false
233 } else {
234 true
235 }
236 });
237
238 let mut pruned_activations = 0u32;
240 for rec in &mut hebb.activations {
241 if rec.energy < ACTIVATION_PRUNE_ENERGY && rec.activation_count == 0 {
242 *rec = crate::hebbian::ActivationRecord::default();
243 pruned_activations += 1;
244 }
245 }
246
247 let patterns_before = thought_graph.crystallized_count();
249 thought_graph.detect_patterns();
250 let consolidated_patterns = (thought_graph.crystallized_count() - patterns_before) as u32;
251
252 resonance.decay_field(FIELD_DREAM_DECAY);
254 resonance.expire_pulses();
255
256 pred_cache.dream_cleanup();
258
259 let energy_after: f32 = hebb.activations.iter().map(|r| r.energy).sum();
261
262 hebb.save(output_dir)
264 .map_err(|e| format!("save hebbian: {}", e))?;
265 thought_graph
266 .save(output_dir)
267 .map_err(|e| format!("save thought_graph: {}", e))?;
268 pred_cache
269 .save(output_dir)
270 .map_err(|e| format!("save predictive_cache: {}", e))?;
271 resonance
272 .save(output_dir)
273 .map_err(|e| format!("save resonance: {}", e))?;
274
275 let duration_ms = (now_ms() - t0) as u32;
276
277 Ok(DreamCycle {
278 timestamp_ms: t0,
279 duration_ms,
280 replayed_fingerprints: replayed_count,
281 strengthened_pairs: strengthened,
282 pruned_pairs,
283 pruned_activations,
284 consolidated_patterns,
285 energy_before,
286 energy_after,
287 })
288}
289
290fn read_u32(b: &[u8], off: usize) -> u32 {
293 u32::from_le_bytes(b[off..off + 4].try_into().unwrap())
294}
295fn read_u64(b: &[u8], off: usize) -> u64 {
296 u64::from_le_bytes(b[off..off + 8].try_into().unwrap())
297}
298fn read_f32(b: &[u8], off: usize) -> f32 {
299 f32::from_le_bytes(b[off..off + 4].try_into().unwrap())
300}
301
302fn now_ms() -> u64 {
303 SystemTime::now()
304 .duration_since(UNIX_EPOCH)
305 .unwrap_or_default()
306 .as_millis() as u64
307}
308
309#[cfg(test)]
312mod tests {
313 use super::*;
314 use crate::archetype::ArchetypeState;
315 use crate::hebbian::{ActivationFingerprint, ActivationRecord, CoactivationPair};
316 use crate::predictive_cache::PredictiveCache;
317 use crate::resonance::ResonanceState;
318 use crate::thought_graph::ThoughtGraphState;
319 use std::collections::HashMap;
320
321 fn make_hebb(block_count: usize) -> HebbianState {
322 HebbianState {
323 activations: vec![ActivationRecord::default(); block_count],
324 coactivations: HashMap::new(),
325 fingerprints: Vec::new(),
326 }
327 }
328
329 #[test]
330 fn test_dream_log_roundtrip() {
331 let tmp = tempfile::tempdir().expect("tempdir");
332 let state = DreamState {
333 cycles: vec![
334 DreamCycle {
335 timestamp_ms: 1000,
336 duration_ms: 50,
337 replayed_fingerprints: 10,
338 strengthened_pairs: 3,
339 pruned_pairs: 5,
340 pruned_activations: 2,
341 consolidated_patterns: 1,
342 energy_before: 10.5,
343 energy_after: 8.2,
344 },
345 DreamCycle {
346 timestamp_ms: 2000,
347 duration_ms: 30,
348 replayed_fingerprints: 8,
349 strengthened_pairs: 2,
350 pruned_pairs: 3,
351 pruned_activations: 1,
352 consolidated_patterns: 0,
353 energy_before: 8.2,
354 energy_after: 7.0,
355 },
356 ],
357 last_dream_ms: 2000,
358 };
359 state.save(tmp.path()).unwrap();
360 let loaded = DreamState::load_or_init(tmp.path());
361 assert_eq!(loaded.cycles.len(), 2);
362 assert_eq!(loaded.last_dream_ms, 2000);
363 assert_eq!(loaded.cycles[0].replayed_fingerprints, 10);
364 assert_eq!(loaded.cycles[1].pruned_pairs, 3);
365 }
366
367 #[test]
368 fn test_dream_strengthens_repeated_coactivations() {
369 let tmp = tempfile::tempdir().expect("tempdir");
370 let mut hebb = make_hebb(10);
371
372 hebb.coactivations.insert(
374 (0, 1),
375 CoactivationPair {
376 block_a: 0,
377 block_b: 1,
378 count: 5,
379 last_ts_ms: now_ms(),
380 },
381 );
382
383 let now = now_ms();
385 for i in 0..3 {
386 hebb.fingerprints.push(ActivationFingerprint {
387 timestamp_ms: now - i * 1000,
388 query_hash: 100 + i,
389 activations: vec![(0, 0.5), (1, 0.3)],
390 });
391 }
392
393 hebb.save(tmp.path()).unwrap();
394
395 let tg = ThoughtGraphState::load_or_init(tmp.path());
397 tg.save(tmp.path()).unwrap();
398 let pc = PredictiveCache::load_or_init(tmp.path());
399 pc.save(tmp.path()).unwrap();
400 let res = ResonanceState::load_or_init(tmp.path());
401 res.save(tmp.path()).unwrap();
402 let arc = ArchetypeState::load_or_init(tmp.path());
403 arc.save(tmp.path()).unwrap();
404
405 let cycle = dream_consolidate(tmp.path(), 10).unwrap();
406 assert!(cycle.strengthened_pairs > 0);
407
408 let hebb2 = HebbianState::load_or_init(tmp.path(), 10);
410 let pair = hebb2.coactivations.get(&(0, 1)).unwrap();
411 assert!(pair.count > 5); }
413
414 #[test]
415 fn test_dream_prunes_weak_pairs() {
416 let tmp = tempfile::tempdir().expect("tempdir");
417 let mut hebb = make_hebb(5);
418
419 hebb.coactivations.insert(
421 (0, 1),
422 CoactivationPair {
423 block_a: 0,
424 block_b: 1,
425 count: 1,
426 last_ts_ms: 1000, },
428 );
429 hebb.coactivations.insert(
431 (2, 3),
432 CoactivationPair {
433 block_a: 2,
434 block_b: 3,
435 count: 10,
436 last_ts_ms: now_ms(),
437 },
438 );
439
440 hebb.save(tmp.path()).unwrap();
441 ThoughtGraphState::load_or_init(tmp.path())
442 .save(tmp.path())
443 .unwrap();
444 PredictiveCache::load_or_init(tmp.path())
445 .save(tmp.path())
446 .unwrap();
447 ResonanceState::load_or_init(tmp.path())
448 .save(tmp.path())
449 .unwrap();
450 ArchetypeState::load_or_init(tmp.path())
451 .save(tmp.path())
452 .unwrap();
453
454 let cycle = dream_consolidate(tmp.path(), 5).unwrap();
455 assert_eq!(cycle.pruned_pairs, 1);
456
457 let hebb2 = HebbianState::load_or_init(tmp.path(), 5);
458 assert!(!hebb2.coactivations.contains_key(&(0, 1))); assert!(hebb2.coactivations.contains_key(&(2, 3))); }
461
462 #[test]
463 fn test_dream_replays_fingerprints() {
464 let tmp = tempfile::tempdir().expect("tempdir");
465 let mut hebb = make_hebb(5);
466
467 assert_eq!(hebb.activations[0].energy, 0.0);
469
470 hebb.fingerprints.push(ActivationFingerprint {
472 timestamp_ms: now_ms() - 1000,
473 query_hash: 42,
474 activations: vec![(0, 0.5)],
475 });
476
477 hebb.save(tmp.path()).unwrap();
478 ThoughtGraphState::load_or_init(tmp.path())
479 .save(tmp.path())
480 .unwrap();
481 PredictiveCache::load_or_init(tmp.path())
482 .save(tmp.path())
483 .unwrap();
484 ResonanceState::load_or_init(tmp.path())
485 .save(tmp.path())
486 .unwrap();
487 ArchetypeState::load_or_init(tmp.path())
488 .save(tmp.path())
489 .unwrap();
490
491 let cycle = dream_consolidate(tmp.path(), 5).unwrap();
492 assert_eq!(cycle.replayed_fingerprints, 1);
493
494 let hebb2 = HebbianState::load_or_init(tmp.path(), 5);
495 assert!(hebb2.activations[0].energy >= REPLAY_ENERGY - 0.01);
496 }
497
498 #[test]
499 fn test_dream_no_fingerprints() {
500 let tmp = tempfile::tempdir().expect("tempdir");
501 let hebb = make_hebb(5);
502 hebb.save(tmp.path()).unwrap();
503 ThoughtGraphState::load_or_init(tmp.path())
504 .save(tmp.path())
505 .unwrap();
506 PredictiveCache::load_or_init(tmp.path())
507 .save(tmp.path())
508 .unwrap();
509 ResonanceState::load_or_init(tmp.path())
510 .save(tmp.path())
511 .unwrap();
512 ArchetypeState::load_or_init(tmp.path())
513 .save(tmp.path())
514 .unwrap();
515
516 let cycle = dream_consolidate(tmp.path(), 5).unwrap();
517 assert_eq!(cycle.replayed_fingerprints, 0);
518 assert_eq!(cycle.strengthened_pairs, 0);
519 assert_eq!(cycle.pruned_pairs, 0);
520 }
521
522 #[test]
523 fn test_dream_stats() {
524 let state = DreamState {
525 cycles: vec![
526 DreamCycle {
527 timestamp_ms: 1000,
528 duration_ms: 50,
529 replayed_fingerprints: 10,
530 strengthened_pairs: 3,
531 pruned_pairs: 5,
532 pruned_activations: 2,
533 consolidated_patterns: 1,
534 energy_before: 10.0,
535 energy_after: 8.0,
536 },
537 DreamCycle {
538 timestamp_ms: 2000,
539 duration_ms: 30,
540 replayed_fingerprints: 8,
541 strengthened_pairs: 2,
542 pruned_pairs: 3,
543 pruned_activations: 1,
544 consolidated_patterns: 0,
545 energy_before: 8.0,
546 energy_after: 7.0,
547 },
548 ],
549 last_dream_ms: 2000,
550 };
551 let stats = state.stats();
552 assert_eq!(stats.total_cycles, 2);
553 assert_eq!(stats.total_pruned_pairs, 8);
554 assert_eq!(stats.total_strengthened, 5);
555 assert_eq!(stats.total_replayed, 18);
556 }
557}