1use crate::vision_10d_rights::{evaluate_vision_10d_barrier, Vision10dAccess, Vision10dBarrier};
8use qualia_core_db::container_10d::{
9 header::Container10dHeader, integrity::verify_whole_file_crc32c,
10 node_section::parse_node_header, parse_section_table, section::SectionType,
11};
12use qualia_core_db::render::acoustic::sigma_to_center_frequency_hz;
13use qualia_core_db::render::assets::Mesh;
14use qualia_core_db::render::compile_10d::{compiled_digest, decode_10d_mesh, decode_10d_nodes};
15use qualia_core_db::render::spectral::sigma_to_display_rgb;
16use qualia_core_db::tensor::Tensor10D;
17use serde::Serialize;
18use std::path::Path;
19
20#[derive(Debug, Clone, Copy, Serialize)]
22pub struct VisionNodePaint {
23 pub x: f32,
24 pub y: f32,
25 pub z: f32,
26 pub t: f32,
27 pub sigma: f32,
28 pub rgb: [u8; 3],
29 pub frequency_hz: f32,
30 pub ground_truth: bool,
32}
33
34#[derive(Debug, Clone, Serialize)]
36pub struct Vision10dLoaded {
37 pub path: Option<String>,
38 pub size_bytes: u64,
39 pub compiled_digest_hex: String,
40 pub crc_valid: bool,
41 pub mesh_vertices: u32,
42 pub mesh_triangles: u32,
43 pub node_count: u32,
44 pub has_topology: bool,
45 pub has_spatial_index: bool,
46 pub has_provenance: bool,
47 pub paint: Vec<VisionNodePaint>,
48 pub mean_sigma: f32,
50 pub mean_rgb: [u8; 3],
51 pub mean_frequency_hz: f32,
52}
53
54pub fn load_vision_10d_bytes(bytes: &[u8]) -> Result<(Mesh, Vision10dLoaded), String> {
56 load_vision_10d_bytes_with_access(bytes, Vision10dAccess::BrowseAllowUnattested)
57}
58
59pub fn load_vision_10d_bytes_with_access(
61 bytes: &[u8],
62 access: Vision10dAccess,
63) -> Result<(Mesh, Vision10dLoaded), String> {
64 match evaluate_vision_10d_barrier(bytes, access) {
65 Vision10dBarrier::Permit => {}
66 Vision10dBarrier::Deny { reason } => {
67 return Err(format!("vision .10d barrier: {reason}"));
68 }
69 }
70
71 let mut bytes_mut = bytes.to_vec();
72 let crc_valid = verify_whole_file_crc32c(&mut bytes_mut).is_ok();
73 if !crc_valid {
74 return Err("vision .10d: whole-file CRC failed".into());
75 }
76
77 let header = Container10dHeader::parse(&bytes_mut).map_err(|e| format!("header: {e}"))?;
78 let descs =
79 parse_section_table(&bytes_mut, &header).map_err(|e| format!("section table: {e}"))?;
80
81 let mut has_topology = false;
82 let mut has_spatial_index = false;
83 let mut has_provenance = false;
84 for d in descs.iter() {
85 match d.typ() {
86 Some(SectionType::Topology) => has_topology = true,
87 Some(SectionType::SpatialIndex) => has_spatial_index = true,
88 Some(SectionType::ProvenanceSidecar) => has_provenance = true,
89 _ => {}
90 }
91 }
92
93 let mesh = decode_10d_mesh(&bytes_mut).map_err(|e| format!("mesh: {e}"))?;
94
95 let mut node_cap = 0usize;
97 for d in descs.iter() {
98 if d.typ() == Some(SectionType::Tensor10DNodes) {
99 let start = d.byte_offset as usize;
100 let end = start.saturating_add(d.byte_length as usize);
101 if let Some(payload) = bytes_mut.get(start..end) {
102 if let Ok((nh, _)) = parse_node_header(payload) {
103 node_cap = nh.node_count as usize;
104 }
105 }
106 }
107 }
108
109 let mut nodes = vec![Tensor10D::default(); node_cap.max(1)];
110 let n = if node_cap == 0 {
111 0
112 } else {
113 decode_10d_nodes(&bytes_mut, &mut nodes).unwrap_or(0)
114 };
115 nodes.truncate(n);
116
117 let paint: Vec<VisionNodePaint> = nodes.iter().map(node_to_paint).collect();
118 let mean_sigma = if paint.is_empty() {
119 0.35
120 } else {
121 paint.iter().map(|p| p.sigma).sum::<f32>() / paint.len() as f32
122 };
123 let (mr, mg, mb) = sigma_to_display_rgb(mean_sigma);
124 let mean_hz = sigma_to_center_frequency_hz(mean_sigma);
125
126 let loaded = Vision10dLoaded {
127 path: None,
128 size_bytes: bytes.len() as u64,
129 compiled_digest_hex: format!("{:08x}", compiled_digest(&bytes_mut)),
130 crc_valid: true,
131 mesh_vertices: mesh.vertex_count() as u32,
132 mesh_triangles: mesh.triangle_count() as u32,
133 node_count: n as u32,
134 has_topology,
135 has_spatial_index,
136 has_provenance,
137 paint,
138 mean_sigma,
139 mean_rgb: [mr, mg, mb],
140 mean_frequency_hz: mean_hz,
141 };
142 Ok((mesh, loaded))
143}
144
145pub fn load_vision_10d_path(
147 storage_root: &Path,
148 relative_or_abs: &str,
149) -> Result<(Mesh, Vision10dLoaded), String> {
150 load_vision_10d_path_with_access(
151 storage_root,
152 relative_or_abs,
153 Vision10dAccess::BrowseAllowUnattested,
154 )
155}
156
157pub fn load_vision_10d_path_with_access(
159 storage_root: &Path,
160 relative_or_abs: &str,
161 access: Vision10dAccess,
162) -> Result<(Mesh, Vision10dLoaded), String> {
163 let p = Path::new(relative_or_abs);
164 let full = if p.is_absolute() {
165 p.to_path_buf()
166 } else {
167 storage_root.join(relative_or_abs)
168 };
169 let bytes = std::fs::read(&full).map_err(|e| format!("read {}: {e}", full.display()))?;
170 let (mesh, mut loaded) = load_vision_10d_bytes_with_access(&bytes, access)?;
171 loaded.path = Some(
172 full.strip_prefix(storage_root)
173 .ok()
174 .and_then(|x| x.to_str())
175 .unwrap_or(relative_or_abs)
176 .to_string(),
177 );
178 Ok((mesh, loaded))
179}
180
181pub fn temporal_scrub_paint(
185 paint: &[VisionNodePaint],
186 t_slice: f32,
187 t_window: f32,
188 out_indices: &mut [u32],
189) -> usize {
190 let half = t_window * 0.5;
191 let lo = t_slice - half;
192 let hi = t_slice + half;
193 let mut n = 0usize;
194 for (i, p) in paint.iter().enumerate() {
195 if p.t >= lo && p.t <= hi {
196 if n < out_indices.len() {
197 out_indices[n] = i as u32;
198 n += 1;
199 }
200 }
201 }
202 n
203}
204
205pub fn temporal_scrub_paint_vec(
207 paint: &[VisionNodePaint],
208 t_slice: f32,
209 t_window: f32,
210) -> Vec<VisionNodePaint> {
211 let half = t_window * 0.5;
212 let lo = t_slice - half;
213 let hi = t_slice + half;
214 paint
215 .iter()
216 .copied()
217 .filter(|p| p.t >= lo && p.t <= hi)
218 .collect()
219}
220
221pub fn node_to_paint(t: &Tensor10D) -> VisionNodePaint {
223 let (r, g, b) = sigma_to_display_rgb(t.sigma);
224 VisionNodePaint {
225 x: t.x,
226 y: t.y,
227 z: t.z,
228 t: t.t,
229 sigma: t.sigma,
230 rgb: [r, g, b],
231 frequency_hz: sigma_to_center_frequency_hz(t.sigma),
232 ground_truth: t.is_ground_truth(),
233 }
234}
235
236pub fn mesh_vertex_colors_from_nodes(mesh: &Mesh, nodes: &[Tensor10D]) -> Vec<[f32; 4]> {
238 let paints: Vec<_> = nodes.iter().map(node_to_paint).collect();
239 if paints.is_empty() {
240 let (r, g, b) = sigma_to_display_rgb(0.35);
241 return vec![
242 [r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, 1.0];
243 mesh.vertex_count()
244 ];
245 }
246 mesh.positions
247 .iter()
248 .map(|p| {
249 let mut best = 0usize;
250 let mut best_d = f32::INFINITY;
251 for (i, n) in paints.iter().enumerate() {
252 let dx = p[0] - n.x;
255 let dy = p[1] - n.y;
256 let dz = p[2] - n.z;
257 let d = dx * dx + dy * dy + dz * dz;
258 if d < best_d {
259 best_d = d;
260 best = i;
261 }
262 }
263 let c = paints[best].rgb;
264 [
265 c[0] as f32 / 255.0,
266 c[1] as f32 / 255.0,
267 c[2] as f32 / 255.0,
268 1.0,
269 ]
270 })
271 .collect()
272}
273
274#[cfg(test)]
275mod tests {
276 use super::*;
277 use qualia_core_db::container_10d::provenance_section::ProvenanceSidecar;
278 use qualia_core_db::render::compile_10d::{
279 compile_mesh_to_10d_vision, compile_mesh_to_10d_vision_with_provenance,
280 };
281
282 #[test]
283 fn load_vision_seal_with_paint() {
284 let mesh = Mesh {
285 positions: vec![
286 [0.0, 0.0, 0.0],
287 [1.0, 0.0, 0.0],
288 [0.0, 1.0, 0.0],
289 [0.0, 0.0, 1.0],
290 ],
291 triangles: vec![[0, 1, 2], [0, 2, 3]],
292 min: [0.0, 0.0, 0.0],
293 max: [1.0, 1.0, 1.0],
294 };
295 let nodes = [
296 Tensor10D::parallel_context(1.0, 0.0, 0.0, 0.2, 0.2, 0.0, 0.0, 1.0, 0.0, 0.2),
297 Tensor10D::parallel_context(1.0, 0.0, 0.0, 0.8, 0.8, 0.0, 1.0, 1.0, 0.0, 0.8),
298 ];
299 let bytes = compile_mesh_to_10d_vision(&mesh, &nodes).unwrap();
300 let (m, loaded) = load_vision_10d_bytes(&bytes).unwrap();
301 assert_eq!(m.triangle_count(), 2);
302 assert_eq!(loaded.node_count, 2);
303 assert_eq!(loaded.paint.len(), 2);
304 assert!(loaded.crc_valid);
305 assert!(loaded.mean_frequency_hz > 0.0);
306 let colors = mesh_vertex_colors_from_nodes(&m, &nodes);
307 assert_eq!(colors.len(), 4);
308 assert!(loaded.has_topology, "expected Topology on host vision seal");
310 assert!(
311 loaded.has_spatial_index,
312 "expected SpatialIndex on host vision seal"
313 );
314 }
315
316 #[test]
317 fn temporal_scrub_keeps_window() {
318 let paint = [
319 VisionNodePaint {
320 x: 0.0,
321 y: 0.0,
322 z: 0.0,
323 t: 0.0,
324 sigma: 0.1,
325 rgb: [0, 0, 0],
326 frequency_hz: 100.0,
327 ground_truth: false,
328 },
329 VisionNodePaint {
330 x: 0.0,
331 y: 0.0,
332 z: 0.0,
333 t: 5.0,
334 sigma: 0.2,
335 rgb: [0, 0, 0],
336 frequency_hz: 100.0,
337 ground_truth: false,
338 },
339 VisionNodePaint {
340 x: 0.0,
341 y: 0.0,
342 z: 0.0,
343 t: 10.0,
344 sigma: 0.3,
345 rgb: [0, 0, 0],
346 frequency_hz: 100.0,
347 ground_truth: false,
348 },
349 ];
350 let kept = temporal_scrub_paint_vec(&paint, 5.0, 2.0);
351 assert_eq!(kept.len(), 1);
352 assert!((kept[0].t - 5.0).abs() < 1e-5);
353 let mut idx = [0u32; 8];
354 let n = temporal_scrub_paint(&paint, 5.0, 12.0, &mut idx);
355 assert_eq!(n, 3);
356 }
357
358 #[test]
359 fn citable_load_requires_provenance() {
360 let mesh = Mesh {
361 positions: vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
362 triangles: vec![[0, 1, 2]],
363 min: [0.0, 0.0, 0.0],
364 max: [1.0, 1.0, 0.0],
365 };
366 let nodes = [Tensor10D::default()];
367 let bare = compile_mesh_to_10d_vision(&mesh, &nodes).unwrap();
368 assert!(load_vision_10d_bytes_with_access(
369 &bare,
370 Vision10dAccess::CitableRequireProvenance
371 )
372 .is_err());
373 let prov = ProvenanceSidecar::new(b"src", "image/rgb8", "CC0");
374 let sealed = compile_mesh_to_10d_vision_with_provenance(&mesh, &nodes, &prov).unwrap();
375 let (_m, loaded) =
376 load_vision_10d_bytes_with_access(&sealed, Vision10dAccess::CitableRequireProvenance)
377 .unwrap();
378 assert!(loaded.has_provenance);
379 }
380}