Skip to main content

microscope_memory/
absentia.rs

1//! Absentia — Csend Réteg / Silence Layer
2//!
3//! Azt követi nyomon, ami NEM történt meg, amit NEM mondtak ki,
4//! és ami hiányzik onnan, ahol lennie kellene.
5//!
6//! Ez a réteg a causal laundering egyetlen valódi ellenszere:
7//! nem a jelenlétet, hanem a *hiányt* detektálja.
8//!
9//! Bináris fájl: absentia.bin (ABS1)
10
11use std::path::Path;
12use std::time::{SystemTime, UNIX_EPOCH};
13
14use crate::epistemic::EvidenceLedger;
15use crate::hebbian::HebbianState;
16
17// ─── Helpers ────────────────────────────────────────
18
19fn now_ms() -> u64 {
20    SystemTime::now()
21        .duration_since(UNIX_EPOCH)
22        .unwrap_or_default()
23        .as_millis() as u64
24}
25
26// ─── AbsencePattern ─────────────────────────────────
27
28/// A hiány mintázatának típusa.
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum AbsencePattern {
31    /// Rendszeresen jelen volt, eltűnt.
32    ExpectedDisappearance,
33    /// Logikusan ott kellene lennie, de nincs.
34    LogicalAbsence,
35    /// Elkerült téma — soha nem bukkan fel, pedig illeszkedne.
36    Taboo,
37    /// Befejezetlen gondolat — nyitott hurok.
38    InterruptedThought,
39    /// Együtt KELLENE aktiválódni, de nem — anti-Hebbian jel.
40    AntiHebbian,
41}
42
43impl std::fmt::Display for AbsencePattern {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        match self {
46            AbsencePattern::ExpectedDisappearance => write!(f, "disappearance"),
47            AbsencePattern::LogicalAbsence => write!(f, "logical_absence"),
48            AbsencePattern::Taboo => write!(f, "taboo"),
49            AbsencePattern::InterruptedThought => write!(f, "interrupted"),
50            AbsencePattern::AntiHebbian => write!(f, "anti_hebbian"),
51        }
52    }
53}
54
55impl AbsencePattern {
56    pub fn from_u8(v: u8) -> Self {
57        match v {
58            0 => Self::ExpectedDisappearance,
59            1 => Self::LogicalAbsence,
60            2 => Self::Taboo,
61            3 => Self::InterruptedThought,
62            4 => Self::AntiHebbian,
63            _ => Self::LogicalAbsence,
64        }
65    }
66
67    pub fn to_u8(self) -> u8 {
68        match self {
69            Self::ExpectedDisappearance => 0,
70            Self::LogicalAbsence => 1,
71            Self::Taboo => 2,
72            Self::InterruptedThought => 3,
73            Self::AntiHebbian => 4,
74        }
75    }
76}
77
78// ─── AbsentiaRecord ─────────────────────────────────
79
80/// Egyetlen hiány-rekord: ami logikusan ott kellene legyen, de nincs.
81#[derive(Debug, Clone)]
82pub struct AbsentiaRecord {
83    /// A kontextus hash-e, amiben a hiányt észleltük.
84    pub expected_context_hash: u64,
85    /// A hiányzó entitás hash-e.
86    pub missing_entity_hash: u64,
87    /// Hányszor fordult elő korábban ebben a kontextusban.
88    pub expected_frequency: f32,
89    /// Hányszor fordult elő ténylegesen.
90    pub actual_frequency: f32,
91    /// Hiány pontszám: 0.0 = jelen van, 1.0 = teljesen hiányzik.
92    pub absence_score: f32,
93    /// Mikor észleltük először.
94    pub first_detected_ms: u64,
95    /// Mennyi ideje hiányzik.
96    pub duration_ms: u64,
97    /// A hiány mintázatának típusa.
98    pub pattern_type: AbsencePattern,
99    /// Súly a morfogenezis gradiensben.
100    pub gradient_weight: f32,
101}
102
103// ─── AntiHebbianPair ────────────────────────────────
104
105/// Anti-Hebbian pár: két blokk, aminek együtt KELLENE aktiválódnia, de nem.
106#[derive(Debug, Clone)]
107pub struct AntiHebbianPair {
108    pub block_a: u32,
109    pub block_b: u32,
110    pub expected_coactivation: f32,
111    pub actual_coactivation: f32,
112    pub absence_score: f32,
113    pub first_detected_ms: u64,
114}
115
116// ─── AbsentiaState ──────────────────────────────────
117
118/// Az Absentia réteg állapota.
119#[derive(Default)]
120pub struct AbsentiaState {
121    /// Hiány-rekordok.
122    pub records: Vec<AbsentiaRecord>,
123    /// Anti-Hebbian párok.
124    pub anti_hebbian: Vec<AntiHebbianPair>,
125    /// Negatív attractorok a MorphogenFieldben: (x, y, z, weight).
126    pub negative_attractors: Vec<(f64, f64, f64, f64)>,
127    /// Utolsó szkennelés időpontja.
128    pub last_scan_ms: u64,
129}
130
131impl AbsentiaState {
132    pub fn new() -> Self {
133        Self::default()
134    }
135
136    /// Betölti vagy inicializálja az Absentia állapotot.
137    pub fn load_or_init(output_dir: &Path) -> Self {
138        let path = output_dir.join("absentia.bin");
139        if let Ok(data) = std::fs::read(&path) {
140            if data.len() >= 4 && &data[0..4] == b"ABS1" {
141                return Self::decode(&data);
142            }
143        }
144        Self::new()
145    }
146
147    /// Elmenti az Absentia állapotot.
148    pub fn save(&self, output_dir: &Path) -> Result<(), String> {
149        let path = output_dir.join("absentia.bin");
150        let data = self.encode();
151        let tmp = output_dir.join("absentia.bin.tmp");
152        std::fs::write(&tmp, &data).map_err(|e| format!("write absentia: {}", e))?;
153        std::fs::rename(&tmp, &path).map_err(|e| format!("rename absentia: {}", e))?;
154        Ok(())
155    }
156
157    /// Szkenneli a Hebbian állapotot és az Evidence ledgert hiányok után.
158    ///
159    /// Anti-Hebbian detektálás: két blokk, aminek együtt KELLENE aktiválódnia
160    /// (struktúra alapján), de a co-aktivációs rekordjuk alacsony vagy nulla.
161    pub fn scan(&mut self, hebb: &HebbianState, _evidence: &EvidenceLedger, _block_count: usize) {
162        let now = now_ms();
163        self.last_scan_ms = now;
164
165        // ─── Anti-Hebbian detektálás ─────────────────
166        // Keresünk blokk-párokat, amik:
167        // 1. Ugyanabban a rétegben vannak (strukturális szomszédok)
168        // 2. Magas activation_count-juk van külön-külön
169        // 3. Alacsony vagy nulla co-aktivációjuk van
170
171        // Top aktív blokkok
172        let mut active_blocks: Vec<(u32, f32)> = hebb
173            .activations
174            .iter()
175            .enumerate()
176            .filter(|(_, r)| r.energy > 0.3)
177            .map(|(i, r)| (i as u32, r.energy))
178            .collect();
179        active_blocks.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
180        active_blocks.truncate(50); // top 50
181
182        // Páronként ellenőrizzük
183        for i in 0..active_blocks.len() {
184            for j in (i + 1)..active_blocks.len() {
185                let a = active_blocks[i].0.min(active_blocks[j].0);
186                let b = active_blocks[i].0.max(active_blocks[j].0);
187                let pair_key = (a, b);
188
189                // Ha nincs co-aktiváció, de mindkét blokk aktív
190                if !hebb.coactivations.contains_key(&pair_key) {
191                    let energy_a = active_blocks[i].1;
192                    let energy_b = active_blocks[j].1;
193                    // Minél aktívabbak külön-külön, annál gyanúsabb az együtt nem aktiválódás
194                    let absence = (energy_a * energy_b).min(1.0);
195
196                    if absence > 0.2 {
197                        // Anti-Hebbian pár
198                        self.anti_hebbian.push(AntiHebbianPair {
199                            block_a: a,
200                            block_b: b,
201                            expected_coactivation: absence,
202                            actual_coactivation: 0.0,
203                            absence_score: absence,
204                            first_detected_ms: now,
205                        });
206                    }
207                }
208            }
209        }
210
211        // ─── Evidence hiány detektálás ───────────────
212        // Keresünk aktív blokkokat, amiknek NINCS evidence record-uk
213        // de magas Hebbian energiájuk van — ez "hamis biztonság"
214        for (i, rec) in hebb.activations.iter().enumerate() {
215            if rec.energy > 0.5 {
216                // Nincs evidence record — feltűnő hiány
217                // (nem tudjuk közvetlenül ellenőrizni content_hash nélkül,
218                //  de a hiány detektálható)
219                self.records.push(AbsentiaRecord {
220                    expected_context_hash: i as u64,
221                    missing_entity_hash: 0,
222                    expected_frequency: rec.activation_count as f32,
223                    actual_frequency: 0.0,
224                    absence_score: rec.energy,
225                    first_detected_ms: now,
226                    duration_ms: 0,
227                    pattern_type: AbsencePattern::LogicalAbsence,
228                    gradient_weight: rec.energy * 0.5,
229                });
230            }
231        }
232
233        // ─── Negatív attractorok építése ─────────────
234        // Az anti-Hebbian párok negatív attractorokat hoznak létre
235        // a MorphogenFieldben — olyan pontokat, ahova a hifák NEM nőnek
236        self.negative_attractors.clear();
237        for pair in &self.anti_hebbian {
238            // A negatív attractor a két blokk közötti térben helyezkedik el
239            // Ez "árnyékot vet" a gradiensre
240            let weight = -(pair.absence_score as f64);
241            self.negative_attractors.push((
242                pair.block_a as f64 * 0.1,
243                pair.block_b as f64 * 0.1,
244                0.0,
245                weight,
246            ));
247        }
248
249        // Korlátozzuk a rekordok számát
250        if self.records.len() > 500 {
251            self.records.drain(0..self.records.len() - 500);
252        }
253        if self.anti_hebbian.len() > 200 {
254            self.anti_hebbian.drain(0..self.anti_hebbian.len() - 200);
255        }
256    }
257
258    /// Statisztikák.
259    pub fn stats(&self) -> AbsentiaStats {
260        let total_records = self.records.len();
261        let anti_hebbian_count = self.anti_hebbian.len();
262        let negative_attractor_count = self.negative_attractors.len();
263
264        let avg_absence = if total_records > 0 {
265            self.records
266                .iter()
267                .map(|r| r.absence_score as f64)
268                .sum::<f64>()
269                / total_records as f64
270        } else {
271            0.0
272        };
273
274        let avg_anti_hebbian = if anti_hebbian_count > 0 {
275            self.anti_hebbian
276                .iter()
277                .map(|p| p.absence_score as f64)
278                .sum::<f64>()
279                / anti_hebbian_count as f64
280        } else {
281            0.0
282        };
283
284        // Causal laundering gyanús: anti-Hebbian párok, ahol a co-aktiváció 0
285        // de mindkét blokk aktív
286        let causal_laundering_suspect = self
287            .anti_hebbian
288            .iter()
289            .filter(|p| p.absence_score > 0.5)
290            .count();
291
292        AbsentiaStats {
293            total_records,
294            anti_hebbian_count,
295            negative_attractor_count,
296            avg_absence,
297            avg_anti_hebbian,
298            causal_laundering_suspect,
299            last_scan_ms: self.last_scan_ms,
300        }
301    }
302
303    // ─── Bináris szerializás ────────────────────────
304
305    fn encode(&self) -> Vec<u8> {
306        let mut buf = Vec::new();
307        buf.extend_from_slice(b"ABS1");
308        buf.extend_from_slice(&(self.records.len() as u32).to_le_bytes());
309        buf.extend_from_slice(&(self.anti_hebbian.len() as u32).to_le_bytes());
310        buf.extend_from_slice(&(self.negative_attractors.len() as u32).to_le_bytes());
311        buf.extend_from_slice(&self.last_scan_ms.to_le_bytes());
312
313        // Records
314        for r in &self.records {
315            buf.extend_from_slice(&r.expected_context_hash.to_le_bytes());
316            buf.extend_from_slice(&r.missing_entity_hash.to_le_bytes());
317            buf.extend_from_slice(&r.expected_frequency.to_le_bytes());
318            buf.extend_from_slice(&r.actual_frequency.to_le_bytes());
319            buf.extend_from_slice(&r.absence_score.to_le_bytes());
320            buf.extend_from_slice(&r.first_detected_ms.to_le_bytes());
321            buf.extend_from_slice(&r.duration_ms.to_le_bytes());
322            buf.extend_from_slice(&r.pattern_type.to_u8().to_le_bytes());
323            buf.extend_from_slice(&r.gradient_weight.to_le_bytes());
324        }
325
326        // Anti-Hebbian pairs
327        for p in &self.anti_hebbian {
328            buf.extend_from_slice(&p.block_a.to_le_bytes());
329            buf.extend_from_slice(&p.block_b.to_le_bytes());
330            buf.extend_from_slice(&p.expected_coactivation.to_le_bytes());
331            buf.extend_from_slice(&p.actual_coactivation.to_le_bytes());
332            buf.extend_from_slice(&p.absence_score.to_le_bytes());
333            buf.extend_from_slice(&p.first_detected_ms.to_le_bytes());
334        }
335
336        // Negative attractors
337        for &(x, y, z, w) in &self.negative_attractors {
338            buf.extend_from_slice(&x.to_le_bytes());
339            buf.extend_from_slice(&y.to_le_bytes());
340            buf.extend_from_slice(&z.to_le_bytes());
341            buf.extend_from_slice(&w.to_le_bytes());
342        }
343
344        buf
345    }
346
347    fn decode(data: &[u8]) -> Self {
348        if data.len() < 20 {
349            return Self::new();
350        }
351        let record_count = u32::from_le_bytes(data[4..8].try_into().unwrap()) as usize;
352        let anti_count = u32::from_le_bytes(data[8..12].try_into().unwrap()) as usize;
353        let neg_count = u32::from_le_bytes(data[12..16].try_into().unwrap()) as usize;
354        let last_scan = u64::from_le_bytes(data[16..24].try_into().unwrap());
355
356        let mut off = 24;
357        let mut records = Vec::with_capacity(record_count);
358        for _ in 0..record_count {
359            if off + 41 > data.len() {
360                break;
361            }
362            let expected_context_hash = u64::from_le_bytes(data[off..off + 8].try_into().unwrap());
363            off += 8;
364            let missing_entity_hash = u64::from_le_bytes(data[off..off + 8].try_into().unwrap());
365            off += 8;
366            let expected_frequency = f32::from_le_bytes(data[off..off + 4].try_into().unwrap());
367            off += 4;
368            let actual_frequency = f32::from_le_bytes(data[off..off + 4].try_into().unwrap());
369            off += 4;
370            let absence_score = f32::from_le_bytes(data[off..off + 4].try_into().unwrap());
371            off += 4;
372            let first_detected_ms = u64::from_le_bytes(data[off..off + 8].try_into().unwrap());
373            off += 8;
374            let duration_ms = u64::from_le_bytes(data[off..off + 8].try_into().unwrap());
375            off += 8;
376            let pattern_byte = data[off];
377            off += 1;
378            let gradient_weight = f32::from_le_bytes(data[off..off + 4].try_into().unwrap());
379            off += 4;
380            records.push(AbsentiaRecord {
381                expected_context_hash,
382                missing_entity_hash,
383                expected_frequency,
384                actual_frequency,
385                absence_score,
386                first_detected_ms,
387                duration_ms,
388                pattern_type: AbsencePattern::from_u8(pattern_byte),
389                gradient_weight,
390            });
391        }
392
393        let mut anti_hebbian = Vec::with_capacity(anti_count);
394        for _ in 0..anti_count {
395            if off + 28 > data.len() {
396                break;
397            }
398            let block_a = u32::from_le_bytes(data[off..off + 4].try_into().unwrap());
399            off += 4;
400            let block_b = u32::from_le_bytes(data[off..off + 4].try_into().unwrap());
401            off += 4;
402            let expected_coactivation = f32::from_le_bytes(data[off..off + 4].try_into().unwrap());
403            off += 4;
404            let actual_coactivation = f32::from_le_bytes(data[off..off + 4].try_into().unwrap());
405            off += 4;
406            let absence_score = f32::from_le_bytes(data[off..off + 4].try_into().unwrap());
407            off += 4;
408            let first_detected_ms = u64::from_le_bytes(data[off..off + 8].try_into().unwrap());
409            off += 8;
410            anti_hebbian.push(AntiHebbianPair {
411                block_a,
412                block_b,
413                expected_coactivation,
414                actual_coactivation,
415                absence_score,
416                first_detected_ms,
417            });
418        }
419
420        let mut negative_attractors = Vec::with_capacity(neg_count);
421        for _ in 0..neg_count {
422            if off + 32 > data.len() {
423                break;
424            }
425            let x = f64::from_le_bytes(data[off..off + 8].try_into().unwrap());
426            off += 8;
427            let y = f64::from_le_bytes(data[off..off + 8].try_into().unwrap());
428            off += 8;
429            let z = f64::from_le_bytes(data[off..off + 8].try_into().unwrap());
430            off += 8;
431            let w = f64::from_le_bytes(data[off..off + 8].try_into().unwrap());
432            off += 8;
433            negative_attractors.push((x, y, z, w));
434        }
435
436        Self {
437            records,
438            anti_hebbian,
439            negative_attractors,
440            last_scan_ms: last_scan,
441        }
442    }
443}
444
445// ─── AbsentiaStats ──────────────────────────────────
446
447pub struct AbsentiaStats {
448    pub total_records: usize,
449    pub anti_hebbian_count: usize,
450    pub negative_attractor_count: usize,
451    pub avg_absence: f64,
452    pub avg_anti_hebbian: f64,
453    pub causal_laundering_suspect: usize,
454    pub last_scan_ms: u64,
455}
456
457// ─── Morfogenezis integráció ────────────────────────
458
459/// Shadow term integráció a MorphogenFieldbe.
460///
461/// A shadow term formula:
462///   effective_gradient = positive_gradient * (1 - absence_shadow)
463///
464/// Ez megőrzi a különbséget aközött, hogy "nincs vonzás" vs "van vonzás,
465/// de valami hiány miatt gyanús". Nem mínusz gradiens — hanem szorzó.
466pub fn apply_absentia_to_field(
467    absentia: &AbsentiaState,
468    field: &mut crate::morphogenesis::MorphogenField,
469    evidence_confidence: u8,
470) {
471    // Minden gradiens-pontnál kiszámoljuk a shadow értéket
472    // és megszorozzuk a gradienst (1 - shadow)-val
473    let keys: Vec<(i32, i32, i32)> = field.gradients.keys().cloned().collect();
474    for key in keys {
475        let shadow = compute_absence_shadow(
476            absentia,
477            key.0 as f64,
478            key.1 as f64,
479            key.2 as f64,
480            evidence_confidence,
481        );
482        if shadow > 0.01 {
483            if let Some(val) = field.gradients.get_mut(&key) {
484                *val *= 1.0 - shadow;
485            }
486        }
487    }
488}
489
490/// Kiszámolja az absence shadow értéket egy adott pontban.
491///
492/// A shadow a következőkből áll:
493/// 1. Anti-Hebbian távolság: közel anti-Hebbian párokhoz → magasabb shadow
494/// 2. Absence records: közel absence rekordokhoz → magasabb shadow
495///
496/// Visszatérés: [0.0, 0.95] — 0.0 = nincs árnyék, 0.95 = teljes árnyék
497pub fn compute_absence_shadow(
498    absentia: &AbsentiaState,
499    x: f64,
500    y: f64,
501    z: f64,
502    evidence_confidence: u8,
503) -> f64 {
504    let mut shadow = 0.0f64;
505
506    // Anti-Hebbian párok: a negatív attractorok pozíciójából
507    for &(ax, ay, az, weight) in &absentia.negative_attractors {
508        let dx = x - ax;
509        let dy = y - ay;
510        let dz = z - az;
511        let dist_sq = dx * dx + dy * dy + dz * dz;
512        // Inverz távolság-alapú shadow: közelebb = erősebb
513        let local_shadow = weight.abs() / (1.0 + dist_sq);
514        shadow += local_shadow;
515    }
516
517    // Absence records: a gradient_weight alapján
518    for rec in &absentia.records {
519        // A record pozíciója a expected_context_hash-ből
520        let ax = (rec.expected_context_hash % 100) as f64;
521        let ay = ((rec.expected_context_hash / 100) % 100) as f64;
522        let az = 0.0;
523        let dx = x - ax;
524        let dy = y - ay;
525        let dz = z - az;
526        let dist_sq = dx * dx + dy * dy + dz * dz;
527        let local_shadow = rec.gradient_weight as f64 / (1.0 + dist_sq);
528        shadow += local_shadow * 0.5; // fele súly, mint az anti-Hebbian
529    }
530
531    // Korlátozzuk [0.0, 0.95] tartományba — soha nem teljesen 1.0
532    // (a teljes blokkolás veszélyes, mindig hagyunk kis esélyt)
533    let base_shadow = shadow.min(0.95);
534
535    // Evidence moduláció: ha van evidence, a shadow csökken
536    // evidence_confidence: 0-100 → evidence_factor: 0.0-1.0
537    let evidence_factor = evidence_confidence as f64 / 100.0;
538    // A shadow csökken az evidence-szel: shadow * (1 - evidence * 0.8)
539    // Max 80%-os csökkenés — soha nem szünteti meg teljesen
540    let final_shadow = base_shadow * (1.0 - evidence_factor * 0.8);
541    final_shadow.max(0.0)
542}
543
544/// Presence-driven growth ↔ absence-driven inhibition teszt.
545///
546/// Ha egy erős Hebbian/predictive útvonal evidence nélkül indul,
547/// az Absentia shadow-nak csökkentenie kell a növekedést.
548/// Ha evidence-t adunk hozzá, a shadow-nak csökkennie kell.
549pub fn test_presence_absence_inhibition(
550    _absentia: &AbsentiaState,
551    hebb_energy: f64,
552    evidence_confidence: u8,
553) -> PresenceAbsenceResult {
554    let absence_shadow = if evidence_confidence < 10 {
555        // Nincs evidence → magas shadow
556        0.8
557    } else if evidence_confidence < 50 {
558        // Kevés evidence → közepes shadow
559        0.4
560    } else {
561        // Elég evidence → alacsony shadow
562        0.1
563    };
564
565    let effective_gradient = hebb_energy * (1.0 - absence_shadow);
566    let growth_inhibited = absence_shadow > 0.5;
567
568    PresenceAbsenceResult {
569        hebb_energy,
570        evidence_confidence,
571        absence_shadow,
572        effective_gradient,
573        growth_inhibited,
574    }
575}
576
577pub struct PresenceAbsenceResult {
578    pub hebb_energy: f64,
579    pub evidence_confidence: u8,
580    pub absence_shadow: f64,
581    pub effective_gradient: f64,
582    pub growth_inhibited: bool,
583}