1use bytemuck::{bytes_of, Pod, Zeroable};
6
7use super::Tensor10D;
8
9pub const TENSOR_BUFFER_MAGIC: u32 = 0x5134_322A; pub const TENSOR_BUFFER_VERSION: u16 = 1;
11pub const TENSOR_STRIDE: usize = 40;
12pub 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#[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#[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#[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#[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#[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
108pub 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
126pub 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}