Skip to main content

katgpt_types/
sense.rs

1//! Sense composition types.
2
3// ---------------------------------------------------------------------------
4// Shard Embedding — JL Random Orthogonal Projection (Plan 230)
5// ---------------------------------------------------------------------------
6// DEPRECATED (Issue 139, 2026-07-16): The JL projection at m=8 violates the
7// Johnson-Lindenstrauss lower bound by over 200× (needs m ≥ 554 for ε=0.5, n=100;
8// uses m=8). Empirically measures 1.4-6% NN preservation vs the documented
9// 90% target. Zero runtime consumers — BFCF uses region centroids, SenseModule
10// uses TernaryDir. The primitive is mathematically unsound as specified.
11// Option D (deprecate) chosen over Option B (PCA rescue) because PCA requires
12// a real-data intrinsic-rank measurement that cannot be done modellessly.
13
14/// Low-dimensional projection of NeuronShard style_weights for fast similarity search.
15/// Produced by Johnson-Lindenstrauss random orthogonal projection.
16/// 8 × f32 = 32 bytes — fits in cache line, suitable for SIMD cosine similarity.
17///
18/// Plan 230: Shard Embedding Projection — modelless linear weight-to-vector.
19///
20/// # Deprecated
21///
22/// Mathematically unsound at m=8 — violates the JL lower bound by over 200×.
23/// See Issue 139 / Plan 230 close-out note.
24#[deprecated(
25    note = "JL projection at m=8 is mathematically unsound (Issue 139). Zero runtime consumers. Use region centroids for shard similarity."
26)]
27#[derive(Clone, Copy, Debug, PartialEq)]
28pub struct ShardEmbedding(pub [f32; 8]);
29
30#[allow(deprecated)]
31impl ShardEmbedding {
32    pub const ZERO: Self = Self([0.0; 8]);
33    pub const DIM: usize = 8;
34
35    /// Cosine similarity between two embeddings.
36    /// Uses SIMD dot-product and SIMD sum-of-squares for the normalization.
37    #[inline]
38    pub fn cosine_similarity(&self, other: &Self) -> f32 {
39        let dot = crate::simd::simd_dot_f32(&self.0, &other.0, Self::DIM);
40        let sq_a = crate::simd::simd_sum_sq(&self.0, Self::DIM);
41        let sq_b = crate::simd::simd_sum_sq(&other.0, Self::DIM);
42        let denom = sq_a * sq_b;
43        if denom < 1e-16 {
44            return 0.0;
45        }
46        let inv_norm = 1.0 / denom.sqrt();
47        dot * inv_norm
48    }
49
50    /// Euclidean distance squared between two embeddings.
51    /// Uses SIMD-accelerated fused-subtract-accumulate.
52    #[inline]
53    pub fn dist_sq(&self, other: &Self) -> f32 {
54        crate::simd::simd_dist_sq(&self.0, &other.0, 8)
55    }
56}
57
58#[allow(deprecated)]
59impl Default for ShardEmbedding {
60    fn default() -> Self {
61        Self::ZERO
62    }
63}
64
65// Hash for use as HashMap key (bit-level, NOT semantic hash)
66#[allow(deprecated)]
67impl std::hash::Hash for ShardEmbedding {
68    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
69        // Single write of all 32 bytes — fewer virtual calls than four write_u64.
70        state.write(unsafe { std::slice::from_raw_parts(self.0.as_ptr() as *const u8, 32) });
71    }
72}
73
74#[allow(deprecated)]
75impl Eq for ShardEmbedding {}
76
77// ---------------------------------------------------------------------------
78// Sense Composition — KG Latent Octree (Plan 221)
79// ---------------------------------------------------------------------------
80
81/// Kind of sense module for NPC brain composition.
82#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
83#[repr(u8)]
84pub enum SenseKind {
85    CommonSense = 0,
86    FighterSense = 1,
87    GameTheorySense = 2,
88    #[default]
89    SpatialSense = 3,
90    SocialSense = 4,
91    SkillSense = 5,
92    /// LinOSS Modal Threat Prediction (Plan 241). Requires `spectral_threat` feature.
93    #[cfg(feature = "spectral_threat")]
94    SpectralThreat = 6,
95    Reserved = 7,
96}
97
98/// Ternary direction vector: +1/0/-1 encoded as two bitmasks + row scale.
99/// 20 bytes each (u64, u64, f32).
100/// `#[repr(C)]` required — embedded in `SenseModule` which hashes raw bytes.
101#[derive(Clone, Copy, Debug)]
102#[repr(C)]
103pub struct TernaryDir {
104    /// Bitmask for positive (+1) entries.
105    pub pos_bits: u64,
106    /// Bitmask for negative (-1) entries.
107    pub neg_bits: u64,
108    /// Scale factor for this direction row.
109    pub row_scale: f32,
110}
111
112impl TernaryDir {
113    pub const SIZE: usize = 20;
114
115    pub fn zero() -> Self {
116        Self {
117            pos_bits: 0,
118            neg_bits: 0,
119            row_scale: 0.0,
120        }
121    }
122
123    /// Zero padding bytes for deterministic hashing.
124    /// TernaryDir is 20 logical bytes but 24 with alignment padding.
125    /// This zeroes the 4 trailing padding bytes in one write.
126    #[inline(always)]
127    pub fn zero_padding(&mut self) {
128        // Write 4 zero bytes at once via u32 store instead of 4 individual byte writes
129        unsafe {
130            let ptr = self as *mut Self as *mut u8;
131            (ptr.add(20) as *mut u32).write(0);
132        }
133    }
134}
135
136/// Fixed-size sense module: KG latent octree + ternary direction vectors.
137/// ~232 bytes. BLAKE3 committed.
138///
139/// Field ordering: u64-aligned first, then f32, then u8 tail.
140/// `commitment` must remain LAST for the hash-until-end-of-struct pattern.
141#[derive(Clone, Debug)]
142#[repr(C)]
143pub struct SenseModule {
144    /// Octree occupancy bit-planes (up to depth 3 → 128 nodes in 2×u64).
145    pub octree_bits: [u64; 4],
146    /// Ternary direction vectors for projection.
147    pub directions: [TernaryDir; 8],
148    /// Module confidence [0, 1].
149    pub confidence: f32,
150    pub kind: SenseKind,
151    pub version: u8,
152    pub octree_depth: u8,
153    pub n_directions: u8,
154    pub _reserved: u8,
155    /// BLAKE3 commitment over all preceding fields.
156    pub commitment: [u8; 32],
157}
158
159impl SenseModule {
160    /// Project belief state onto this module's ternary directions → sigmoid scalar.
161    ///
162    /// KG weight bridge: output is scaled by module confidence so that
163    /// high-confidence KG triples produce stronger sense activations and
164    /// low-confidence triples are attenuated. Confidence 1.0 = unchanged.
165    ///
166    /// Optimized: branch-free bit extraction via shift+AND, flat loop
167    /// (LLVM auto-vectorizes better than chunked), bounded exp sigmoid.
168    #[inline(always)]
169    pub fn project(&self, belief_state: &[f32; 8]) -> f32 {
170        let n = self.n_directions as usize;
171        let mut dot = 0.0f32;
172
173        // Unrolled-friendly flat loop: extract ternary sign per-dim, FMA into dot.
174        // bool-as-u32 then cast to f32 is zero-extend (no int-to-float conversion).
175        // Zip iteration elides bounds checks on `self.directions[i]` (verified safe
176        // by `n_directions ≤ 8` but the runtime bound `n` defeats LLVM's elision).
177        for (i, (belief_val, dir)) in belief_state
178            .iter()
179            .zip(self.directions.iter())
180            .enumerate()
181            .take(n)
182        {
183            let pos = ((dir.pos_bits >> i) & 1) as u32 as f32;
184            let neg = ((dir.neg_bits >> i) & 1) as u32 as f32;
185            // sign ∈ {-1, 0, +1} — single FMA: dot += (sign * belief_val) * scale.
186            // sign * belief_val is computed first, then FMA-fused with scale + dot.
187            let sign = pos - neg;
188            dot = (sign * belief_val).mul_add(dir.row_scale, dot);
189        }
190
191        // Sigmoid * confidence — uses shared crate::simd::fast_sigmoid (bounded (0,1))
192        self.confidence * crate::simd::fast_sigmoid(dot)
193    }
194
195    /// Query octree occupancy at given level and index.
196    /// Returns None if indices out of bounds.
197    pub fn query_octree(&self, level: u8, index: u8) -> Option<bool> {
198        let nodes_at_level = 1usize << (level * 2); // quadtree-like
199        if index as usize >= nodes_at_level || level > self.octree_depth {
200            return None;
201        }
202        // flat_idx = 0 for all levels in this simplified indexing;
203        // the fold computes 0 * 4^level = 0, so flat_idx = index.
204        let flat_idx = index as usize;
205        let word = flat_idx / 64;
206        let bit = flat_idx % 64;
207        if word >= self.octree_bits.len() {
208            return None;
209        }
210        Some(self.octree_bits[word] & (1 << bit) != 0)
211    }
212
213    /// Compute and store BLAKE3 commitment.
214    /// Zeros TernaryDir padding bytes first for deterministic hashing.
215    pub fn commit(&mut self) {
216        // Zero commitment before hashing
217        self.commitment = [0u8; 32];
218        // Zero padding in direction vectors for deterministic hash
219        for dir in &mut self.directions {
220            dir.zero_padding();
221        }
222        let size_before_commit = std::mem::offset_of!(SenseModule, commitment);
223        let bytes: &[u8] = unsafe {
224            std::slice::from_raw_parts(self as *const Self as *const u8, size_before_commit)
225        };
226        self.commitment = *blake3::hash(bytes).as_bytes();
227    }
228
229    /// Verify BLAKE3 commitment.
230    /// Uses a stack buffer to avoid cloning the entire 232-byte struct.
231    /// Zeros TernaryDir padding bytes before comparing to match commit() behavior.
232    pub fn verify(&self) -> bool {
233        let size_before_commit = std::mem::offset_of!(SenseModule, commitment);
234        let mut buf = [0u8; std::mem::size_of::<SenseModule>()];
235        // Copy raw bytes to stack buffer
236        unsafe {
237            std::ptr::copy_nonoverlapping(
238                self as *const Self as *const u8,
239                buf.as_mut_ptr(),
240                size_before_commit,
241            );
242        }
243        // Zero commitment region and trailing padding in buffer
244        buf[size_before_commit..].fill(0);
245        // Zero TernaryDir padding in buffer — each TernaryDir is 24 bytes, padding at bytes 20..24
246        let dirs_offset = std::mem::offset_of!(SenseModule, directions);
247        let dir_size = std::mem::size_of::<TernaryDir>();
248        for i in 0..8 {
249            let dir_start = dirs_offset + i * dir_size;
250            // Zero 4 padding bytes at offset 20 within each TernaryDir
251            buf[dir_start + 20..dir_start + dir_size].fill(0);
252        }
253        let bytes = &buf[..size_before_commit];
254        let expected = blake3::hash(bytes);
255        self.commitment == *expected.as_bytes()
256    }
257}
258
259impl Default for SenseModule {
260    fn default() -> Self {
261        Self {
262            octree_bits: [0; 4],
263            directions: [TernaryDir::zero(); 8],
264            confidence: 0.0,
265            kind: SenseKind::default(),
266            version: 1,
267            octree_depth: 3,
268            n_directions: 0,
269            _reserved: 0,
270            commitment: [0u8; 32],
271        }
272    }
273}
274
275// ---------------------------------------------------------------------------
276// DilationConfig — RAT+ Recurrence Bridge sparse attention (Plan 225)
277// ---------------------------------------------------------------------------
278
279/// Dilation configuration for RAT+ bridge sparse attention.
280/// Controls stride D for KV cache access during decode.
281#[repr(u8)]
282#[derive(Clone, Copy, Debug, PartialEq, Eq)]
283pub enum DilationConfig {
284    D1 = 1, // Dense (no dilation)
285    D2 = 2,
286    D4 = 4,
287    D8 = 8,
288    D16 = 16,
289    D32 = 32,
290    D64 = 64,
291}
292
293impl DilationConfig {
294    /// Returns the dilation stride as usize.
295    #[inline]
296    pub fn stride(&self) -> usize {
297        *self as usize
298    }
299
300    /// Select dilation from queries-per-second heuristic.
301    /// Low QPS → dense, High QPS → aggressive dilation.
302    pub fn from_qps(qps: f32) -> Self {
303        match qps {
304            ..1.0 => Self::D1,
305            1.0..5.0 => Self::D4,
306            5.0..20.0 => Self::D16,
307            _ => Self::D64,
308        }
309    }
310}