Skip to main content

qualia_core_db/inference/
compute_universe.rs

1//! Qualia-native **compute universe fabric** (Track B2).
2//!
3//! This is not a generic GPU scheduler. Universes U0/U1/U2 map to QualiaDB primitives:
4//!
5//! 1. **Graph–tensor duality** — `NQuin` graph and `Tensor10D` SOA share one resident substrate (U1 pin).
6//! 2. **Phase-8 bifurcation** — lock-free SPSC rings between U0 (LLM), Sentinel, and U1 (tensor).
7//! 3. **Sentinel governance** — `DenyRollback` on the control ring before the next token matmul.
8//! 4. **VramLedger pins** — Full / Eco / Reserve per universe; U0 KV protected in Reserve.
9//!
10//! One physical `wgpu::Device` (`gpu_context::shared_gpu`); logical parallelism via queues + rings.
11
12use core::cell::UnsafeCell;
13#[cfg(not(target_arch = "wasm32"))]
14use std::sync::atomic::AtomicBool;
15use std::sync::atomic::{AtomicU32, Ordering};
16
17#[cfg(not(target_arch = "wasm32"))]
18use std::thread;
19#[cfg(not(target_arch = "wasm32"))]
20use std::time::Duration;
21
22use crate::gpu_context::{
23    ComputeUniverse, OperationalMode, QueueLane, UniverseOrchestrator, VramLedger, VramLedgerSlot,
24};
25use crate::tensor::buffer_export::{TensorBufferHeader, TENSOR_STRIDE};
26use crate::tensor::resident_substrate::{global_resident_substrate, MAX_KNN_HITS};
27use crate::tensor::Tensor10D;
28
29/// Qualia engine primitive exploited by a compute universe (not portable DB semantics).
30#[repr(u8)]
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum QualiaPrimitive {
33    /// U0 autoregressive forward + KV (Phase-8 LLM thread).
34    Phase8LlmForward = 0,
35    /// U1/U2 shared SOA: `NQuin` ↔ `Tensor10D` zero-copy substrate.
36    GraphTensorSubstrate = 1,
37    /// Sentinel on logit stream; `DenyRollback` on control ring.
38    Phase8Sentinel = 2,
39    /// Pinned ledger slots per universe (Full / Eco / Reserve).
40    VramLedgerPin = 3,
41}
42
43impl ComputeUniverse {
44    /// Native primitives this universe must use (documentation + static dispatch hooks).
45    #[inline]
46    pub fn qualia_primitives(self) -> &'static [QualiaPrimitive] {
47        match self {
48            ComputeUniverse::LlmInference => &[
49                QualiaPrimitive::Phase8LlmForward,
50                QualiaPrimitive::Phase8Sentinel,
51                QualiaPrimitive::VramLedgerPin,
52            ],
53            ComputeUniverse::Tensor10D => &[
54                QualiaPrimitive::GraphTensorSubstrate,
55                QualiaPrimitive::VramLedgerPin,
56            ],
57            ComputeUniverse::Viewport => &[
58                QualiaPrimitive::GraphTensorSubstrate,
59                QualiaPrimitive::VramLedgerPin,
60            ],
61            ComputeUniverse::AcousticPlane => &[
62                QualiaPrimitive::GraphTensorSubstrate,
63                QualiaPrimitive::VramLedgerPin,
64            ],
65        }
66    }
67
68    #[inline]
69    pub fn phase8_channels(self) -> &'static [Phase8Channel] {
70        match self {
71            ComputeUniverse::LlmInference => &[
72                Phase8Channel::LogitUpstream,
73                Phase8Channel::ControlDownstream,
74                Phase8Channel::ContextInject,
75            ],
76            ComputeUniverse::Tensor10D => &[Phase8Channel::ContextInject],
77            ComputeUniverse::Viewport => &[],
78            ComputeUniverse::AcousticPlane => &[],
79        }
80    }
81}
82
83/// Phase-8 bifurcation ring (see `llm_agent.rs` LogitStream / ControlStream + U1 inject).
84#[repr(u8)]
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86pub enum Phase8Channel {
87    /// U0 → Sentinel: per-step logit summary (anomaly / governance).
88    LogitUpstream = 0,
89    /// Sentinel → U0: `DenyRollback` before next matmul.
90    ControlDownstream = 1,
91    /// U1 → U0: continuous context from `visit_tensor_search_into` (no stop-and-RAG).
92    ContextInject = 2,
93}
94
95pub const CONTEXT_INJECT_RING_CAP: usize = 64;
96pub const ATTENTION_MASK_WORDS: usize = 256;
97/// KV cache bitmask for `fused_attention.wgsl` (1024 context slots).
98pub const KV_ATTENTION_MASK_WORDS: usize = 32;
99pub const MAX_DRAFT_LEN: usize = 8;
100pub const TOPOLOGY_DRAFT_RING_CAP: usize = 4;
101
102/// Lightweight pointer pushed U1→U0 (48 B `NQuin` stays in SOA; ring carries index + hash).
103#[repr(C, align(8))]
104#[derive(Clone, Copy, Debug, PartialEq)]
105pub struct ContextInjectToken {
106    pub tensor_index: u32,
107    pub subject_hash: u64,
108    pub distance: f32,
109    pub manifold_w: f32,
110}
111
112impl ContextInjectToken {
113    pub const fn empty() -> Self {
114        Self {
115            tensor_index: 0,
116            subject_hash: 0,
117            distance: 0.0,
118            manifold_w: 0.0,
119        }
120    }
121}
122
123impl Default for ContextInjectToken {
124    fn default() -> Self {
125        Self::empty()
126    }
127}
128
129/// Fixed-capacity SPSC context ring (producer U1, consumer U0).
130pub struct ContextInjectRing {
131    slots: UnsafeCell<[ContextInjectToken; CONTEXT_INJECT_RING_CAP]>,
132    write_seq: AtomicU32,
133    read_seq: AtomicU32,
134}
135
136// SAFETY: SPSC — one producer (U1), one consumer (U0); slot published via write_seq Release.
137unsafe impl Sync for ContextInjectRing {}
138
139impl ContextInjectRing {
140    pub const fn new() -> Self {
141        Self {
142            slots: UnsafeCell::new([ContextInjectToken::empty(); CONTEXT_INJECT_RING_CAP]),
143            write_seq: AtomicU32::new(0),
144            read_seq: AtomicU32::new(0),
145        }
146    }
147
148    #[inline]
149    pub fn len(&self) -> usize {
150        let w = self.write_seq.load(Ordering::Acquire);
151        let r = self.read_seq.load(Ordering::Acquire);
152        (w.wrapping_sub(r)) as usize
153    }
154
155    #[inline]
156    pub fn is_empty(&self) -> bool {
157        self.len() == 0
158    }
159
160    /// Producer (U1 tensor search). Returns false when full.
161    pub fn try_push(&self, token: ContextInjectToken) -> bool {
162        let w = self.write_seq.load(Ordering::Relaxed);
163        let r = self.read_seq.load(Ordering::Acquire);
164        if w.wrapping_sub(r) >= CONTEXT_INJECT_RING_CAP as u32 {
165            return false;
166        }
167        let slot = (w % CONTEXT_INJECT_RING_CAP as u32) as usize;
168        unsafe {
169            (*self.slots.get())[slot] = token;
170        }
171        self.write_seq.store(w.wrapping_add(1), Ordering::Release);
172        true
173    }
174
175    /// Consumer (U0 decode loop). Returns None when empty.
176    pub fn try_pop(&self) -> Option<ContextInjectToken> {
177        let r = self.read_seq.load(Ordering::Relaxed);
178        let w = self.write_seq.load(Ordering::Acquire);
179        if r == w {
180            return None;
181        }
182        let slot = (r % CONTEXT_INJECT_RING_CAP as u32) as usize;
183        let token = unsafe { (*self.slots.get())[slot] };
184        self.read_seq.store(r.wrapping_add(1), Ordering::Release);
185        Some(token)
186    }
187}
188
189/// Resident graph↔tensor SOA visible to U1 (write/search), U0 (read/inject), U2 (render).
190#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
191pub struct GraphTensorSubstrate {
192    pub byte_len: u64,
193    pub node_count: u32,
194    pub stride_bytes: u32,
195    pub header_bytes: u32,
196}
197
198impl GraphTensorSubstrate {
199    #[inline]
200    pub fn from_tensor_bytes(resident_bytes: u64) -> Self {
201        let header_bytes = std::mem::size_of::<TensorBufferHeader>() as u32;
202        let stride = TENSOR_STRIDE as u32;
203        if resident_bytes <= header_bytes as u64 {
204            return Self {
205                byte_len: resident_bytes,
206                header_bytes,
207                stride_bytes: stride,
208                node_count: 0,
209            };
210        }
211        let payload = resident_bytes - header_bytes as u64;
212        let node_count = (payload / stride as u64).min(u32::MAX as u64) as u32;
213        Self {
214            byte_len: resident_bytes,
215            node_count,
216            stride_bytes: stride,
217            header_bytes,
218        }
219    }
220
221    #[inline]
222    pub fn is_resident(&self) -> bool {
223        self.node_count > 0
224    }
225}
226
227/// U1 output: sparse attention routing bitmask for U0 matmul (10D kNN filter).
228#[derive(Debug, Clone, Copy)]
229pub struct AttentionRouteMask {
230    pub words: [u64; ATTENTION_MASK_WORDS],
231    pub active_bits: u32,
232}
233
234impl Default for AttentionRouteMask {
235    fn default() -> Self {
236        Self {
237            words: [0u64; ATTENTION_MASK_WORDS],
238            active_bits: 0,
239        }
240    }
241}
242
243impl AttentionRouteMask {
244    #[inline]
245    pub fn set_index(&mut self, index: u32) {
246        let bit = index as usize;
247        let word = bit / 64;
248        let offset = bit % 64;
249        if word < ATTENTION_MASK_WORDS {
250            self.words[word] |= 1u64 << offset;
251            self.active_bits = self.active_bits.saturating_add(1);
252        }
253    }
254
255    #[inline]
256    pub fn is_set(&self, index: u32) -> bool {
257        let bit = index as usize;
258        let word = bit / 64;
259        let offset = bit % 64;
260        word < ATTENTION_MASK_WORDS && (self.words[word] & (1u64 << offset)) != 0
261    }
262}
263
264/// Qualia fabric: ledger orchestration + shared substrate + Phase-8 context ring.
265pub struct UniverseFabric {
266    pub orchestrator: UniverseOrchestrator,
267    pub substrate: GraphTensorSubstrate,
268}
269
270impl UniverseFabric {
271    #[inline]
272    pub fn current(ledger: &VramLedger) -> Self {
273        let tensor_bytes = ledger.used_in_slot(VramLedgerSlot::Tensor10D);
274        Self {
275            orchestrator: crate::gpu_context::universe_orchestrator(),
276            substrate: GraphTensorSubstrate::from_tensor_bytes(tensor_bytes),
277        }
278    }
279
280    /// Reserve mode: U2 capped, U0 KV stays Full (LLM wins scheduling).
281    #[inline]
282    pub fn reserve_protects_llm_kv(&self, global: OperationalMode) -> bool {
283        global == OperationalMode::Reserve
284            && self
285                .orchestrator
286                .effective_mode(ComputeUniverse::LlmInference, global)
287                == OperationalMode::Full
288            && self
289                .orchestrator
290                .effective_mode(ComputeUniverse::Viewport, global)
291                == OperationalMode::Reserve
292    }
293
294    #[inline]
295    pub fn queue_for(&self, universe: ComputeUniverse) -> QueueLane {
296        self.orchestrator.partition(universe).queue_lane
297    }
298
299    #[inline]
300    pub fn can_pin_tensor(&self, ledger: &VramLedger, extra_bytes: u64) -> bool {
301        ledger.can_allocate_in_universe(&self.orchestrator, ComputeUniverse::Tensor10D, extra_bytes)
302    }
303}
304
305static CONTEXT_INJECT_RING: ContextInjectRing = ContextInjectRing::new();
306
307#[inline]
308pub fn context_inject_ring() -> &'static ContextInjectRing {
309    &CONTEXT_INJECT_RING
310}
311
312/// Record a tensor kNN hit for async U0 consumption (continuous RAG path).
313#[inline]
314pub fn push_tensor_context(token: ContextInjectToken) -> bool {
315    let ok = context_inject_ring().try_push(token);
316    if ok {
317        crate::gpu_context::record_logic_flash();
318    }
319    ok
320}
321
322/// U0 decode loop pulls injected graph context (non-blocking).
323#[inline]
324pub fn pop_tensor_context() -> Option<ContextInjectToken> {
325    context_inject_ring().try_pop()
326}
327
328// ── U0 decode hints (atomics) ───────────────────────────────────────────────
329
330static DECODE_TOKEN_ID: AtomicU32 = AtomicU32::new(0);
331static DECODE_STEP: AtomicU32 = AtomicU32::new(0);
332static QUERY_SUBJECT_HASH: AtomicU32 = AtomicU32::new(0); // lower 32 bits; upper in QUERY_SUBJECT_HI
333static QUERY_SUBJECT_HI: AtomicU32 = AtomicU32::new(0);
334struct SyncUnsafeCell<T>(UnsafeCell<T>);
335unsafe impl<T> Sync for SyncUnsafeCell<T> {}
336
337const ZERO_TENSOR10D: Tensor10D = Tensor10D {
338    q: 0.0,
339    v: 0.0,
340    w: 0.0,
341    x: 0.0,
342    y: 0.0,
343    z: 0.0,
344    t: 0.0,
345    alpha: 0.0,
346    mu: 0.0,
347    sigma: 0.0,
348};
349
350static QUERY_TENSOR_CELL: SyncUnsafeCell<Tensor10D> =
351    SyncUnsafeCell(UnsafeCell::new(ZERO_TENSOR10D));
352static QUERY_SEQ: AtomicU32 = AtomicU32::new(0);
353
354static ATTENTION_MASK_CELL: SyncUnsafeCell<AttentionRouteMask> =
355    SyncUnsafeCell(UnsafeCell::new(AttentionRouteMask {
356        words: [0u64; ATTENTION_MASK_WORDS],
357        active_bits: 0,
358    }));
359static ATTENTION_MASK_SEQ: AtomicU32 = AtomicU32::new(0);
360
361#[cfg(not(target_arch = "wasm32"))]
362static PRODUCER_STARTED: AtomicBool = AtomicBool::new(false);
363#[cfg(not(target_arch = "wasm32"))]
364static PRODUCER_STOP: AtomicBool = AtomicBool::new(false);
365
366/// U0 publishes the active decode position for the U1 producer (non-blocking).
367#[inline]
368pub fn publish_decode_hint(token_id: u32, step: u32) {
369    DECODE_TOKEN_ID.store(token_id, Ordering::Relaxed);
370    DECODE_STEP.store(step, Ordering::Release);
371}
372
373/// U0 publishes the 10D query anchor U1 should search around.
374#[inline]
375pub fn publish_query_tensor(tensor: Tensor10D, subject_hash: u64) {
376    unsafe {
377        *QUERY_TENSOR_CELL.0.get() = tensor;
378    }
379    QUERY_SUBJECT_HASH.store(subject_hash as u32, Ordering::Relaxed);
380    QUERY_SUBJECT_HI.store((subject_hash >> 32) as u32, Ordering::Relaxed);
381    QUERY_SEQ.fetch_add(1, Ordering::Release);
382}
383
384#[inline]
385pub fn query_subject_hash() -> u64 {
386    let lo = QUERY_SUBJECT_HASH.load(Ordering::Relaxed) as u64;
387    let hi = QUERY_SUBJECT_HI.load(Ordering::Relaxed) as u64;
388    (hi << 32) | lo
389}
390
391#[inline]
392pub fn load_query_tensor() -> Tensor10D {
393    unsafe { *QUERY_TENSOR_CELL.0.get() }
394}
395
396#[inline]
397pub fn attention_route_mask() -> AttentionRouteMask {
398    unsafe { *ATTENTION_MASK_CELL.0.get() }
399}
400
401#[inline]
402pub fn attention_mask_seq() -> u32 {
403    ATTENTION_MASK_SEQ.load(Ordering::Acquire)
404}
405
406/// Publish U1 route mask snapshot (producer / tests).
407#[inline]
408pub fn publish_attention_route_mask(mask: AttentionRouteMask) {
409    unsafe {
410        *ATTENTION_MASK_CELL.0.get() = mask;
411    }
412    ATTENTION_MASK_SEQ.fetch_add(1, Ordering::Release);
413}
414
415#[inline]
416fn set_kv_mask_bit(words: &mut [u32; KV_ATTENTION_MASK_WORDS], slot: u32) {
417    let bit = slot as usize;
418    let word = bit / 32;
419    let offset = bit % 32;
420    if word < KV_ATTENTION_MASK_WORDS {
421        words[word] |= 1u32 << offset;
422    }
423}
424
425/// Map U1 route mask (tensor indices) → KV slot mask for attention (B3.2b light).
426#[inline]
427pub fn attention_kv_mask_u32(
428    token_idx: u32,
429    max_context: u32,
430) -> ([u32; KV_ATTENTION_MASK_WORDS], u32) {
431    let route = attention_route_mask();
432    let mut words = [0u32; KV_ATTENTION_MASK_WORDS];
433    let cap = max_context.min((KV_ATTENTION_MASK_WORDS as u32) * 32);
434    set_kv_mask_bit(&mut words, token_idx.min(cap.saturating_sub(1)));
435
436    let provenance = crate::tensor::kv_provenance::global_kv_provenance();
437    let mut mapped = 1u32;
438    for (word_idx, w) in route.words.iter().enumerate() {
439        for bit in 0..64 {
440            if (*w & (1u64 << bit)) == 0 {
441                continue;
442            }
443            let tensor_idx = (word_idx * 64 + bit) as u32;
444            let kv_slot = provenance
445                .kv_slot_for_tensor(tensor_idx)
446                .unwrap_or(tensor_idx);
447            if kv_slot < cap {
448                set_kv_mask_bit(&mut words, kv_slot);
449                mapped += 1;
450            }
451        }
452    }
453
454    let mask_active = if mapped > 1 && route.active_bits > 0 {
455        1u32
456    } else {
457        0u32
458    };
459    (words, mask_active)
460}
461
462/// B3.2a — kNN hits → sparse attention bitmask (zero-heap).
463#[inline]
464pub fn build_attention_route_mask(
465    query: &Tensor10D,
466    max_distance: f32,
467    hits: &[usize],
468    hit_count: usize,
469) -> AttentionRouteMask {
470    let mut mask = AttentionRouteMask::default();
471    for i in 0..hit_count.min(hits.len()) {
472        mask.set_index(hits[i] as u32);
473    }
474    let _ = (query, max_distance);
475    mask
476}
477
478/// One U1 producer cycle: search resident SOA, push ring, refresh mask, draft topology.
479pub fn run_tensor_search_producer_cycle(max_distance: f32, vocab_len: u32) -> usize {
480    let substrate = global_resident_substrate();
481    if substrate.node_count() == 0 {
482        return 0;
483    }
484
485    let query = load_query_tensor();
486    let subject_hash = query_subject_hash();
487    let mut hits = [0usize; MAX_KNN_HITS];
488    // METRIC PARITY (ALGEBRA_MANIFOLD_PLAN.md Phase 4.1, now unified): the GPU shader
489    // (shaders/tensor_volume.wgsl) ports `Tensor10D::full_distance` exactly — the metric is
490    // chosen by the QUERY's `v` topology class (euclidean / cyclic / hyperbolic / boundary).
491    // So this GPU path and the CPU fallback below (which also uses `full_distance`) agree
492    // for ALL `v`, not only `v == 0`. `volume_gpu::cpu_tensor_search_into` is the shared,
493    // GPU-independent reference for the same metric.
494    let hit_count =
495        crate::tensor::volume_gpu::try_gpu_tensor_search_into(&query, max_distance, &mut hits)
496            .unwrap_or_else(|| {
497                substrate
498                    .tensor_search_into(&query, max_distance, &mut hits)
499                    .unwrap_or(0)
500            });
501
502    let mask = build_attention_route_mask(&query, max_distance, &hits, hit_count);
503    publish_attention_route_mask(mask);
504
505    let pushed = push_tensor_search_hits(&hits[..hit_count], subject_hash, query.w, max_distance);
506    let _ = extrapolate_topology_draft(&query, subject_hash, 4, vocab_len);
507    pushed
508}
509
510#[cfg(not(target_arch = "wasm32"))]
511fn tensor_search_producer_loop() {
512    crate::platform_scheduler::bind_background_thread();
513    let mut idle_spins = 0u32;
514    while !PRODUCER_STOP.load(Ordering::Relaxed) {
515        let pushed = run_tensor_search_producer_cycle(3.0, 32_000);
516        if pushed > 0 {
517            crate::gpu_context::record_producer_cycle(pushed as u32);
518            idle_spins = 0;
519        } else if global_resident_substrate().node_count() == 0 {
520            thread::sleep(Duration::from_millis(4));
521        } else {
522            idle_spins = idle_spins.saturating_add(1);
523            if idle_spins > 32 {
524                thread::sleep(Duration::from_millis(1));
525                idle_spins = 0;
526            } else {
527                thread::yield_now();
528            }
529        }
530    }
531}
532
533/// B3.3a — spawn background U1 tensor search producer (idempotent).
534#[cfg(not(target_arch = "wasm32"))]
535pub fn start_tensor_search_producer() -> bool {
536    if PRODUCER_STARTED.swap(true, Ordering::AcqRel) {
537        return false;
538    }
539    PRODUCER_STOP.store(false, Ordering::Release);
540    thread::Builder::new()
541        .name("qualia-u1-tensor-producer".into())
542        .spawn(tensor_search_producer_loop)
543        .expect("U1 tensor search producer thread");
544    true
545}
546
547#[cfg(not(target_arch = "wasm32"))]
548pub fn stop_tensor_search_producer() {
549    PRODUCER_STOP.store(true, Ordering::Release);
550}
551
552#[cfg(target_arch = "wasm32")]
553pub fn start_tensor_search_producer() -> bool {
554    false
555}
556
557#[cfg(target_arch = "wasm32")]
558pub fn stop_tensor_search_producer() {}
559
560/// B2.4 producer hook: push kNN indices from `visit_tensor_search` / `tensor_search_into`.
561#[inline]
562pub fn push_tensor_search_hits(
563    indices: &[usize],
564    subject_hash: u64,
565    manifold_w: f32,
566    max_distance: f32,
567) -> usize {
568    let mut pushed = 0usize;
569    for (rank, &index) in indices.iter().enumerate() {
570        if !push_tensor_context(ContextInjectToken {
571            tensor_index: index as u32,
572            subject_hash,
573            distance: max_distance * (1.0 + rank as f32 * 0.01),
574            manifold_w,
575        }) {
576            crate::gpu_context::record_context_ring_drop();
577            break;
578        }
579        pushed += 1;
580    }
581    pushed
582}
583
584// ── B3.1 topological speculative decoding (draft ring scaffold) ───────────────
585
586/// Draft batch pushed U1→U0 for parallel verify (no draft-model weights).
587#[repr(C, align(8))]
588#[derive(Clone, Copy, Debug, PartialEq)]
589pub struct TopologyDraftBatch {
590    pub draft_len: u8,
591    pub _pad: [u8; 7],
592    pub draft_ids: [u32; MAX_DRAFT_LEN],
593    pub concept_hashes: [u64; MAX_DRAFT_LEN],
594}
595
596impl TopologyDraftBatch {
597    pub const fn empty() -> Self {
598        Self {
599            draft_len: 0,
600            _pad: [0; 7],
601            draft_ids: [0; MAX_DRAFT_LEN],
602            concept_hashes: [0; MAX_DRAFT_LEN],
603        }
604    }
605}
606
607pub struct TopologyDraftRing {
608    slots: UnsafeCell<[TopologyDraftBatch; TOPOLOGY_DRAFT_RING_CAP]>,
609    write_seq: AtomicU32,
610    read_seq: AtomicU32,
611}
612
613unsafe impl Sync for TopologyDraftRing {}
614
615impl TopologyDraftRing {
616    pub const fn new() -> Self {
617        Self {
618            slots: UnsafeCell::new([TopologyDraftBatch::empty(); TOPOLOGY_DRAFT_RING_CAP]),
619            write_seq: AtomicU32::new(0),
620            read_seq: AtomicU32::new(0),
621        }
622    }
623
624    pub fn try_push(&self, batch: TopologyDraftBatch) -> bool {
625        if batch.draft_len == 0 {
626            return false;
627        }
628        let w = self.write_seq.load(Ordering::Relaxed);
629        let r = self.read_seq.load(Ordering::Acquire);
630        if w.wrapping_sub(r) >= TOPOLOGY_DRAFT_RING_CAP as u32 {
631            return false;
632        }
633        let slot = (w % TOPOLOGY_DRAFT_RING_CAP as u32) as usize;
634        unsafe {
635            (*self.slots.get())[slot] = batch;
636        }
637        self.write_seq.store(w.wrapping_add(1), Ordering::Release);
638        true
639    }
640
641    pub fn try_pop(&self) -> Option<TopologyDraftBatch> {
642        let r = self.read_seq.load(Ordering::Relaxed);
643        let w = self.write_seq.load(Ordering::Acquire);
644        if r == w {
645            return None;
646        }
647        let slot = (r % TOPOLOGY_DRAFT_RING_CAP as u32) as usize;
648        let batch = unsafe { (*self.slots.get())[slot] };
649        self.read_seq.store(r.wrapping_add(1), Ordering::Release);
650        Some(batch)
651    }
652}
653
654static TOPOLOGY_DRAFT_RING: TopologyDraftRing = TopologyDraftRing::new();
655
656#[inline]
657pub fn topology_draft_ring() -> &'static TopologyDraftRing {
658    &TOPOLOGY_DRAFT_RING
659}
660
661#[inline]
662pub fn pop_topology_draft() -> Option<TopologyDraftBatch> {
663    topology_draft_ring().try_pop()
664}
665
666/// Phase-8 Sentinel gate for U1→U0 topology drafts (B3.1d polish).
667///
668/// Empty drafts are rejected. The previous `0x99` first-byte filter on token ids /
669/// concept hashes was a false positive (~1/256 legitimate tokens) and is removed.
670/// Real governance signals must use an explicit flag / quin rule, not a random byte.
671#[inline]
672pub fn sentinel_allows_topology_draft(batch: &TopologyDraftBatch) -> bool {
673    batch.draft_len > 0
674}
675
676/// Extrapolate γ concept hashes from kNN trajectory (B3.1b); optional `TopologyDraftMapper` (B3.1c).
677pub fn extrapolate_topology_draft(
678    query: &Tensor10D,
679    subject_hash: u64,
680    gamma: usize,
681    vocab_len: u32,
682) -> Option<TopologyDraftBatch> {
683    #[cfg(any(not(target_arch = "wasm32"), feature = "wasm-llm"))]
684    {
685        extrapolate_topology_draft_mapped(query, subject_hash, gamma, vocab_len, None)
686    }
687    #[cfg(all(target_arch = "wasm32", not(feature = "wasm-llm")))]
688    {
689        extrapolate_topology_draft_mapped(query, subject_hash, gamma, vocab_len)
690    }
691}
692
693pub fn extrapolate_topology_draft_mapped(
694    query: &Tensor10D,
695    subject_hash: u64,
696    gamma: usize,
697    vocab_len: u32,
698    #[cfg(any(not(target_arch = "wasm32"), feature = "wasm-llm"))] mapper: Option<
699        &crate::topology_draft::TopologyDraftMapper<'_>,
700    >,
701) -> Option<TopologyDraftBatch> {
702    let gamma = gamma.clamp(1, MAX_DRAFT_LEN);
703    let substrate = global_resident_substrate();
704    if substrate.node_count() == 0 || vocab_len == 0 {
705        return None;
706    }
707    let mut hits = [0usize; MAX_KNN_HITS];
708    let hit_count = {
709        #[cfg(any(not(target_arch = "wasm32"), feature = "wasm-llm"))]
710        {
711            crate::tensor::volume_gpu::try_gpu_tensor_search_into(query, 4.0, &mut hits)
712                .unwrap_or_else(|| {
713                    substrate
714                        .tensor_search_into(query, 4.0, &mut hits)
715                        .unwrap_or(0)
716                })
717        }
718        #[cfg(all(target_arch = "wasm32", not(feature = "wasm-llm")))]
719        {
720            substrate
721                .tensor_search_into(query, 4.0, &mut hits)
722                .unwrap_or(0)
723        }
724    };
725    if hit_count == 0 {
726        return None;
727    }
728
729    let mut concepts = [0u64; MAX_DRAFT_LEN];
730    for i in 0..gamma.min(hit_count) {
731        concepts[i] = substrate.subject_hash_at(hits[i] as u32) ^ subject_hash;
732    }
733    let batch = {
734        #[cfg(any(not(target_arch = "wasm32"), feature = "wasm-llm"))]
735        if let Some(m) = mapper {
736            m.fill_draft_batch(&concepts[..gamma.min(hit_count)], gamma.min(hit_count))
737        } else {
738            let mut batch = TopologyDraftBatch::empty();
739            for i in 0..gamma.min(hit_count) {
740                batch.concept_hashes[i] = concepts[i];
741                batch.draft_ids[i] = (concepts[i] as u32) % vocab_len.max(1);
742            }
743            batch.draft_len = gamma.min(hit_count) as u8;
744            batch
745        }
746        #[cfg(all(target_arch = "wasm32", not(feature = "wasm-llm")))]
747        {
748            let mut batch = TopologyDraftBatch::empty();
749            for i in 0..gamma.min(hit_count) {
750                batch.concept_hashes[i] = concepts[i];
751                batch.draft_ids[i] = (concepts[i] as u32) % vocab_len.max(1);
752            }
753            batch.draft_len = gamma.min(hit_count) as u8;
754            batch
755        }
756    };
757    let _ = topology_draft_ring().try_push(batch);
758    Some(batch)
759}
760
761#[cfg(test)]
762mod tests {
763    use super::*;
764
765    #[test]
766    fn context_ring_push_pop() {
767        let ring = ContextInjectRing::new();
768        assert!(ring.try_push(ContextInjectToken {
769            tensor_index: 1,
770            subject_hash: 42,
771            distance: 0.5,
772            manifold_w: 0.0,
773        }));
774        let t = ring.try_pop().expect("token");
775        assert_eq!(t.tensor_index, 1);
776        assert!(ring.is_empty());
777    }
778
779    #[test]
780    fn substrate_node_count_from_bytes() {
781        let header = std::mem::size_of::<TensorBufferHeader>() as u64;
782        let sub = GraphTensorSubstrate::from_tensor_bytes(header + 40 * 10);
783        assert_eq!(sub.node_count, 10);
784    }
785
786    #[test]
787    fn u0_has_sentinel_primitive() {
788        let prims = ComputeUniverse::LlmInference.qualia_primitives();
789        assert!(prims.contains(&QualiaPrimitive::Phase8Sentinel));
790    }
791
792    #[test]
793    fn attention_mask_sets_bits() {
794        let mut mask = AttentionRouteMask::default();
795        mask.set_index(100);
796        assert!(mask.is_set(100));
797        assert!(!mask.is_set(101));
798    }
799
800    #[test]
801    fn push_tensor_search_hits_drains_to_ring() {
802        let pushed = push_tensor_search_hits(&[3, 7], 99, 0.5, 1.0);
803        assert_eq!(pushed, 2);
804        assert_eq!(pop_tensor_context().unwrap().tensor_index, 3);
805        assert_eq!(pop_tensor_context().unwrap().tensor_index, 7);
806    }
807
808    #[test]
809    fn build_attention_mask_from_hits() {
810        let query = Tensor10D::default();
811        let mask = build_attention_route_mask(&query, 1.0, &[2, 5, 9], 3);
812        assert!(mask.is_set(2));
813        assert!(mask.is_set(5));
814        assert_eq!(mask.active_bits, 3);
815    }
816
817    #[test]
818    fn attention_kv_mask_maps_tensor_bits() {
819        let mut route = AttentionRouteMask::default();
820        route.set_index(5);
821        route.set_index(12);
822        publish_attention_route_mask(route);
823        let (words, active) = attention_kv_mask_u32(20, 1024);
824        assert_eq!(active, 1);
825        assert_ne!(words[0] & (1u32 << 5), 0);
826        assert_ne!(words[0] & (1u32 << 12), 0);
827        assert_ne!(words[0] & (1u32 << 20), 0);
828    }
829
830    #[test]
831    fn topology_draft_ring_roundtrip() {
832        let ring = TopologyDraftRing::new();
833        let batch = TopologyDraftBatch {
834            draft_len: 2,
835            _pad: [0; 7],
836            draft_ids: [10, 20, 0, 0, 0, 0, 0, 0],
837            concept_hashes: [1, 2, 0, 0, 0, 0, 0, 0],
838        };
839        assert!(ring.try_push(batch));
840        let popped = ring.try_pop().unwrap();
841        assert_eq!(popped.draft_len, 2);
842        assert_eq!(popped.draft_ids[0], 10);
843    }
844
845    #[test]
846    fn sentinel_allows_nonempty_drafts_including_former_false_positives() {
847        let clean = TopologyDraftBatch {
848            draft_len: 1,
849            _pad: [0; 7],
850            draft_ids: [42, 0, 0, 0, 0, 0, 0, 0],
851            concept_hashes: [7, 0, 0, 0, 0, 0, 0, 0],
852        };
853        assert!(sentinel_allows_topology_draft(&clean));
854
855        // Token id / hash low byte 0x99 is legitimate (~1/256) — no longer rejected.
856        let former_false_positive = TopologyDraftBatch {
857            draft_len: 1,
858            _pad: [0; 7],
859            draft_ids: [0x99, 0, 0, 0, 0, 0, 0, 0],
860            concept_hashes: [0x99, 0, 0, 0, 0, 0, 0, 0],
861        };
862        assert!(sentinel_allows_topology_draft(&former_false_positive));
863
864        let empty = TopologyDraftBatch {
865            draft_len: 0,
866            _pad: [0; 7],
867            draft_ids: [0; 8],
868            concept_hashes: [0; 8],
869        };
870        assert!(!sentinel_allows_topology_draft(&empty));
871    }
872
873    #[test]
874    fn producer_cycle_with_global_substrate() {
875        let tensors = [
876            Tensor10D::new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
877            Tensor10D::new(0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
878        ];
879        global_resident_substrate()
880            .load_from_tensors(&tensors, 77)
881            .unwrap();
882        publish_query_tensor(tensors[0], 77);
883        let pushed = run_tensor_search_producer_cycle(2.0, 32_000);
884        assert!(pushed >= 1);
885        assert!(attention_route_mask().active_bits >= 1);
886        while pop_tensor_context().is_some() {}
887    }
888}