Skip to main content

qualia_core_db/tensor/
buffer_export.rs

1//! Binary tensor buffer export for Qualia portal GPU upload.
2//!
3//! Layout: header (32 B) + N × Tensor10D (40 B each), little-endian f32.
4
5use bytemuck::{bytes_of, Pod, Zeroable};
6
7use super::Tensor10D;
8
9pub const TENSOR_BUFFER_MAGIC: u32 = 0x5134_322A; // "Q42*"
10pub const TENSOR_BUFFER_VERSION: u16 = 1;
11pub const TENSOR_STRIDE: usize = 40;
12/// Byte offset to the first `Tensor10D` record in a baked buffer blob.
13pub const TENSOR_HEADER_BYTES: usize = std::mem::size_of::<TensorBufferHeader>();
14
15#[repr(C, align(4))]
16#[derive(Clone, Copy, Debug, Default, Pod, Zeroable)]
17pub struct TensorBufferHeader {
18    pub magic: u32,
19    pub version: u16,
20    pub _pad0: u16,
21    pub node_count: u32,
22    pub stride: u32,
23    pub _reserved: [u32; 4],
24}
25
26impl TensorBufferHeader {
27    pub fn new(node_count: u32) -> Self {
28        Self {
29            magic: TENSOR_BUFFER_MAGIC,
30            version: TENSOR_BUFFER_VERSION,
31            node_count,
32            stride: TENSOR_STRIDE as u32,
33            ..Default::default()
34        }
35    }
36
37    pub fn total_bytes(node_count: usize) -> usize {
38        std::mem::size_of::<TensorBufferHeader>() + node_count * TENSOR_STRIDE
39    }
40}
41
42/// Parse header from a tensor buffer blob. Returns (header, header_byte_len).
43#[inline]
44pub fn parse_header(bytes: &[u8]) -> Result<(TensorBufferHeader, usize), &'static str> {
45    let header_len = std::mem::size_of::<TensorBufferHeader>();
46    if bytes.len() < header_len {
47        return Err("buffer too small for header");
48    }
49    let header: TensorBufferHeader = bytemuck::pod_read_unaligned(&bytes[..header_len]);
50    if header.magic != TENSOR_BUFFER_MAGIC {
51        return Err("invalid tensor buffer magic");
52    }
53    if header.stride as usize != TENSOR_STRIDE {
54        return Err("unsupported tensor stride");
55    }
56    Ok((header, header_len))
57}
58
59/// Node count encoded in a tensor buffer (zero-alloc).
60#[inline]
61pub fn tensor_node_count(bytes: &[u8]) -> Result<usize, &'static str> {
62    let (header, _) = parse_header(bytes)?;
63    Ok(header.node_count as usize)
64}
65
66/// Write epistemic `q` for one node (wavefunction collapse). Returns previous q.
67#[inline]
68pub fn write_tensor_q_at(bytes: &mut [u8], index: usize, q: f32) -> Result<f32, &'static str> {
69    let (header, header_len) = parse_header(bytes)?;
70    let count = header.node_count as usize;
71    if index >= count {
72        return Err("tensor index out of range");
73    }
74    let offset = header_len + index * TENSOR_STRIDE;
75    let end = offset + 4;
76    if bytes.len() < end {
77        return Err("buffer truncated");
78    }
79    let prev = f32::from_le_bytes(bytes[offset..offset + 4].try_into().unwrap());
80    bytes[offset..offset + 4].copy_from_slice(&q.to_le_bytes());
81    Ok(prev)
82}
83
84/// Read one `Tensor10D` by index from a buffer (zero-alloc).
85#[inline]
86pub fn read_tensor_at(bytes: &[u8], index: usize) -> Result<Tensor10D, &'static str> {
87    let (header, header_len) = parse_header(bytes)?;
88    let count = header.node_count as usize;
89    if index >= count {
90        return Err("tensor index out of range");
91    }
92    let offset = header_len + index * TENSOR_STRIDE;
93    let end = offset + TENSOR_STRIDE;
94    if bytes.len() < end {
95        return Err("buffer truncated");
96    }
97    Ok(bytemuck::pod_read_unaligned(&bytes[offset..end]))
98}
99
100/// Pack σ / α / q into linear RGBA for viewport shaders (cold path).
101#[inline]
102pub fn tensor_render_color(t: &Tensor10D) -> [f32; 4] {
103    let rgb = crate::render::spectral::sigma_to_linear_rgb(t.sigma);
104    let alpha = (0.35 + t.alpha * 0.55).clamp(0.15, 1.0);
105    [rgb[0], rgb[1], rgb[2], alpha]
106}
107
108/// Write header + tensor slice into caller buffer. Returns bytes written.
109pub fn write_tensor_buffer(tensors: &[Tensor10D], out: &mut [u8]) -> Result<usize, &'static str> {
110    let need = TensorBufferHeader::total_bytes(tensors.len());
111    if out.len() < need {
112        return Err("output buffer too small");
113    }
114    let header = TensorBufferHeader::new(tensors.len() as u32);
115    let header_bytes = bytes_of(&header);
116    out[..header_bytes.len()].copy_from_slice(header_bytes);
117    let mut offset = header_bytes.len();
118    for tensor in tensors {
119        let tb = bytes_of(tensor);
120        out[offset..offset + tb.len()].copy_from_slice(tb);
121        offset += tb.len();
122    }
123    Ok(offset)
124}
125
126/// Export a bounded slice from the resident U1 substrate into a tensor buffer blob.
127pub fn write_tensor_slice_from_resident(
128    substrate: &crate::tensor::resident_substrate::ResidentTensorSubstrate,
129    max_nodes: usize,
130    out: &mut [u8],
131) -> Result<usize, &'static str> {
132    let count = substrate.node_count() as usize;
133    if count == 0 {
134        return Err("no resident tensors");
135    }
136    let export_n = count.min(max_nodes);
137    let need = TensorBufferHeader::total_bytes(export_n);
138    if out.len() < need {
139        return Err("output buffer too small");
140    }
141    let mut offset = std::mem::size_of::<TensorBufferHeader>();
142    let header = TensorBufferHeader::new(export_n as u32);
143    out[..offset].copy_from_slice(bytes_of(&header));
144    for i in 0..export_n {
145        let tensor = substrate
146            .tensor_at(i as u32)
147            .ok_or("resident tensor missing")?;
148        let tb = bytes_of(&tensor);
149        out[offset..offset + tb.len()].copy_from_slice(tb);
150        offset += tb.len();
151    }
152    Ok(offset)
153}
154
155#[cfg(test)]
156mod tests {
157    use super::*;
158
159    #[test]
160    fn header_size_stable() {
161        assert_eq!(std::mem::size_of::<TensorBufferHeader>(), 32);
162        assert_eq!(std::mem::size_of::<Tensor10D>(), 40);
163    }
164
165    #[test]
166    fn write_tensor_slice_from_resident_round_trip() {
167        use crate::tensor::resident_substrate::ResidentTensorSubstrate;
168        let sub = ResidentTensorSubstrate::new();
169        let tensors = [
170            Tensor10D::ground_truth(0.0, 0.0, 0.1, 0.2, 0.3, 0.0, 1.0, 0.0, 0.5),
171            Tensor10D::new(0.5, 0.0, 0.0, 0.4, 0.5, 0.6, 0.0, 1.0, 0.0, 0.75),
172        ];
173        let need = TensorBufferHeader::total_bytes(2);
174        let mut buf = vec![0u8; need];
175        write_tensor_buffer(&tensors, &mut buf).unwrap();
176        sub.load_from_tensor_buffer(&buf, 0).unwrap();
177
178        let mut slice = vec![0u8; need];
179        let n = write_tensor_slice_from_resident(&sub, 2, &mut slice).unwrap();
180        assert_eq!(n, need);
181        assert_eq!(tensor_node_count(&slice).unwrap(), 2);
182        let t1 = read_tensor_at(&slice, 1).unwrap();
183        assert!((t1.q - 0.5).abs() < 1e-5);
184    }
185
186    #[test]
187    fn write_tensor_q_at_collapses_epistemic_field() {
188        let tensors = [Tensor10D::ground_truth(
189            0.0, 0.0, 0.1, 0.2, 0.3, 0.0, 1.0, 0.0, 0.5,
190        )];
191        let need = TensorBufferHeader::total_bytes(tensors.len());
192        let mut buf = vec![0u8; need];
193        write_tensor_buffer(&tensors, &mut buf).unwrap();
194        let prev = write_tensor_q_at(&mut buf, 0, 0.0).unwrap();
195        assert!((prev - 0.0).abs() < 1e-6);
196        let t = read_tensor_at(&buf, 0).unwrap();
197        assert!(t.q.abs() < 1e-6);
198    }
199
200    #[test]
201    fn read_tensor_at_round_trip() {
202        let tensors = [
203            Tensor10D::ground_truth(0.0, 0.0, 0.1, 0.2, 0.3, 0.0, 1.0, 0.0, 0.5),
204            Tensor10D::ground_truth(0.0, 0.0, 0.4, 0.5, 0.6, 0.0, 1.0, 0.0, 0.75),
205        ];
206        let need = TensorBufferHeader::total_bytes(tensors.len());
207        let mut buf = vec![0u8; need];
208        write_tensor_buffer(&tensors, &mut buf).unwrap();
209        assert_eq!(tensor_node_count(&buf).unwrap(), 2);
210        let t1 = read_tensor_at(&buf, 1).unwrap();
211        assert!((t1.x - 0.4).abs() < 1e-5);
212        assert!((t1.sigma - 0.75).abs() < 1e-5);
213    }
214
215    #[test]
216    fn round_trip_write() {
217        let tensors = [
218            Tensor10D::ground_truth(0.0, 0.0, 0.1, 0.2, 0.3, 0.0, 1.0, 0.0, 0.5),
219            Tensor10D::ground_truth(0.0, 0.0, 0.4, 0.5, 0.6, 0.0, 1.0, 0.0, 0.5),
220        ];
221        let need = TensorBufferHeader::total_bytes(tensors.len());
222        let mut buf = vec![0u8; need];
223        let n = write_tensor_buffer(&tensors, &mut buf).unwrap();
224        assert_eq!(n, need);
225        let magic = u32::from_le_bytes(buf[0..4].try_into().unwrap());
226        assert_eq!(magic, TENSOR_BUFFER_MAGIC);
227    }
228}