@vib3code/sdk 2.0.1 → 2.0.3-canary.0a63e71

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/DOCS/AGENT_HARNESS_ARCHITECTURE.md +245 -0
  3. package/DOCS/ANDROID_DEPLOYMENT.md +59 -0
  4. package/DOCS/ARCHITECTURE.md +1 -0
  5. package/DOCS/CI_TESTING.md +2 -0
  6. package/DOCS/CLI_ONBOARDING.md +3 -1
  7. package/DOCS/CONTROL_REFERENCE.md +2 -0
  8. package/DOCS/CROSS_SITE_DESIGN_PATTERNS.md +119 -0
  9. package/DOCS/ENV_SETUP.md +2 -0
  10. package/DOCS/EPIC_SCROLL_EVENTS.md +775 -0
  11. package/DOCS/EXPANSION_DESIGN.md +979 -0
  12. package/DOCS/EXPANSION_DESIGN_ULTRA.md +389 -0
  13. package/DOCS/EXPORT_FORMATS.md +2 -0
  14. package/DOCS/GPU_DISPOSAL_GUIDE.md +2 -0
  15. package/DOCS/HANDOFF_LANDING_PAGE.md +156 -0
  16. package/DOCS/HANDOFF_SDK_DEVELOPMENT.md +495 -0
  17. package/DOCS/LICENSING_TIERS.md +2 -0
  18. package/DOCS/MASTER_PLAN_2026-01-31.md +4 -2
  19. package/DOCS/MULTIVIZ_CHOREOGRAPHY_PATTERNS.md +939 -0
  20. package/DOCS/OBS_SETUP_GUIDE.md +2 -0
  21. package/DOCS/OPTIMIZATION_PLAN_MATH.md +119 -0
  22. package/DOCS/PRODUCT_STRATEGY.md +65 -0
  23. package/DOCS/PROJECT_SETUP.md +2 -0
  24. package/DOCS/README.md +105 -0
  25. package/DOCS/REFERENCE_SCROLL_ANALYSIS.md +99 -0
  26. package/DOCS/RENDERER_LIFECYCLE.md +2 -0
  27. package/DOCS/REPO_MANIFEST.md +2 -0
  28. package/DOCS/ROADMAP.md +113 -0
  29. package/DOCS/SCROLL_TIMELINE_v3.md +271 -0
  30. package/DOCS/SITE_REFACTOR_PLAN.md +102 -0
  31. package/DOCS/STATUS.md +26 -0
  32. package/DOCS/SYSTEM_INVENTORY.md +37 -32
  33. package/DOCS/TELEMETRY_EXPORTS.md +2 -0
  34. package/DOCS/VISUAL_ANALYSIS_CLICKERSS.md +87 -0
  35. package/DOCS/VISUAL_ANALYSIS_FACETAD.md +135 -0
  36. package/DOCS/VISUAL_ANALYSIS_SIMONE.md +97 -0
  37. package/DOCS/VISUAL_ANALYSIS_TABLESIDE.md +88 -0
  38. package/DOCS/WEBGPU_STATUS.md +121 -38
  39. package/DOCS/XR_BENCHMARKS.md +2 -0
  40. package/DOCS/archive/BLUEPRINT_EXECUTION_PLAN_2026-01-07.md +1 -0
  41. package/DOCS/archive/DEV_TRACK_ANALYSIS.md +1 -0
  42. package/DOCS/archive/DEV_TRACK_PLAN_2026-01-07.md +1 -0
  43. package/DOCS/archive/SESSION_014_PLAN.md +1 -0
  44. package/DOCS/archive/SESSION_LOG_2026-01-07.md +1 -0
  45. package/DOCS/archive/STRATEGIC_BLUEPRINT_2026-01-07.md +1 -0
  46. package/DOCS/archive/SYSTEM_AUDIT_2026-01-30.md +1 -0
  47. package/DOCS/archive/WEBGPU_STATUS_2026-02-15_STALE.md +1 -0
  48. package/DOCS/{DEV_TRACK_SESSION_2026-01-31.md → dev-tracks/DEV_TRACK_SESSION_2026-01-31.md} +3 -1
  49. package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-06.md +233 -0
  50. package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-13.md +129 -0
  51. package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-15.md +144 -0
  52. package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-16.md +110 -0
  53. package/DOCS/dev-tracks/PERF_UPGRADE_2026-02-16.md +310 -0
  54. package/DOCS/dev-tracks/README.md +12 -0
  55. package/README.md +26 -13
  56. package/cpp/CMakeLists.txt +236 -0
  57. package/cpp/bindings/embind.cpp +269 -0
  58. package/cpp/build.sh +129 -0
  59. package/cpp/geometry/Crystal.cpp +103 -0
  60. package/cpp/geometry/Fractal.cpp +136 -0
  61. package/cpp/geometry/GeometryGenerator.cpp +262 -0
  62. package/cpp/geometry/KleinBottle.cpp +71 -0
  63. package/cpp/geometry/Sphere.cpp +134 -0
  64. package/cpp/geometry/Tesseract.cpp +94 -0
  65. package/cpp/geometry/Tetrahedron.cpp +83 -0
  66. package/cpp/geometry/Torus.cpp +65 -0
  67. package/cpp/geometry/WarpFunctions.cpp +238 -0
  68. package/cpp/geometry/Wave.cpp +85 -0
  69. package/cpp/include/vib3_ffi.h +238 -0
  70. package/cpp/math/Mat4x4.cpp +409 -0
  71. package/cpp/math/Mat4x4.hpp +209 -0
  72. package/cpp/math/Projection.cpp +142 -0
  73. package/cpp/math/Projection.hpp +148 -0
  74. package/cpp/math/Rotor4D.cpp +322 -0
  75. package/cpp/math/Rotor4D.hpp +204 -0
  76. package/cpp/math/Vec4.cpp +303 -0
  77. package/cpp/math/Vec4.hpp +225 -0
  78. package/cpp/src/vib3_ffi.cpp +607 -0
  79. package/cpp/tests/Geometry_test.cpp +213 -0
  80. package/cpp/tests/Mat4x4_test.cpp +494 -0
  81. package/cpp/tests/Projection_test.cpp +298 -0
  82. package/cpp/tests/Rotor4D_test.cpp +423 -0
  83. package/cpp/tests/Vec4_test.cpp +489 -0
  84. package/docs/webgpu-live.html +1 -1
  85. package/package.json +41 -30
  86. package/src/agent/index.js +1 -3
  87. package/src/agent/mcp/MCPServer.js +1220 -144
  88. package/src/agent/mcp/index.js +1 -1
  89. package/src/agent/mcp/stdio-server.js +264 -0
  90. package/src/agent/mcp/tools.js +498 -31
  91. package/src/cli/index.js +431 -47
  92. package/src/core/CanvasManager.js +97 -204
  93. package/src/core/ErrorReporter.js +1 -1
  94. package/src/core/Parameters.js +1 -1
  95. package/src/core/VIB3Engine.js +93 -4
  96. package/src/core/VitalitySystem.js +53 -0
  97. package/src/core/index.js +18 -0
  98. package/src/core/renderers/FacetedRendererAdapter.js +10 -9
  99. package/src/core/renderers/HolographicRendererAdapter.js +13 -9
  100. package/src/core/renderers/QuantumRendererAdapter.js +11 -7
  101. package/src/creative/AestheticMapper.js +628 -0
  102. package/src/creative/ChoreographyPlayer.js +481 -0
  103. package/src/creative/index.js +11 -0
  104. package/src/experimental/GameLoop.js +72 -0
  105. package/src/experimental/LatticePhysics.js +100 -0
  106. package/src/experimental/LiveDirector.js +143 -0
  107. package/src/experimental/PlayerController4D.js +154 -0
  108. package/src/experimental/VIB3Actor.js +138 -0
  109. package/src/experimental/VIB3Compositor.js +117 -0
  110. package/src/experimental/VIB3Link.js +122 -0
  111. package/src/experimental/VIB3Orchestrator.js +146 -0
  112. package/src/experimental/VIB3Universe.js +109 -0
  113. package/src/experimental/demos/CrystalLabyrinth.js +202 -0
  114. package/src/export/TradingCardManager.js +3 -4
  115. package/src/export/index.js +11 -1
  116. package/src/faceted/FacetedSystem.js +260 -394
  117. package/src/games/glyph-war/GlyphWarVisualizer.js +641 -0
  118. package/src/geometry/generators/Crystal.js +2 -2
  119. package/src/geometry/warp/HypersphereCore.js +53 -24
  120. package/src/holograms/HolographicVisualizer.js +84 -98
  121. package/src/holograms/RealHolographicSystem.js +194 -43
  122. package/src/math/Mat4x4.js +308 -105
  123. package/src/math/Rotor4D.js +124 -40
  124. package/src/math/Vec4.js +200 -103
  125. package/src/math/index.js +7 -7
  126. package/src/polychora/PolychoraSystem.js +77 -0
  127. package/src/quantum/QuantumEngine.js +103 -66
  128. package/src/quantum/QuantumVisualizer.js +31 -22
  129. package/src/reactivity/index.js +3 -5
  130. package/src/render/LayerPresetManager.js +372 -0
  131. package/src/render/LayerReactivityBridge.js +344 -0
  132. package/src/render/LayerRelationshipGraph.js +610 -0
  133. package/src/render/MultiCanvasBridge.js +148 -25
  134. package/src/render/ShaderLoader.js +38 -0
  135. package/src/render/ShaderProgram.js +4 -4
  136. package/src/render/UnifiedRenderBridge.js +4 -1
  137. package/src/render/backends/WebGPUBackend.js +8 -4
  138. package/src/render/index.js +27 -2
  139. package/src/scene/Node4D.js +74 -24
  140. package/src/scene/index.js +4 -4
  141. package/src/shaders/common/geometry24.glsl +65 -0
  142. package/src/shaders/common/geometry24.wgsl +54 -0
  143. package/src/shaders/common/rotation4d.glsl +4 -4
  144. package/src/shaders/common/rotation4d.wgsl +2 -2
  145. package/src/shaders/common/uniforms.wgsl +15 -8
  146. package/src/shaders/faceted/faceted.frag.glsl +220 -80
  147. package/src/shaders/faceted/faceted.frag.wgsl +144 -90
  148. package/src/shaders/holographic/holographic.frag.glsl +28 -9
  149. package/src/shaders/holographic/holographic.frag.wgsl +112 -41
  150. package/src/shaders/quantum/quantum.frag.glsl +1 -0
  151. package/src/shaders/quantum/quantum.frag.wgsl +6 -4
  152. package/src/testing/ParallelTestFramework.js +2 -2
  153. package/src/ui/adaptive/renderers/webgpu/WebGPURenderer.ts +2 -2
  154. package/src/viewer/GalleryUI.js +17 -0
  155. package/src/viewer/ViewerPortal.js +2 -2
  156. package/src/viewer/index.js +1 -1
  157. package/tools/headless-renderer.js +258 -0
  158. package/tools/shader-sync-verify.js +14 -8
  159. package/tools/site-analysis/all-reports.json +32 -0
  160. package/tools/site-analysis/combined-analysis.md +50 -0
  161. package/tools/site-analyzer.mjs +779 -0
  162. package/tools/visual-catalog/capture.js +276 -0
  163. package/tools/visual-catalog/composite.js +138 -0
  164. package/types/adaptive-sdk.d.ts +204 -5
  165. package/types/agent/cli.d.ts +78 -0
  166. package/types/agent/index.d.ts +18 -0
  167. package/types/agent/mcp.d.ts +87 -0
  168. package/types/agent/telemetry.d.ts +190 -0
  169. package/types/core/VIB3Engine.d.ts +26 -0
  170. package/types/core/index.d.ts +261 -0
  171. package/types/creative/AestheticMapper.d.ts +72 -0
  172. package/types/creative/ChoreographyPlayer.d.ts +96 -0
  173. package/types/creative/index.d.ts +17 -0
  174. package/types/export/index.d.ts +243 -0
  175. package/types/geometry/index.d.ts +164 -0
  176. package/types/math/index.d.ts +214 -0
  177. package/types/render/LayerPresetManager.d.ts +78 -0
  178. package/types/render/LayerReactivityBridge.d.ts +85 -0
  179. package/types/render/LayerRelationshipGraph.d.ts +174 -0
  180. package/types/render/index.d.ts +3 -0
  181. package/types/scene/index.d.ts +204 -0
  182. package/types/systems/index.d.ts +244 -0
  183. package/types/variations/index.d.ts +62 -0
  184. package/types/viewer/index.d.ts +225 -0
  185. package/DOCS/BLUEPRINT_EXECUTION_PLAN_2026-01-07.md +0 -34
  186. package/DOCS/DEV_TRACK_ANALYSIS.md +0 -77
  187. package/DOCS/DEV_TRACK_PLAN_2026-01-07.md +0 -42
  188. package/DOCS/SESSION_014_PLAN.md +0 -195
  189. package/DOCS/SESSION_LOG_2026-01-07.md +0 -56
  190. package/DOCS/STRATEGIC_BLUEPRINT_2026-01-07.md +0 -72
  191. package/DOCS/SYSTEM_AUDIT_2026-01-30.md +0 -738
  192. /package/src/viewer/{ReactivityManager.js → ViewerInputHandler.js} +0 -0
@@ -0,0 +1,423 @@
1
+ /**
2
+ * Rotor4D_test.cpp - Unit tests for Rotor4D 4D rotation class
3
+ *
4
+ * Tests identity construction, plane-angle rotors, Euler6 composition,
5
+ * sandwich product rotation, normalization, and reverse (conjugate).
6
+ */
7
+
8
+ #include <gtest/gtest.h>
9
+ #include "math/Rotor4D.hpp"
10
+ #include "math/Vec4.hpp"
11
+ #include <cmath>
12
+ #include <numbers>
13
+
14
+ using namespace vib3;
15
+
16
+ static constexpr float kEpsilon = 1e-4f;
17
+ static constexpr float kPi = std::numbers::pi_v<float>;
18
+ static constexpr float kHalfPi = kPi / 2.0f;
19
+
20
+ // ============================================================================
21
+ // Identity Rotor
22
+ // ============================================================================
23
+
24
+ TEST(Rotor4D, DefaultConstructorIsIdentity) {
25
+ Rotor4D r;
26
+ EXPECT_FLOAT_EQ(r.s, 1.0f);
27
+ EXPECT_FLOAT_EQ(r.xy, 0.0f);
28
+ EXPECT_FLOAT_EQ(r.xz, 0.0f);
29
+ EXPECT_FLOAT_EQ(r.yz, 0.0f);
30
+ EXPECT_FLOAT_EQ(r.xw, 0.0f);
31
+ EXPECT_FLOAT_EQ(r.yw, 0.0f);
32
+ EXPECT_FLOAT_EQ(r.zw, 0.0f);
33
+ EXPECT_FLOAT_EQ(r.xyzw, 0.0f);
34
+ }
35
+
36
+ TEST(Rotor4D, IdentityFactory) {
37
+ Rotor4D r = Rotor4D::identity();
38
+ EXPECT_FLOAT_EQ(r.s, 1.0f);
39
+ EXPECT_FLOAT_EQ(r.xy, 0.0f);
40
+ EXPECT_FLOAT_EQ(r.xz, 0.0f);
41
+ EXPECT_FLOAT_EQ(r.yz, 0.0f);
42
+ EXPECT_FLOAT_EQ(r.xw, 0.0f);
43
+ EXPECT_FLOAT_EQ(r.yw, 0.0f);
44
+ EXPECT_FLOAT_EQ(r.zw, 0.0f);
45
+ EXPECT_FLOAT_EQ(r.xyzw, 0.0f);
46
+ }
47
+
48
+ TEST(Rotor4D, IdentityIsNormalized) {
49
+ Rotor4D r = Rotor4D::identity();
50
+ EXPECT_TRUE(r.isNormalized());
51
+ EXPECT_NEAR(r.magnitude(), 1.0f, kEpsilon);
52
+ }
53
+
54
+ TEST(Rotor4D, IdentityRotationDoesNothing) {
55
+ Rotor4D r = Rotor4D::identity();
56
+ Vec4 v(1.0f, 2.0f, 3.0f, 4.0f);
57
+ Vec4 rotated = r.rotate(v);
58
+ EXPECT_NEAR(rotated.x, v.x, kEpsilon);
59
+ EXPECT_NEAR(rotated.y, v.y, kEpsilon);
60
+ EXPECT_NEAR(rotated.z, v.z, kEpsilon);
61
+ EXPECT_NEAR(rotated.w, v.w, kEpsilon);
62
+ }
63
+
64
+ // ============================================================================
65
+ // fromPlaneAngle
66
+ // ============================================================================
67
+
68
+ TEST(Rotor4D, FromPlaneAngleZeroIsIdentity) {
69
+ Rotor4D r = Rotor4D::fromPlaneAngle(RotationPlane::XY, 0.0f);
70
+ EXPECT_NEAR(r.s, 1.0f, kEpsilon);
71
+ EXPECT_NEAR(r.magnitude(), 1.0f, kEpsilon);
72
+ }
73
+
74
+ TEST(Rotor4D, FromPlaneAngleXY_HalfTurn) {
75
+ // A rotor encoding angle theta has s = cos(theta/2), bivector = -sin(theta/2)
76
+ Rotor4D r = Rotor4D::fromPlaneAngle(RotationPlane::XY, kPi);
77
+ EXPECT_NEAR(r.s, std::cos(kPi / 2.0f), kEpsilon);
78
+ EXPECT_TRUE(r.isNormalized());
79
+ }
80
+
81
+ TEST(Rotor4D, FromPlaneAngleXY_RotatesXtoY) {
82
+ // Rotating unitX by pi/2 in the XY plane should give unitY
83
+ Rotor4D r = Rotor4D::fromPlaneAngle(RotationPlane::XY, kHalfPi);
84
+ Vec4 result = r.rotate(Vec4::unitX());
85
+ EXPECT_NEAR(result.x, 0.0f, kEpsilon);
86
+ EXPECT_NEAR(result.y, 1.0f, kEpsilon);
87
+ EXPECT_NEAR(result.z, 0.0f, kEpsilon);
88
+ EXPECT_NEAR(result.w, 0.0f, kEpsilon);
89
+ }
90
+
91
+ TEST(Rotor4D, FromPlaneAngleXY_RotatesYtoNegX) {
92
+ // Rotating unitY by pi/2 in XY plane should give -unitX
93
+ Rotor4D r = Rotor4D::fromPlaneAngle(RotationPlane::XY, kHalfPi);
94
+ Vec4 result = r.rotate(Vec4::unitY());
95
+ EXPECT_NEAR(result.x, -1.0f, kEpsilon);
96
+ EXPECT_NEAR(result.y, 0.0f, kEpsilon);
97
+ EXPECT_NEAR(result.z, 0.0f, kEpsilon);
98
+ EXPECT_NEAR(result.w, 0.0f, kEpsilon);
99
+ }
100
+
101
+ TEST(Rotor4D, FromPlaneAngleXZ_RotatesXtoZ) {
102
+ Rotor4D r = Rotor4D::fromPlaneAngle(RotationPlane::XZ, kHalfPi);
103
+ Vec4 result = r.rotate(Vec4::unitX());
104
+ EXPECT_NEAR(result.x, 0.0f, kEpsilon);
105
+ EXPECT_NEAR(result.y, 0.0f, kEpsilon);
106
+ EXPECT_NEAR(result.z, 1.0f, kEpsilon);
107
+ EXPECT_NEAR(result.w, 0.0f, kEpsilon);
108
+ }
109
+
110
+ TEST(Rotor4D, FromPlaneAngleYZ_RotatesYtoZ) {
111
+ Rotor4D r = Rotor4D::fromPlaneAngle(RotationPlane::YZ, kHalfPi);
112
+ Vec4 result = r.rotate(Vec4::unitY());
113
+ EXPECT_NEAR(result.x, 0.0f, kEpsilon);
114
+ EXPECT_NEAR(result.y, 0.0f, kEpsilon);
115
+ EXPECT_NEAR(result.z, 1.0f, kEpsilon);
116
+ EXPECT_NEAR(result.w, 0.0f, kEpsilon);
117
+ }
118
+
119
+ TEST(Rotor4D, FromPlaneAngleXW_RotatesXtoW) {
120
+ // 4D rotation: XW plane, pi/2 should rotate X -> W
121
+ Rotor4D r = Rotor4D::fromPlaneAngle(RotationPlane::XW, kHalfPi);
122
+ Vec4 result = r.rotate(Vec4::unitX());
123
+ EXPECT_NEAR(result.x, 0.0f, kEpsilon);
124
+ EXPECT_NEAR(result.y, 0.0f, kEpsilon);
125
+ EXPECT_NEAR(result.z, 0.0f, kEpsilon);
126
+ EXPECT_NEAR(result.w, 1.0f, kEpsilon);
127
+ }
128
+
129
+ TEST(Rotor4D, FromPlaneAngleYW_RotatesYtoW) {
130
+ Rotor4D r = Rotor4D::fromPlaneAngle(RotationPlane::YW, kHalfPi);
131
+ Vec4 result = r.rotate(Vec4::unitY());
132
+ EXPECT_NEAR(result.x, 0.0f, kEpsilon);
133
+ EXPECT_NEAR(result.y, 0.0f, kEpsilon);
134
+ EXPECT_NEAR(result.z, 0.0f, kEpsilon);
135
+ EXPECT_NEAR(result.w, 1.0f, kEpsilon);
136
+ }
137
+
138
+ TEST(Rotor4D, FromPlaneAngleZW_RotatesZtoW) {
139
+ Rotor4D r = Rotor4D::fromPlaneAngle(RotationPlane::ZW, kHalfPi);
140
+ Vec4 result = r.rotate(Vec4::unitZ());
141
+ EXPECT_NEAR(result.x, 0.0f, kEpsilon);
142
+ EXPECT_NEAR(result.y, 0.0f, kEpsilon);
143
+ EXPECT_NEAR(result.z, 0.0f, kEpsilon);
144
+ EXPECT_NEAR(result.w, 1.0f, kEpsilon);
145
+ }
146
+
147
+ TEST(Rotor4D, PlaneRotationPreservesOrthogonalAxes) {
148
+ // Rotation in XY plane should not affect Z or W components
149
+ Rotor4D r = Rotor4D::fromPlaneAngle(RotationPlane::XY, kHalfPi);
150
+ Vec4 zResult = r.rotate(Vec4::unitZ());
151
+ EXPECT_NEAR(zResult.x, 0.0f, kEpsilon);
152
+ EXPECT_NEAR(zResult.y, 0.0f, kEpsilon);
153
+ EXPECT_NEAR(zResult.z, 1.0f, kEpsilon);
154
+ EXPECT_NEAR(zResult.w, 0.0f, kEpsilon);
155
+
156
+ Vec4 wResult = r.rotate(Vec4::unitW());
157
+ EXPECT_NEAR(wResult.x, 0.0f, kEpsilon);
158
+ EXPECT_NEAR(wResult.y, 0.0f, kEpsilon);
159
+ EXPECT_NEAR(wResult.z, 0.0f, kEpsilon);
160
+ EXPECT_NEAR(wResult.w, 1.0f, kEpsilon);
161
+ }
162
+
163
+ // ============================================================================
164
+ // fromEuler6
165
+ // ============================================================================
166
+
167
+ TEST(Rotor4D, FromEuler6AllZerosIsIdentity) {
168
+ Rotor4D r = Rotor4D::fromEuler6(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
169
+ EXPECT_NEAR(r.s, 1.0f, kEpsilon);
170
+ EXPECT_TRUE(r.isNormalized());
171
+
172
+ // Verify it acts as identity
173
+ Vec4 v(1.0f, 2.0f, 3.0f, 4.0f);
174
+ Vec4 result = r.rotate(v);
175
+ EXPECT_NEAR(result.x, v.x, kEpsilon);
176
+ EXPECT_NEAR(result.y, v.y, kEpsilon);
177
+ EXPECT_NEAR(result.z, v.z, kEpsilon);
178
+ EXPECT_NEAR(result.w, v.w, kEpsilon);
179
+ }
180
+
181
+ TEST(Rotor4D, FromEuler6ArrayOverload) {
182
+ std::array<float, 6> angles = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
183
+ Rotor4D r = Rotor4D::fromEuler6(angles);
184
+ EXPECT_NEAR(r.s, 1.0f, kEpsilon);
185
+ EXPECT_TRUE(r.isNormalized());
186
+ }
187
+
188
+ TEST(Rotor4D, FromEuler6SinglePlaneMatchesFromPlaneAngle) {
189
+ float angle = 0.7f;
190
+ Rotor4D fromEuler = Rotor4D::fromEuler6(angle, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
191
+ Rotor4D fromPlane = Rotor4D::fromPlaneAngle(RotationPlane::XY, angle);
192
+
193
+ // They should produce the same rotation on any vector
194
+ Vec4 test(1.0f, 1.0f, 1.0f, 1.0f);
195
+ Vec4 r1 = fromEuler.rotate(test);
196
+ Vec4 r2 = fromPlane.rotate(test);
197
+ EXPECT_NEAR(r1.x, r2.x, kEpsilon);
198
+ EXPECT_NEAR(r1.y, r2.y, kEpsilon);
199
+ EXPECT_NEAR(r1.z, r2.z, kEpsilon);
200
+ EXPECT_NEAR(r1.w, r2.w, kEpsilon);
201
+ }
202
+
203
+ TEST(Rotor4D, FromEuler6ProducesNormalizedRotor) {
204
+ Rotor4D r = Rotor4D::fromEuler6(0.5f, 0.3f, 0.2f, 0.1f, 0.4f, 0.6f);
205
+ // The composed rotor should still be approximately unit
206
+ EXPECT_NEAR(r.magnitude(), 1.0f, 0.01f);
207
+ }
208
+
209
+ // ============================================================================
210
+ // Normalization
211
+ // ============================================================================
212
+
213
+ TEST(Rotor4D, NormalizeMaintainsUnit) {
214
+ Rotor4D r = Rotor4D::fromPlaneAngle(RotationPlane::XY, 1.0f);
215
+ r.normalize();
216
+ EXPECT_NEAR(r.magnitude(), 1.0f, kEpsilon);
217
+ EXPECT_TRUE(r.isNormalized());
218
+ }
219
+
220
+ TEST(Rotor4D, NormalizedReturnsUnitRotor) {
221
+ // Manually create a non-unit rotor
222
+ Rotor4D r(2.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
223
+ Rotor4D n = r.normalized();
224
+ EXPECT_NEAR(n.magnitude(), 1.0f, kEpsilon);
225
+ EXPECT_NEAR(n.s, 1.0f, kEpsilon);
226
+ }
227
+
228
+ TEST(Rotor4D, MagnitudeSquared) {
229
+ Rotor4D r = Rotor4D::identity();
230
+ EXPECT_NEAR(r.magnitudeSquared(), 1.0f, kEpsilon);
231
+ }
232
+
233
+ // ============================================================================
234
+ // Reverse (Conjugate)
235
+ // ============================================================================
236
+
237
+ TEST(Rotor4D, ReverseNegatesBivectors) {
238
+ Rotor4D r(1.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f);
239
+ Rotor4D rev = r.reverse();
240
+
241
+ // Scalar stays the same
242
+ EXPECT_FLOAT_EQ(rev.s, r.s);
243
+ // Bivectors are negated
244
+ EXPECT_FLOAT_EQ(rev.xy, -r.xy);
245
+ EXPECT_FLOAT_EQ(rev.xz, -r.xz);
246
+ EXPECT_FLOAT_EQ(rev.yz, -r.yz);
247
+ EXPECT_FLOAT_EQ(rev.xw, -r.xw);
248
+ EXPECT_FLOAT_EQ(rev.yw, -r.yw);
249
+ EXPECT_FLOAT_EQ(rev.zw, -r.zw);
250
+ // Pseudoscalar: grade-4 element, reverse keeps it the same
251
+ EXPECT_FLOAT_EQ(rev.xyzw, r.xyzw);
252
+ }
253
+
254
+ TEST(Rotor4D, ReverseOfIdentityIsIdentity) {
255
+ Rotor4D r = Rotor4D::identity();
256
+ Rotor4D rev = r.reverse();
257
+ EXPECT_FLOAT_EQ(rev.s, 1.0f);
258
+ EXPECT_FLOAT_EQ(rev.xy, 0.0f);
259
+ EXPECT_FLOAT_EQ(rev.xz, 0.0f);
260
+ EXPECT_FLOAT_EQ(rev.yz, 0.0f);
261
+ }
262
+
263
+ TEST(Rotor4D, RotorTimesReverseGivesIdentityRotation) {
264
+ // R * R_reverse should produce identity rotation
265
+ Rotor4D r = Rotor4D::fromPlaneAngle(RotationPlane::XY, 1.0f);
266
+ Rotor4D product = r * r.reverse();
267
+
268
+ // The result should act as identity on any vector
269
+ Vec4 v(1.0f, 2.0f, 3.0f, 4.0f);
270
+ Vec4 result = product.rotate(v);
271
+ EXPECT_NEAR(result.x, v.x, kEpsilon);
272
+ EXPECT_NEAR(result.y, v.y, kEpsilon);
273
+ EXPECT_NEAR(result.z, v.z, kEpsilon);
274
+ EXPECT_NEAR(result.w, v.w, kEpsilon);
275
+ }
276
+
277
+ // ============================================================================
278
+ // Rotor Composition
279
+ // ============================================================================
280
+
281
+ TEST(Rotor4D, IdentityTimesRotorIsRotor) {
282
+ Rotor4D id = Rotor4D::identity();
283
+ Rotor4D r = Rotor4D::fromPlaneAngle(RotationPlane::XZ, 0.5f);
284
+ Rotor4D product = id * r;
285
+
286
+ Vec4 test(1.0f, 0.0f, 0.0f, 0.0f);
287
+ Vec4 r1 = product.rotate(test);
288
+ Vec4 r2 = r.rotate(test);
289
+ EXPECT_NEAR(r1.x, r2.x, kEpsilon);
290
+ EXPECT_NEAR(r1.y, r2.y, kEpsilon);
291
+ EXPECT_NEAR(r1.z, r2.z, kEpsilon);
292
+ EXPECT_NEAR(r1.w, r2.w, kEpsilon);
293
+ }
294
+
295
+ TEST(Rotor4D, TwoHalfTurnsMakeFullTurn) {
296
+ // Two 90-degree rotations = 180-degree rotation
297
+ Rotor4D half = Rotor4D::fromPlaneAngle(RotationPlane::XY, kHalfPi);
298
+ Rotor4D full = half * half;
299
+
300
+ // unitX rotated by pi in XY plane should become -unitX
301
+ Vec4 result = full.rotate(Vec4::unitX());
302
+ EXPECT_NEAR(result.x, -1.0f, kEpsilon);
303
+ EXPECT_NEAR(result.y, 0.0f, kEpsilon);
304
+ EXPECT_NEAR(result.z, 0.0f, kEpsilon);
305
+ EXPECT_NEAR(result.w, 0.0f, kEpsilon);
306
+ }
307
+
308
+ // ============================================================================
309
+ // Rotation preserves length
310
+ // ============================================================================
311
+
312
+ TEST(Rotor4D, RotationPreservesLength) {
313
+ Rotor4D r = Rotor4D::fromEuler6(0.5f, 0.3f, 0.7f, 0.2f, 0.4f, 0.1f);
314
+ Vec4 v(1.0f, 2.0f, 3.0f, 4.0f);
315
+ Vec4 rotated = r.rotate(v);
316
+ EXPECT_NEAR(rotated.length(), v.length(), kEpsilon);
317
+ }
318
+
319
+ TEST(Rotor4D, RotationPreservesZeroVector) {
320
+ Rotor4D r = Rotor4D::fromPlaneAngle(RotationPlane::XW, 1.0f);
321
+ Vec4 result = r.rotate(Vec4::zero());
322
+ EXPECT_NEAR(result.x, 0.0f, kEpsilon);
323
+ EXPECT_NEAR(result.y, 0.0f, kEpsilon);
324
+ EXPECT_NEAR(result.z, 0.0f, kEpsilon);
325
+ EXPECT_NEAR(result.w, 0.0f, kEpsilon);
326
+ }
327
+
328
+ // ============================================================================
329
+ // Inverse
330
+ // ============================================================================
331
+
332
+ TEST(Rotor4D, InverseUndoesRotation) {
333
+ Rotor4D r = Rotor4D::fromPlaneAngle(RotationPlane::YZ, 0.8f);
334
+ Rotor4D inv = r.inverse();
335
+ Vec4 v(1.0f, 2.0f, 3.0f, 4.0f);
336
+ Vec4 rotated = r.rotate(v);
337
+ Vec4 unrotated = inv.rotate(rotated);
338
+ EXPECT_NEAR(unrotated.x, v.x, kEpsilon);
339
+ EXPECT_NEAR(unrotated.y, v.y, kEpsilon);
340
+ EXPECT_NEAR(unrotated.z, v.z, kEpsilon);
341
+ EXPECT_NEAR(unrotated.w, v.w, kEpsilon);
342
+ }
343
+
344
+ // ============================================================================
345
+ // Matrix Conversion
346
+ // ============================================================================
347
+
348
+ TEST(Rotor4D, IdentityRotorGivesIdentityMatrix) {
349
+ Rotor4D r = Rotor4D::identity();
350
+ Mat4x4 m = r.toMatrix();
351
+ EXPECT_TRUE(m.isIdentity());
352
+ }
353
+
354
+ TEST(Rotor4D, MatrixRotationMatchesSandwichProduct) {
355
+ Rotor4D r = Rotor4D::fromPlaneAngle(RotationPlane::XY, kHalfPi);
356
+ Mat4x4 m = r.toMatrix();
357
+
358
+ Vec4 v = Vec4::unitX();
359
+ Vec4 rotorResult = r.rotate(v);
360
+ Vec4 matResult = m * v;
361
+
362
+ EXPECT_NEAR(rotorResult.x, matResult.x, kEpsilon);
363
+ EXPECT_NEAR(rotorResult.y, matResult.y, kEpsilon);
364
+ EXPECT_NEAR(rotorResult.z, matResult.z, kEpsilon);
365
+ EXPECT_NEAR(rotorResult.w, matResult.w, kEpsilon);
366
+ }
367
+
368
+ // ============================================================================
369
+ // Interpolation
370
+ // ============================================================================
371
+
372
+ TEST(Rotor4D, SlerpEndpoints) {
373
+ Rotor4D a = Rotor4D::identity();
374
+ Rotor4D b = Rotor4D::fromPlaneAngle(RotationPlane::XY, kPi);
375
+
376
+ // t=0 should give a
377
+ Rotor4D s0 = a.slerp(b, 0.0f);
378
+ Vec4 v0 = s0.rotate(Vec4::unitX());
379
+ Vec4 va = a.rotate(Vec4::unitX());
380
+ EXPECT_NEAR(v0.x, va.x, kEpsilon);
381
+ EXPECT_NEAR(v0.y, va.y, kEpsilon);
382
+
383
+ // t=1 should give b
384
+ Rotor4D s1 = a.slerp(b, 1.0f);
385
+ Vec4 v1 = s1.rotate(Vec4::unitX());
386
+ Vec4 vb = b.rotate(Vec4::unitX());
387
+ EXPECT_NEAR(v1.x, vb.x, kEpsilon);
388
+ EXPECT_NEAR(v1.y, vb.y, kEpsilon);
389
+ }
390
+
391
+ // ============================================================================
392
+ // Comparison and toArray
393
+ // ============================================================================
394
+
395
+ TEST(Rotor4D, EqualityOperator) {
396
+ Rotor4D a = Rotor4D::identity();
397
+ Rotor4D b = Rotor4D::identity();
398
+ EXPECT_TRUE(a == b);
399
+ }
400
+
401
+ TEST(Rotor4D, ToArrayRoundTrip) {
402
+ Rotor4D r(1.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f);
403
+ auto arr = r.toArray();
404
+ EXPECT_EQ(arr.size(), 8u);
405
+ EXPECT_FLOAT_EQ(arr[0], r.s);
406
+ EXPECT_FLOAT_EQ(arr[1], r.xy);
407
+ EXPECT_FLOAT_EQ(arr[2], r.xz);
408
+ EXPECT_FLOAT_EQ(arr[3], r.yz);
409
+ EXPECT_FLOAT_EQ(arr[4], r.xw);
410
+ EXPECT_FLOAT_EQ(arr[5], r.yw);
411
+ EXPECT_FLOAT_EQ(arr[6], r.zw);
412
+ EXPECT_FLOAT_EQ(arr[7], r.xyzw);
413
+ }
414
+
415
+ // ============================================================================
416
+ // Dot product between rotors
417
+ // ============================================================================
418
+
419
+ TEST(Rotor4D, DotProductWithSelf) {
420
+ Rotor4D r = Rotor4D::fromPlaneAngle(RotationPlane::XY, 0.5f);
421
+ float d = r.dot(r);
422
+ EXPECT_NEAR(d, r.magnitudeSquared(), kEpsilon);
423
+ }