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}