qualia_core_db/render/projection.rs
1//! Unified manifold projection (Phase 1.4, `RENDERER_IMPLEMENTATION_PLAN.md`) — **one projection,
2//! many views**.
3//!
4//! The renderer's foundation is the 10D tensor manifold. A node's *place* is decided once, by the
5//! semantic-motor map `10D → 3D world` (the same map `projector.wgsl` applies on the GPU;
6//! [`crate::render::pga`] is its parity-tested CPU oracle). Every "view" — the 3D scene, the 2D
7//! canvas — is then a projection of that **same** world point onto a target, not an independent
8//! re-computation. This module is the single entry point that makes that explicit:
9//!
10//! * [`manifold_world`] — the shared step: `Tensor10D → [x,y,z]` world.
11//! * [`project`] — that world point as the requested [`ProjectionTarget`] (3D volume, or its 2D
12//! planar shadow). One call, selectable view.
13//!
14//! The 3D *scene* additionally applies the orbit camera ([`crate::render::camera`]) on top of the
15//! world point; the 2D *canvas* uses the planar shadow directly. Both start from one `project`.
16
17use crate::render::pga::{sandwich_point, semantic_motor_intrinsic};
18use crate::render::telemetry::STANDPOINT_SPECTATOR;
19use crate::tensor::Tensor10D;
20
21/// Which view of the shared manifold world point to produce.
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
23pub enum ProjectionTarget {
24 /// 2D canvas: the orthographic shadow of the world point on the `z = 0` plane.
25 Plane2D,
26 /// 3D scene: the world point itself (the orbit camera is applied downstream).
27 Volume3D,
28}
29
30/// The shared projection step — a 10D tensor node to its 3D world position via the semantic-motor
31/// manifold map. `time` drives the animated bands (`v`/`q`); a spectator standpoint with full
32/// epistemic aperture is used (view-neutral). This is the parity-tested oracle of `projector.wgsl`.
33#[inline]
34pub fn manifold_world(t: &Tensor10D, time: f32) -> [f32; 3] {
35 let local = [t.x, t.y, t.z];
36 let motor = semantic_motor_intrinsic(
37 t.v,
38 t.w,
39 t.q,
40 t.sigma,
41 time,
42 t.alpha,
43 local,
44 STANDPOINT_SPECTATOR,
45 1.0,
46 );
47 sandwich_point(motor, local)
48}
49
50/// One projection, many views: project a 10D node through the shared manifold map, then select the
51/// view. `Volume3D` yields the 3D world point; `Plane2D` yields its 2D shadow (`z` zeroed). The two
52/// agree on `(x, y)` by construction — they are the *same* manifold point seen two ways.
53#[inline]
54pub fn project(t: &Tensor10D, time: f32, target: ProjectionTarget) -> [f32; 3] {
55 let world = manifold_world(t, time);
56 match target {
57 ProjectionTarget::Volume3D => world,
58 ProjectionTarget::Plane2D => [world[0], world[1], 0.0],
59 }
60}
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65
66 fn node() -> Tensor10D {
67 Tensor10D {
68 q: 0.4,
69 v: 1.5,
70 w: 2.0,
71 x: 0.3,
72 y: -0.2,
73 z: 0.5,
74 t: 0.5,
75 alpha: 0.9,
76 mu: 0.0,
77 sigma: 0.25,
78 }
79 }
80
81 #[test]
82 fn one_projection_many_views() {
83 let t = node();
84 let time = 0.7;
85 let v3 = project(&t, time, ProjectionTarget::Volume3D);
86 let v2 = project(&t, time, ProjectionTarget::Plane2D);
87 // Both views are the SAME manifold world point: the 2D plane view is the (x,y) shadow of
88 // the 3D volume view, and the 3D view is exactly the shared step.
89 assert!((v2[0] - v3[0]).abs() < 1e-6);
90 assert!((v2[1] - v3[1]).abs() < 1e-6);
91 assert_eq!(v2[2], 0.0);
92 assert_eq!(v3, manifold_world(&t, time));
93 }
94
95 #[test]
96 fn euclidean_node_projects_to_itself() {
97 // v=0 (Euclidean), w=0, q=0 → identity motor → world == local, independent of time.
98 let t = Tensor10D {
99 q: 0.0,
100 v: 0.0,
101 w: 0.0,
102 x: 0.2,
103 y: -0.4,
104 z: 0.1,
105 sigma: 0.0,
106 alpha: 1.0,
107 ..node()
108 };
109 let w = manifold_world(&t, 1.23);
110 assert!((w[0] - 0.2).abs() < 1e-6);
111 assert!((w[1] + 0.4).abs() < 1e-6);
112 assert!((w[2] - 0.1).abs() < 1e-6);
113 }
114
115 #[test]
116 fn projection_is_deterministic() {
117 let t = node();
118 assert_eq!(
119 project(&t, 2.5, ProjectionTarget::Volume3D),
120 project(&t, 2.5, ProjectionTarget::Volume3D)
121 );
122 }
123}