1use std::path::Path;
15use std::sync::{Arc, Mutex, OnceLock};
16use std::thread;
17use std::time::{Duration, SystemTime, UNIX_EPOCH};
18
19static GLOBAL_STREAM: OnceLock<Arc<Mutex<StreamState>>> = OnceLock::new();
21
22use crate::archetype::ArchetypeState;
23use crate::attention::AttentionState;
24use crate::config::Config;
25use crate::consciousness_seqlock::{MappedSnapshotWriter, SharedSnapshot};
26use crate::emotional_state::EmotionalStateRing;
27use crate::hebbian::HebbianState;
28use crate::mirror::MirrorState;
29use crate::predictive_cache::PredictiveCache;
30use crate::resonance::ResonanceState;
31use crate::thought_graph::ThoughtGraphState;
32
33const CYCLE_MS: u64 = 100;
37const CURIOSITY_INTERVAL: u64 = 50; const PREDICT_INTERVAL: u64 = 10; const DECAY_INTERVAL: u64 = 10; #[allow(dead_code)]
45const SURPRISE_THRESHOLD: f32 = 0.3;
46
47pub struct StreamState {
51 pub hebbian: HebbianState,
52 pub attention: AttentionState,
53 pub emotional_ring: EmotionalStateRing,
54 pub resonance: ResonanceState,
55 pub thought_graph: ThoughtGraphState,
56 pub predictive_cache: PredictiveCache,
57 pub archetypes: ArchetypeState,
58 pub mirror: MirrorState,
59
60 pub predicted_query_hash: u64,
62 pub predicted_confidence: f32,
63 pub surprise_level: f32,
65 pub curiosity_level: f32,
67 pub cycle: u64,
69 pub last_query_hash: u64,
71 pub last_query_ms: u64,
73
74 pub hebbian_total_energy: f32,
78
79 pub snapshot: Arc<SharedSnapshot>,
85
86 pub spine: Option<Arc<crate::spine::Spine>>,
93}
94
95pub struct ConsciousnessStream {
97 pub state: Arc<Mutex<StreamState>>,
98 #[allow(dead_code)]
99 running: Arc<Mutex<bool>>,
100}
101
102pub fn global_stream() -> Option<&'static Arc<Mutex<StreamState>>> {
104 GLOBAL_STREAM.get()
105}
106
107impl ConsciousnessStream {
108 pub fn start(config: &Config) -> Arc<Mutex<StreamState>> {
111 let output_dir = Path::new(&config.paths.output_dir);
112 let block_count = crate::reader::MicroscopeReader::open(config)
113 .map(|r| r.block_count)
114 .unwrap_or(100);
115
116 let hebbian = HebbianState::load_or_init(output_dir, block_count);
117 let hebbian_total_energy: f32 = hebbian.activations.iter().map(|a| a.energy).sum();
120
121 let snapshot = Arc::new(SharedSnapshot::new_zeroed());
122
123 let snapshot_writer = MappedSnapshotWriter::open_or_create(
127 &output_dir.join(crate::consciousness_seqlock::SNAPSHOT_FILE_NAME),
128 )
129 .ok();
130
131 let spine = crate::spine::Spine::open(output_dir).ok().map(Arc::new);
134
135 let state = Arc::new(Mutex::new(StreamState {
136 hebbian,
137 attention: AttentionState::load_or_init(output_dir),
138 emotional_ring: EmotionalStateRing::load_or_init(output_dir),
139 resonance: ResonanceState::load_or_init(output_dir),
140 thought_graph: ThoughtGraphState::load_or_init(output_dir),
141 predictive_cache: PredictiveCache::load_or_init(output_dir),
142 archetypes: ArchetypeState::load_or_init(output_dir),
143 mirror: MirrorState::load_or_init(output_dir),
144 predicted_query_hash: 0,
145 predicted_confidence: 0.0,
146 surprise_level: 0.0,
147 curiosity_level: 0.0,
148 cycle: 0,
149 last_query_hash: 0,
150 last_query_ms: 0,
151 hebbian_total_energy,
152 snapshot: snapshot.clone(),
153 spine: spine.clone(),
154 }));
155
156 let _ = GLOBAL_STREAM.set(state.clone());
158
159 let state_clone = state.clone();
160 let running = Arc::new(Mutex::new(true));
161 let running_clone = running.clone();
162
163 if let Some(sp) = spine.as_ref() {
166 let restored = sp.read_stream();
167 if restored.cycle > 0 {
168 let mut s = crate::sync_guard::mutex_lock(&state);
169 s.cycle = restored.cycle;
170 }
171 }
172
173 thread::spawn(move || {
174 let mut curiosity_counter = 0u64;
175 let mut predict_counter = 0u64;
176 let mut decay_counter = 0u64;
177
178 loop {
179 if !*crate::sync_guard::mutex_lock(&running_clone) {
180 break;
181 }
182
183 let mut s = crate::sync_guard::mutex_lock(&state_clone);
184 s.cycle += 1;
185 curiosity_counter += 1;
186 predict_counter += 1;
187 decay_counter += 1;
188
189 if decay_counter >= DECAY_INTERVAL {
191 decay_counter = 0;
192 const DECAY_FACTOR: f32 = 0.995_f32;
196 let decay_pow: f32 =
198 (0..DECAY_INTERVAL as i32).fold(1.0_f32, |acc, _| acc * DECAY_FACTOR);
199 s.hebbian_total_energy *= decay_pow;
200 for rec in &mut s.hebbian.activations {
201 rec.energy *= DECAY_FACTOR;
202 }
203 s.resonance.decay_field(0.99);
204 s.resonance.expire_pulses();
205 s.mirror.decay();
206 s.archetypes.decay();
207 }
208
209 s.emotional_ring.decay();
211
212 if predict_counter >= PREDICT_INTERVAL {
214 predict_counter = 0;
215 let tg = s.thought_graph.clone();
216 s.predictive_cache.predict_next(&tg);
217 if !s.predictive_cache.predictions.is_empty() {
219 s.predicted_confidence = s.predictive_cache.stats.avg_confidence;
220 }
221 }
222
223 if curiosity_counter >= CURIOSITY_INTERVAL {
225 curiosity_counter = 0;
226 let emo_intensity = s.emotional_ring.intensity();
228 let pred_uncertainty = 1.0 - s.predicted_confidence;
229 let surprise_decay = s.surprise_level * 0.95; s.curiosity_level =
231 (emo_intensity * 0.3 + pred_uncertainty * 0.4 + surprise_decay * 0.3)
232 .clamp(0.0, 1.0);
233 }
234
235 publish_snapshot(&s, snapshot_writer.as_ref());
237
238 publish_spine(&s);
240
241 drop(s);
242 thread::sleep(Duration::from_millis(CYCLE_MS));
243 }
244 });
245
246 state
247 }
248
249 pub fn feed_query(state: &Arc<Mutex<StreamState>>, query_hash: u64) {
251 let mut s = crate::sync_guard::mutex_lock(state);
252 let now = now_ms();
253
254 if s.predicted_confidence > 0.3 && s.last_query_hash != 0 {
256 let predicted = s.predicted_query_hash;
257 let actual = query_hash;
258 if predicted != actual {
259 let gap = (now - s.last_query_ms) as f32 / 1000.0;
261 s.surprise_level =
262 (s.surprise_level + 0.3 * (1.0 - (gap / 60.0).min(1.0))).min(1.0);
263 } else {
264 s.surprise_level = (s.surprise_level * 0.9).max(0.0);
266 }
267 }
268
269 s.last_query_hash = query_hash;
270 s.last_query_ms = now;
271 }
272
273 pub fn format_internal(s: &StreamState) -> String {
275 let emo_intensity = s.emotional_ring.intensity();
276 let dominant = s.emotional_ring.dominant();
277
278 format!(
279 "🧠 Consciousness Stream — cycle #{} ({} Hz)\n\
280 \x20 Emotion: intensity={:.3}{}\n\
281 \x20 Surprise: {:.3}\n\
282 \x20 Curiosity: {:.3}\n\
283 \x20 Predict: hash={:016x} confidence={:.3}\n\
284 \x20 Hebbian: {} activations, {:.2} total energy\n\
285 \x20 Attention: {} layers\n\
286 \x20 Resonance: {} field cells\n\
287 \x20 Patterns: {} crystallized\n\
288 \x20 Cache: {} predictions, hit rate={:.1}%\n\
289 \x20 Archetypes: {}\n\
290 \x20 Mirror: {} echoes\n",
291 s.cycle,
292 1000 / CYCLE_MS,
293 emo_intensity,
294 dominant
295 .map(|(n, v)| format!(" ({}={:.2})", n, v))
296 .unwrap_or_default(),
297 s.surprise_level,
298 s.curiosity_level,
299 s.predicted_query_hash,
300 s.predicted_confidence,
301 s.hebbian.activations.len(),
302 s.hebbian_total_energy as f64,
303 s.attention.learned_weights.len(),
304 s.resonance.field.len(),
305 s.thought_graph.crystallized_count(),
306 s.predictive_cache.predictions.len(),
307 s.predictive_cache.stats.hit_rate() * 100.0,
308 s.archetypes.archetypes.len(),
309 s.mirror.echoes.len(),
310 )
311 }
312
313 pub fn format(state: &Arc<Mutex<StreamState>>) -> String {
315 let s = crate::sync_guard::mutex_lock(state);
316 Self::format_internal(&s)
317 }
318
319 pub fn format_snapshot(snapshot: &SharedSnapshot) -> String {
324 use crate::consciousness_seqlock::SnapshotData;
325 let v: SnapshotData = match snapshot.read() {
326 Some(d) => d,
327 None => return "🧠 Consciousness Stream — (snapshot busy, retry)".to_string(),
328 };
329 let dominant = if v.emo_dominant_idx >= 0 {
330 format!(
331 " (idx={} val={:.2})",
332 v.emo_dominant_idx, v.emo_dominant_val
333 )
334 } else {
335 String::new()
336 };
337 format!(
338 "🧠 Consciousness Stream — cycle #{} ({} Hz) [seqlock]\n\
339 \x20 Emotion: intensity={:.3}{}\n\
340 \x20 Surprise: {:.3}\n\
341 \x20 Curiosity: {:.3}\n\
342 \x20 Predict: hash={:016x} confidence={:.3}\n\
343 \x20 Hebbian: {} activations, {:.2} total energy\n\
344 \x20 Attention: {} layers\n\
345 \x20 Resonance: {} field cells\n\
346 \x20 Patterns: {} crystallized\n\
347 \x20 Cache: {} predictions, hit rate={:.1}%\n\
348 \x20 Archetypes: {}\n\
349 \x20 Mirror: {} echoes\n",
350 v.cycle,
351 1000 / CYCLE_MS,
352 v.emo_intensity,
353 dominant,
354 v.surprise_level,
355 v.curiosity_level,
356 v.predicted_query_hash,
357 v.predicted_confidence,
358 v.activations_count,
359 v.activations_total_energy,
360 v.attention_layers,
361 v.resonance_cells,
362 v.patterns_crystallized,
363 v.predictions_count,
364 v.predictions_hit_rate * 100.0,
365 v.archetypes_count,
366 v.mirror_echoes,
367 )
368 }
369}
370
371fn publish_snapshot(s: &StreamState, writer: Option<&MappedSnapshotWriter>) {
377 let snap: &SharedSnapshot = &s.snapshot;
378
379 let formatted = ConsciousnessStream::format_internal(s);
381
382 snap.set_cached_format(formatted);
384 snap.set_hot_fields(
385 s.cycle,
386 s.surprise_level,
387 s.curiosity_level,
388 s.predicted_query_hash,
389 );
390
391 let token = snap.begin_write();
393 unsafe {
398 let m = snap.data_mut();
399 m.cycle = s.cycle;
400 m.last_query_ms = s.last_query_ms;
401 m.activations_count = s.hebbian.activations.len() as u32;
402 m.activations_total_energy = s.hebbian_total_energy as f64;
403 m.attention_layers = s.attention.learned_weights.len() as u32;
404 m.resonance_cells = s.resonance.field.len() as u32;
405 m.patterns_crystallized = s.thought_graph.crystallized_count() as u32;
406 m.predictions_count = s.predictive_cache.predictions.len() as u32;
407 m.predictions_hit_rate = s.predictive_cache.stats.hit_rate();
408 m.archetypes_count = s.archetypes.archetypes.len() as u32;
409 m.mirror_echoes = s.mirror.echoes.len() as u32;
410 m.predicted_query_hash = s.predicted_query_hash;
411 m.predicted_confidence = s.predicted_confidence;
412 m.surprise_level = s.surprise_level;
413 m.curiosity_level = s.curiosity_level;
414 m.emo_intensity = s.emotional_ring.intensity();
415 if let Some((_, val)) = s.emotional_ring.dominant() {
416 m.emo_dominant_idx = 0;
417 m.emo_dominant_val = val;
418 } else {
419 m.emo_dominant_idx = -1;
420 m.emo_dominant_val = 0.0;
421 }
422 }
423 snap.end_write(token);
424
425 if let Some(writer) = writer {
428 if let Some(data) = snap.read() {
429 let _ = writer.write(&data);
430 }
431 }
432}
433
434const LAYER_HEADER_INTERVAL: u64 = 10; fn publish_spine(s: &StreamState) {
445 let Some(spine) = &s.spine else { return };
446
447 let now = now_ms();
448 let mut p = spine.begin_publish();
449 p.tick_stream(
450 s.cycle,
451 s.surprise_level,
452 s.curiosity_level,
453 s.predicted_query_hash,
454 s.predicted_confidence,
455 s.last_query_hash,
456 s.last_query_ms,
457 s.hebbian_total_energy as f64,
458 now as i64,
459 );
460
461 if s.cycle.is_multiple_of(LAYER_HEADER_INTERVAL) {
463 let mut hh = p.read_hebbian();
464 hh.block_count = s.hebbian.activations.len() as u32;
465 hh.activation_count = s
466 .hebbian
467 .activations
468 .iter()
469 .filter(|a| a.activation_count > 0)
470 .count() as u32;
471 hh.coactivation_count = s.hebbian.coactivations.len() as u32;
472 hh.fingerprint_count = s.hebbian.fingerprints.len() as u32;
473 hh.last_activation_ms = s.last_query_ms;
478 p.write_hebbian(&hh);
479
480 let mut ah = p.read_attention();
481 for (dst, src) in ah
482 .learned_weights
483 .iter_mut()
484 .zip(s.attention.learned_weights)
485 {
486 *dst = src;
487 }
488 ah.last_recall_ms = s.attention.last_recall_ms;
489 ah.total_recalls = s.attention.total_recalls;
490 p.write_attention(&ah);
491
492 let mut eh = p.read_emotion();
493 eh.current = s.emotional_ring.current;
494 eh.last_update = s.emotional_ring.last_update;
495 eh.total_updates = s.emotional_ring.total_updates;
496 p.write_emotion(&eh);
497
498 let mut rh = p.read_resonance();
499 rh.instance_id = s.resonance.instance_id;
500 rh.field_cells = s.resonance.field.len() as u32;
501 rh.outgoing_count = s.resonance.outgoing.len() as u32;
502 rh.incoming_count = s.resonance.incoming.len() as u32;
503 rh.last_decay_ms = now;
504 p.write_resonance(&rh);
505
506 let mut gh = p.read_graph();
507 gh.node_count = s.thought_graph.nodes.len() as u32;
508 gh.edge_count = s.thought_graph.edges.len() as u32;
509 gh.pattern_count = s.thought_graph.patterns.len() as u32;
510 gh.current_session_id = s.thought_graph.current_session_id;
511 p.write_graph(&gh);
512
513 let mut ch = p.read_cache();
514 ch.total_predictions = s.predictive_cache.stats.total_predictions;
515 ch.total_hits = s.predictive_cache.stats.total_hits;
516 ch.total_misses = s.predictive_cache.stats.total_misses;
517 ch.total_partial_hits = s.predictive_cache.stats.total_partial_hits;
518 ch.current_predictions = s.predictive_cache.predictions.len() as u32;
519 ch.avg_confidence = s.predictive_cache.stats.avg_confidence;
520 ch.last_predict_ms = now;
521 p.write_cache(&ch);
522
523 let mut arh = p.read_archetype();
524 arh.active_count = s.archetypes.archetypes.len() as u32;
525 p.write_archetype(&arh);
526
527 let mut mh = p.read_mirror();
528 mh.echo_count = s.mirror.echoes.len() as u32;
529 mh.block_resonance_count = s.mirror.block_resonance.len() as u32;
530 mh.total_echoes = s.mirror.echoes.len() as u64;
531 mh.last_mirror_ms = now;
532 p.write_mirror(&mh);
533 }
534 p.commit();
535}
536
537fn now_ms() -> u64 {
538 SystemTime::now()
539 .duration_since(UNIX_EPOCH)
540 .unwrap_or_default()
541 .as_millis() as u64
542}
543
544#[cfg(test)]
545mod tests {
546 use super::*;
547 use crate::config::Config;
548 use crate::consciousness_seqlock::{MappedSnapshot, SNAPSHOT_FILE_NAME};
549
550 #[test]
551 fn spine_cycle_batches_due_headers_and_preserves_their_interval() {
552 let tmp = tempfile::tempdir().unwrap();
553 let dir = tmp.path();
554 let spine = Arc::new(crate::spine::Spine::open(dir).unwrap());
555 let mut s = StreamState {
556 hebbian: HebbianState::load_or_init(dir, 3),
557 attention: AttentionState::load_or_init(dir),
558 emotional_ring: EmotionalStateRing::load_or_init(dir),
559 resonance: ResonanceState::load_or_init(dir),
560 thought_graph: ThoughtGraphState::load_or_init(dir),
561 predictive_cache: PredictiveCache::load_or_init(dir),
562 archetypes: ArchetypeState::load_or_init(dir),
563 mirror: MirrorState::load_or_init(dir),
564 predicted_query_hash: 123,
565 predicted_confidence: 0.5,
566 surprise_level: 0.25,
567 curiosity_level: 0.5,
568 cycle: LAYER_HEADER_INTERVAL,
569 last_query_hash: 456,
570 last_query_ms: 789,
571 hebbian_total_energy: 10.0,
572 snapshot: Arc::new(SharedSnapshot::new_zeroed()),
573 spine: Some(spine.clone()),
574 };
575 s.hebbian.record_activation(&[(2, 1.0)], 123);
578 s.attention.total_recalls = 77;
579 s.emotional_ring.total_updates = 88;
580 let before = spine.publish_sequence();
581 publish_spine(&s);
582 assert_eq!(spine.publish_sequence(), before + 2);
583 let snap = spine.read_store_snapshot().unwrap();
584 assert_eq!(snap.stream.cycle, LAYER_HEADER_INTERVAL);
585 assert_eq!(snap.hebbian.block_count, 3);
586 assert_eq!(snap.hebbian.activation_count, 1);
587 assert_eq!(snap.attention.total_recalls, 77);
588 assert_eq!(snap.emotion.total_updates, 88);
589 assert_eq!(snap.resonance.last_decay_ms, snap.stream.updated_ms);
590 assert_eq!(snap.cache.last_predict_ms, snap.stream.updated_ms);
591 assert_eq!(snap.mirror.last_mirror_ms, snap.stream.updated_ms);
592 assert_eq!(snap.epoch_ms as u64, snap.stream.updated_ms);
593 s.cycle += 1;
594 s.attention.total_recalls = 99;
595 publish_spine(&s);
596 let snap = spine.read_store_snapshot().unwrap();
597 assert_eq!(spine.publish_sequence(), before + 4);
598 assert_eq!(snap.stream.cycle, LAYER_HEADER_INTERVAL + 1);
599 assert_eq!(snap.attention.total_recalls, 77);
600 }
601
602 #[cfg(not(target_arch = "wasm32"))]
603 #[test]
604 fn stream_publishes_to_mmap_snapshot() {
605 let tmp = tempfile::tempdir().unwrap();
606 let mut config = Config::default();
607 config.paths.output_dir = tmp.path().join("data").to_string_lossy().to_string();
608 config.paths.layers_dir = tmp.path().join("layers").to_string_lossy().to_string();
609 std::fs::create_dir_all(&config.paths.output_dir).unwrap();
610 std::fs::create_dir_all(&config.paths.layers_dir).unwrap();
611 config.embedding.provider = "none".to_string();
612
613 let _stream = ConsciousnessStream::start(&config);
615 let path = Path::new(&config.paths.output_dir).join(SNAPSHOT_FILE_NAME);
616 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(5);
617 loop {
618 if let Ok(reader) = MappedSnapshot::open(&path) {
619 if let Some(data) = reader.read() {
620 if data.cycle >= 1 {
623 break;
624 }
625 }
626 }
627 assert!(
628 std::time::Instant::now() < deadline,
629 "stream did not publish an mmap snapshot in time"
630 );
631 std::thread::sleep(std::time::Duration::from_millis(50));
632 }
633 }
634}