1use crate::config::FieldConfig;
9use crate::observer::{FieldEvent, FieldObserver, MonitoredRegion, TriggerConfig};
10use crate::vector::FieldVector;
11use std::ops::Range;
12use std::sync::Arc;
13
14pub struct TemporalField {
26 frames: Vec<FieldVector>,
28
29 config: FieldConfig,
31
32 write_head: usize,
34
35 tick_count: u64,
37
38 observers: Vec<Arc<dyn FieldObserver>>,
40
41 triggers: TriggerConfig,
43
44 was_active: Vec<bool>,
46}
47
48impl TemporalField {
49 pub fn new(config: FieldConfig) -> Self {
56 let frames = (0..config.frame_count)
57 .map(|_| FieldVector::new(config.dims))
58 .collect();
59
60 Self {
61 frames,
62 config,
63 write_head: 0,
64 tick_count: 0,
65 observers: Vec::new(),
66 triggers: TriggerConfig::default(),
67 was_active: Vec::new(),
68 }
69 }
70
71 pub fn monitor_region(&mut self, region: MonitoredRegion) {
73 self.triggers.regions.push(region);
74 self.was_active.push(false);
75 }
76
77 pub fn set_convergence_threshold(&mut self, threshold: usize) {
79 self.triggers.convergence_threshold = threshold;
80 }
81
82 pub fn subscribe(&mut self, observer: Arc<dyn FieldObserver>) {
88 self.observers.push(observer);
89 }
90
91 pub fn clear_observers(&mut self) {
93 self.observers.clear();
94 }
95
96 fn fire(&self, event: FieldEvent) {
98 for observer in &self.observers {
99 observer.on_event(event.clone());
100 }
101 }
102
103 fn check_and_fire(&mut self) {
110 if self.triggers.regions.is_empty() {
111 return;
112 }
113
114 let mut active_regions = Vec::new();
115 let mut total_energy = 0.0;
116
117 for (i, region) in self.triggers.regions.iter().enumerate() {
118 let energy = self.frames[self.write_head].range_energy(region.range.clone());
119 let was = self.was_active.get(i).copied().unwrap_or(false);
120
121 let is_active = if was {
125 energy >= region.off_threshold
127 } else {
128 energy > region.on_threshold
130 };
131
132 if is_active && !was {
134 self.fire(FieldEvent::RegionActive {
135 region: region.range.clone(),
136 energy,
137 threshold: region.on_threshold,
138 });
139 }
140
141 if !is_active && was {
143 self.fire(FieldEvent::RegionQuiet {
144 region: region.range.clone(),
145 energy,
146 threshold: region.off_threshold,
147 });
148 }
149
150 if is_active {
152 active_regions.push(region.range.clone());
153 total_energy += energy * region.weight;
154 }
155
156 if i < self.was_active.len() {
158 self.was_active[i] = is_active;
159 }
160 }
161
162 if active_regions.len() >= self.triggers.convergence_threshold {
164 self.fire(FieldEvent::Convergence {
165 active_regions,
166 total_energy,
167 });
168 }
169 }
170
171 pub fn tick(&mut self) {
177 self.tick_count += 1;
178 for frame in &mut self.frames {
179 frame.decay(self.config.retention);
180 }
181 self.check_and_fire();
182 }
183
184 pub fn tick_n(&mut self, n: usize) {
186 for _ in 0..n {
187 self.tick();
188 }
189 }
190
191 pub fn advance_write_head(&mut self) {
193 self.write_head = (self.write_head + 1) % self.config.frame_count;
194 }
195
196 pub fn write_region(&mut self, values: &[f32], range: Range<usize>) {
202 self.frames[self.write_head].add_to_range(values, range);
203 self.check_and_fire();
204 }
205
206 pub fn set_region(&mut self, values: &[f32], range: Range<usize>) {
208 self.frames[self.write_head].set_range(values, range);
209 self.check_and_fire();
210 }
211
212 pub fn write_full(&mut self, vector: &FieldVector) {
214 self.frames[self.write_head].add(vector);
215 self.check_and_fire();
216 }
217
218 pub fn clear_current(&mut self) {
220 self.frames[self.write_head] = FieldVector::new(self.config.dims);
221 }
222
223 pub fn read_current(&self) -> &FieldVector {
229 &self.frames[self.write_head]
230 }
231
232 pub fn read_region(&self, range: Range<usize>) -> Vec<f32> {
234 self.frames[self.write_head].get_range(range)
235 }
236
237 pub fn region_energy(&self, range: Range<usize>) -> f32 {
239 self.frames[self.write_head].range_energy(range)
240 }
241
242 pub fn region_active(&self, range: Range<usize>, threshold: f32) -> bool {
244 self.region_energy(range) > threshold
245 }
246
247 pub fn read_window(&self, n: usize) -> Vec<&FieldVector> {
249 let n = n.min(self.config.frame_count);
250 let mut result = Vec::with_capacity(n);
251
252 for i in 0..n {
253 let idx = (self.write_head + self.config.frame_count - n + i)
254 % self.config.frame_count;
255 result.push(&self.frames[idx]);
256 }
257
258 result
259 }
260
261 pub fn region_peak(&self, range: Range<usize>, window: usize) -> Vec<f32> {
263 let frames = self.read_window(window);
264 if frames.is_empty() {
265 return vec![0.0; range.len()];
266 }
267
268 let mut best_frame_idx = 0;
269 let mut best_energy = 0.0f32;
270
271 for (i, frame) in frames.iter().enumerate() {
272 let energy = frame.range_energy(range.clone());
273 if energy > best_energy {
274 best_energy = energy;
275 best_frame_idx = i;
276 }
277 }
278
279 frames[best_frame_idx].get_range(range)
280 }
281
282 pub fn region_mean(&self, range: Range<usize>, window: usize) -> Vec<f32> {
284 let frames = self.read_window(window);
285 if frames.is_empty() {
286 return vec![0.0; range.len()];
287 }
288
289 let len = range.len();
290 let mut avg = vec![0.0; len];
291
292 for frame in &frames {
293 for (i, idx) in range.clone().enumerate() {
294 avg[i] += frame.get(idx);
295 }
296 }
297
298 let n = frames.len() as f32;
299 for v in &mut avg {
300 *v /= n;
301 }
302
303 avg
304 }
305
306 pub fn config(&self) -> &FieldConfig {
312 &self.config
313 }
314
315 pub fn triggers(&self) -> &TriggerConfig {
317 &self.triggers
318 }
319
320 pub fn regions(&self) -> &[MonitoredRegion] {
322 &self.triggers.regions
323 }
324
325 pub fn tick_count(&self) -> u64 {
327 self.tick_count
328 }
329
330 pub fn write_head(&self) -> usize {
332 self.write_head
333 }
334
335 pub fn dims(&self) -> usize {
337 self.config.dims
338 }
339
340 pub fn frame_count(&self) -> usize {
342 self.config.frame_count
343 }
344
345 pub fn max_energy(&self) -> f32 {
347 self.frames
348 .iter()
349 .map(|f| f.max_abs())
350 .fold(0.0f32, f32::max)
351 }
352
353 pub fn total_activity(&self) -> usize {
355 self.frames.iter().map(|f| f.non_zero_count()).sum()
356 }
357
358 pub fn clear(&mut self) {
360 for frame in &mut self.frames {
361 *frame = FieldVector::new(self.config.dims);
362 }
363 self.write_head = 0;
364 self.tick_count = 0;
365 self.was_active.fill(false);
366 }
367
368 pub fn ticks_to_ms(&self, ticks: u64) -> f32 {
370 (ticks as f32 * 1000.0) / self.config.tick_rate_hz as f32
371 }
372
373 pub fn ms_to_ticks(&self, ms: f32) -> u64 {
375 ((ms * self.config.tick_rate_hz as f32) / 1000.0).round() as u64
376 }
377}
378
379impl Clone for TemporalField {
380 fn clone(&self) -> Self {
383 Self {
384 frames: self.frames.clone(),
385 config: self.config.clone(),
386 write_head: self.write_head,
387 tick_count: self.tick_count,
388 observers: Vec::new(), triggers: self.triggers.clone(),
390 was_active: self.was_active.clone(),
391 }
392 }
393}
394
395impl std::fmt::Debug for TemporalField {
396 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
397 f.debug_struct("TemporalField")
398 .field("dims", &self.config.dims)
399 .field("frame_count", &self.config.frame_count)
400 .field("retention", &self.config.retention)
401 .field("write_head", &self.write_head)
402 .field("tick_count", &self.tick_count)
403 .field("observers", &self.observers.len())
404 .field("regions", &self.triggers.regions.len())
405 .finish()
406 }
407}
408
409#[cfg(test)]
410mod tests {
411 use super::*;
412 use std::sync::atomic::{AtomicUsize, Ordering};
413
414 #[test]
415 fn test_new_field() {
416 let config = FieldConfig::new(64, 10, 0.95);
417 let field = TemporalField::new(config);
418
419 assert_eq!(field.dims(), 64);
420 assert_eq!(field.frame_count(), 10);
421 assert_eq!(field.tick_count(), 0);
422 assert_eq!(field.total_activity(), 0);
423 }
424
425 #[test]
426 fn test_write_and_read_region() {
427 let config = FieldConfig::new(128, 10, 0.95);
428 let mut field = TemporalField::new(config);
429
430 let values = vec![0.5; 32];
431 field.write_region(&values, 0..32);
432
433 assert!(field.region_active(0..32, 0.1));
434 assert!(!field.region_active(32..64, 0.1));
435 }
436
437 #[test]
438 fn test_decay() {
439 let config = FieldConfig::new(64, 10, 0.5);
440 let mut field = TemporalField::new(config);
441
442 field.write_region(&vec![1.0; 64], 0..64);
443 let initial = field.region_energy(0..64);
444
445 field.tick();
446 let after_tick = field.region_energy(0..64);
447
448 assert!(after_tick < initial * 0.5);
449 }
450
451 #[test]
452 fn test_region_active_fires_event() {
453 let config = FieldConfig::new(64, 10, 0.95);
454 let mut field = TemporalField::new(config);
455
456 field.monitor_region(MonitoredRegion::new("test", 0..32, 0.1));
458
459 let count = Arc::new(AtomicUsize::new(0));
461 let count_clone = count.clone();
462 field.subscribe(Arc::new(crate::observer::FnObserver(move |event| {
463 if matches!(event, FieldEvent::RegionActive { .. }) {
464 count_clone.fetch_add(1, Ordering::SeqCst);
465 }
466 })));
467
468 field.write_region(&vec![0.5; 32], 0..32);
470
471 assert_eq!(count.load(Ordering::SeqCst), 1);
472 }
473
474 #[test]
475 fn test_convergence_fires() {
476 let config = FieldConfig::new(128, 10, 0.95);
477 let mut field = TemporalField::new(config);
478
479 field.monitor_region(MonitoredRegion::new("a", 0..32, 0.1));
481 field.monitor_region(MonitoredRegion::new("b", 32..64, 0.1));
482 field.monitor_region(MonitoredRegion::new("c", 64..96, 0.1));
483 field.set_convergence_threshold(2);
484
485 let convergence_count = Arc::new(AtomicUsize::new(0));
486 let cc = convergence_count.clone();
487
488 field.subscribe(Arc::new(crate::observer::FnObserver(move |event| {
489 if matches!(event, FieldEvent::Convergence { .. }) {
490 cc.fetch_add(1, Ordering::SeqCst);
491 }
492 })));
493
494 field.write_region(&vec![0.5; 32], 0..32);
496 field.write_region(&vec![0.5; 32], 32..64);
497
498 assert!(convergence_count.load(Ordering::SeqCst) >= 1);
499 }
500
501 #[test]
502 fn test_ring_buffer_wrap() {
503 let config = FieldConfig::new(64, 3, 1.0);
504 let mut field = TemporalField::new(config);
505
506 for i in 0..5 {
507 field.clear_current();
508 field.write_region(&vec![(i + 1) as f32 * 0.1; 64], 0..64);
509 field.advance_write_head();
510 }
511
512 assert_eq!(field.write_head(), 2);
513 }
514
515 #[test]
516 fn test_window_chronological() {
517 let config = FieldConfig::new(64, 5, 1.0);
518 let mut field = TemporalField::new(config);
519
520 for i in 0..3 {
521 field.clear_current();
522 field.write_region(&vec![(i + 1) as f32 * 0.25; 1], 0..1);
523 field.advance_write_head();
524 }
525
526 let window = field.read_window(3);
527 assert_eq!(window.len(), 3);
528
529 assert!((window[0].get(0) - 0.25).abs() < 0.01);
530 assert!((window[1].get(0) - 0.50).abs() < 0.01);
531 assert!((window[2].get(0) - 0.75).abs() < 0.01);
532 }
533
534 #[test]
535 fn test_hysteresis_prevents_chattering() {
536 let config = FieldConfig::new(1, 10, 1.0); let mut field = TemporalField::new(config);
540
541 field.monitor_region(MonitoredRegion::with_hysteresis("test", 0..1, 0.25, 0.09));
547
548 let active_count = Arc::new(AtomicUsize::new(0));
549 let quiet_count = Arc::new(AtomicUsize::new(0));
550 let ac = active_count.clone();
551 let qc = quiet_count.clone();
552
553 field.subscribe(Arc::new(crate::observer::FnObserver(move |event| {
554 match event {
555 FieldEvent::RegionActive { .. } => {
556 ac.fetch_add(1, Ordering::SeqCst);
557 }
558 FieldEvent::RegionQuiet { .. } => {
559 qc.fetch_add(1, Ordering::SeqCst);
560 }
561 _ => {}
562 }
563 })));
564
565 field.set_region(&[0.6], 0..1);
567 assert_eq!(active_count.load(Ordering::SeqCst), 1, "Should fire RegionActive");
568 assert_eq!(quiet_count.load(Ordering::SeqCst), 0, "Should not fire RegionQuiet");
569
570 field.set_region(&[0.4], 0..1);
573 assert_eq!(active_count.load(Ordering::SeqCst), 1, "Should not fire again (hysteresis)");
574 assert_eq!(quiet_count.load(Ordering::SeqCst), 0, "Should stay active (hysteresis)");
575
576 field.set_region(&[0.2], 0..1);
578 assert_eq!(active_count.load(Ordering::SeqCst), 1, "Should not fire RegionActive");
579 assert_eq!(quiet_count.load(Ordering::SeqCst), 1, "Should fire RegionQuiet");
580
581 field.set_region(&[0.4], 0..1);
584 assert_eq!(active_count.load(Ordering::SeqCst), 1, "Should not become active (hysteresis)");
585 assert_eq!(quiet_count.load(Ordering::SeqCst), 1, "Should stay quiet");
586
587 field.set_region(&[0.6], 0..1);
589 assert_eq!(active_count.load(Ordering::SeqCst), 2, "Should fire RegionActive again");
590 }
591
592 #[test]
593 fn test_default_hysteresis_gap() {
594 let region = MonitoredRegion::new("test", 0..32, 0.5);
596 assert!((region.on_threshold - 0.5).abs() < 0.001);
597 assert!((region.off_threshold - 0.4).abs() < 0.001); assert!((region.hysteresis_gap() - 0.2).abs() < 0.001);
599 }
600
601 #[test]
602 fn test_custom_hysteresis_gap() {
603 let region = MonitoredRegion::new("test", 0..32, 1.0).with_gap(0.3);
605 assert!((region.on_threshold - 1.0).abs() < 0.001);
606 assert!((region.off_threshold - 0.7).abs() < 0.001); assert!((region.hysteresis_gap() - 0.3).abs() < 0.001);
608 }
609}