@vib3code/sdk 2.0.1 → 2.0.3-canary.6f35b4c

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 (96) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/DOCS/AGENT_HARNESS_ARCHITECTURE.md +243 -0
  3. package/DOCS/CLI_ONBOARDING.md +1 -1
  4. package/DOCS/CROSS_SITE_DESIGN_PATTERNS.md +117 -0
  5. package/DOCS/EPIC_SCROLL_EVENTS.md +773 -0
  6. package/DOCS/HANDOFF_LANDING_PAGE.md +154 -0
  7. package/DOCS/HANDOFF_SDK_DEVELOPMENT.md +493 -0
  8. package/DOCS/MULTIVIZ_CHOREOGRAPHY_PATTERNS.md +937 -0
  9. package/DOCS/PRODUCT_STRATEGY.md +63 -0
  10. package/DOCS/README.md +103 -0
  11. package/DOCS/REFERENCE_SCROLL_ANALYSIS.md +97 -0
  12. package/DOCS/ROADMAP.md +111 -0
  13. package/DOCS/SCROLL_TIMELINE_v3.md +269 -0
  14. package/DOCS/SITE_REFACTOR_PLAN.md +100 -0
  15. package/DOCS/STATUS.md +24 -0
  16. package/DOCS/SYSTEM_INVENTORY.md +33 -30
  17. package/DOCS/VISUAL_ANALYSIS_CLICKERSS.md +85 -0
  18. package/DOCS/VISUAL_ANALYSIS_FACETAD.md +133 -0
  19. package/DOCS/VISUAL_ANALYSIS_SIMONE.md +95 -0
  20. package/DOCS/VISUAL_ANALYSIS_TABLESIDE.md +86 -0
  21. package/DOCS/{BLUEPRINT_EXECUTION_PLAN_2026-01-07.md → archive/BLUEPRINT_EXECUTION_PLAN_2026-01-07.md} +1 -1
  22. package/DOCS/{DEV_TRACK_ANALYSIS.md → archive/DEV_TRACK_ANALYSIS.md} +3 -0
  23. package/DOCS/{SYSTEM_AUDIT_2026-01-30.md → archive/SYSTEM_AUDIT_2026-01-30.md} +3 -0
  24. package/DOCS/{DEV_TRACK_SESSION_2026-01-31.md → dev-tracks/DEV_TRACK_SESSION_2026-01-31.md} +1 -1
  25. package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-06.md +231 -0
  26. package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-13.md +127 -0
  27. package/DOCS/dev-tracks/README.md +10 -0
  28. package/README.md +26 -13
  29. package/cpp/CMakeLists.txt +236 -0
  30. package/cpp/bindings/embind.cpp +269 -0
  31. package/cpp/build.sh +129 -0
  32. package/cpp/geometry/Crystal.cpp +103 -0
  33. package/cpp/geometry/Fractal.cpp +136 -0
  34. package/cpp/geometry/GeometryGenerator.cpp +262 -0
  35. package/cpp/geometry/KleinBottle.cpp +71 -0
  36. package/cpp/geometry/Sphere.cpp +134 -0
  37. package/cpp/geometry/Tesseract.cpp +94 -0
  38. package/cpp/geometry/Tetrahedron.cpp +83 -0
  39. package/cpp/geometry/Torus.cpp +65 -0
  40. package/cpp/geometry/WarpFunctions.cpp +238 -0
  41. package/cpp/geometry/Wave.cpp +85 -0
  42. package/cpp/include/vib3_ffi.h +238 -0
  43. package/cpp/math/Mat4x4.cpp +409 -0
  44. package/cpp/math/Mat4x4.hpp +209 -0
  45. package/cpp/math/Projection.cpp +142 -0
  46. package/cpp/math/Projection.hpp +148 -0
  47. package/cpp/math/Rotor4D.cpp +322 -0
  48. package/cpp/math/Rotor4D.hpp +204 -0
  49. package/cpp/math/Vec4.cpp +303 -0
  50. package/cpp/math/Vec4.hpp +225 -0
  51. package/cpp/src/vib3_ffi.cpp +607 -0
  52. package/cpp/tests/Geometry_test.cpp +213 -0
  53. package/cpp/tests/Mat4x4_test.cpp +494 -0
  54. package/cpp/tests/Projection_test.cpp +298 -0
  55. package/cpp/tests/Rotor4D_test.cpp +423 -0
  56. package/cpp/tests/Vec4_test.cpp +489 -0
  57. package/package.json +31 -27
  58. package/src/agent/mcp/MCPServer.js +722 -0
  59. package/src/agent/mcp/stdio-server.js +264 -0
  60. package/src/agent/mcp/tools.js +367 -0
  61. package/src/cli/index.js +0 -0
  62. package/src/core/CanvasManager.js +97 -204
  63. package/src/core/ErrorReporter.js +1 -1
  64. package/src/core/Parameters.js +1 -1
  65. package/src/core/VIB3Engine.js +38 -1
  66. package/src/core/VitalitySystem.js +53 -0
  67. package/src/core/renderers/HolographicRendererAdapter.js +2 -2
  68. package/src/creative/AestheticMapper.js +628 -0
  69. package/src/creative/ChoreographyPlayer.js +481 -0
  70. package/src/export/TradingCardManager.js +3 -4
  71. package/src/faceted/FacetedSystem.js +237 -388
  72. package/src/holograms/HolographicVisualizer.js +29 -12
  73. package/src/holograms/RealHolographicSystem.js +68 -12
  74. package/src/polychora/PolychoraSystem.js +77 -0
  75. package/src/quantum/QuantumEngine.js +103 -66
  76. package/src/quantum/QuantumVisualizer.js +7 -2
  77. package/src/render/UnifiedRenderBridge.js +3 -0
  78. package/src/shaders/faceted/faceted.frag.glsl +220 -80
  79. package/src/shaders/faceted/faceted.frag.wgsl +138 -97
  80. package/src/shaders/holographic/holographic.frag.glsl +28 -9
  81. package/src/shaders/holographic/holographic.frag.wgsl +107 -38
  82. package/src/shaders/quantum/quantum.frag.glsl +1 -0
  83. package/src/shaders/quantum/quantum.frag.wgsl +1 -1
  84. package/src/viewer/index.js +1 -1
  85. package/tools/headless-renderer.js +258 -0
  86. package/tools/shader-sync-verify.js +8 -4
  87. package/tools/site-analysis/all-reports.json +32 -0
  88. package/tools/site-analysis/combined-analysis.md +50 -0
  89. package/tools/site-analyzer.mjs +779 -0
  90. package/tools/visual-catalog/capture.js +276 -0
  91. package/tools/visual-catalog/composite.js +138 -0
  92. /package/DOCS/{DEV_TRACK_PLAN_2026-01-07.md → archive/DEV_TRACK_PLAN_2026-01-07.md} +0 -0
  93. /package/DOCS/{SESSION_014_PLAN.md → archive/SESSION_014_PLAN.md} +0 -0
  94. /package/DOCS/{SESSION_LOG_2026-01-07.md → archive/SESSION_LOG_2026-01-07.md} +0 -0
  95. /package/DOCS/{STRATEGIC_BLUEPRINT_2026-01-07.md → archive/STRATEGIC_BLUEPRINT_2026-01-07.md} +0 -0
  96. /package/src/viewer/{ReactivityManager.js → ViewerInputHandler.js} +0 -0
@@ -0,0 +1,213 @@
1
+ /**
2
+ * Geometry_test.cpp - Unit tests for geometry generation
3
+ *
4
+ * Tests that the geometry generator produces valid vertex data for all 24
5
+ * geometry indices (3 core types x 8 base geometries).
6
+ *
7
+ * Since the geometry library may not have a standalone header, we include
8
+ * the relevant source and math headers directly.
9
+ */
10
+
11
+ #include <gtest/gtest.h>
12
+ #include "math/Vec4.hpp"
13
+ #include "math/Mat4x4.hpp"
14
+ #include "math/Rotor4D.hpp"
15
+ #include "math/Projection.hpp"
16
+
17
+ // Forward-declare the public API from GeometryGenerator.cpp
18
+ // (linked via vib3_geometry library in CMake)
19
+ namespace vib3 {
20
+ std::vector<Vec4> generateGeometry(int geometryIndex, int resolution) noexcept;
21
+ }
22
+
23
+ using namespace vib3;
24
+
25
+ // ============================================================================
26
+ // Geometry Name Constants (mirror the 24-geometry encoding)
27
+ // ============================================================================
28
+
29
+ // geometry_index = core_type * 8 + base_geometry
30
+ // core_type: 0=Base, 1=Hypersphere, 2=Hypertetrahedron
31
+ // base_geometry: 0=Tetrahedron, 1=Hypercube, 2=Sphere, 3=Torus,
32
+ // 4=KleinBottle, 5=Fractal, 6=Wave, 7=Crystal
33
+
34
+ static constexpr int kTetrahedronBase = 0;
35
+ static constexpr int kHypercubeBase = 1;
36
+ static constexpr int kSphereBase = 2;
37
+ static constexpr int kTorusBase = 3;
38
+ static constexpr int kKleinBottleBase = 4;
39
+ static constexpr int kFractalBase = 5;
40
+ static constexpr int kWaveBase = 6;
41
+ static constexpr int kCrystalBase = 7;
42
+
43
+ static constexpr int kHypersphereOffset = 8;
44
+ static constexpr int kHypertetraOffset = 16;
45
+
46
+ static constexpr int kTotalGeometries = 24;
47
+
48
+ /** Default subdivision resolution for tests. */
49
+ static constexpr int kDefaultRes = 16;
50
+
51
+ // ============================================================================
52
+ // Tesseract (Hypercube) Vertex Count
53
+ // ============================================================================
54
+
55
+ TEST(Geometry, TesseractHas16Vertices) {
56
+ // A 4D hypercube (tesseract) has 2^4 = 16 vertices
57
+ // The vertices are all combinations of (+/-1, +/-1, +/-1, +/-1)
58
+ auto vertices = generateGeometry(kHypercubeBase, kDefaultRes);
59
+ EXPECT_GE(vertices.size(), 16u)
60
+ << "Tesseract should have at least 16 vertices";
61
+
62
+ // Verify that all vertices have valid (non-NaN, non-Inf) components
63
+ for (const auto& v : vertices) {
64
+ EXPECT_FALSE(std::isnan(v.x)) << "Vertex has NaN x component";
65
+ EXPECT_FALSE(std::isnan(v.y)) << "Vertex has NaN y component";
66
+ EXPECT_FALSE(std::isnan(v.z)) << "Vertex has NaN z component";
67
+ EXPECT_FALSE(std::isnan(v.w)) << "Vertex has NaN w component";
68
+ EXPECT_FALSE(std::isinf(v.x)) << "Vertex has Inf x component";
69
+ EXPECT_FALSE(std::isinf(v.y)) << "Vertex has Inf y component";
70
+ EXPECT_FALSE(std::isinf(v.z)) << "Vertex has Inf z component";
71
+ EXPECT_FALSE(std::isinf(v.w)) << "Vertex has Inf w component";
72
+ }
73
+ }
74
+
75
+ // ============================================================================
76
+ // Tetrahedron Vertex Count
77
+ // ============================================================================
78
+
79
+ TEST(Geometry, TetrahedronHas4Vertices) {
80
+ // A tetrahedron has 4 vertices
81
+ auto vertices = generateGeometry(kTetrahedronBase, kDefaultRes);
82
+ EXPECT_GE(vertices.size(), 4u)
83
+ << "Tetrahedron should have at least 4 vertices";
84
+
85
+ for (const auto& v : vertices) {
86
+ EXPECT_FALSE(std::isnan(v.x));
87
+ EXPECT_FALSE(std::isnan(v.y));
88
+ EXPECT_FALSE(std::isnan(v.z));
89
+ EXPECT_FALSE(std::isnan(v.w));
90
+ }
91
+ }
92
+
93
+ // ============================================================================
94
+ // All 24 Geometries Produce Non-Empty Vertex Sets
95
+ // ============================================================================
96
+
97
+ TEST(Geometry, AllGeometriesProduceVertices) {
98
+ for (int i = 0; i < kTotalGeometries; ++i) {
99
+ auto vertices = generateGeometry(i, kDefaultRes);
100
+ EXPECT_GT(vertices.size(), 0u)
101
+ << "Geometry index " << i << " should produce non-empty vertex set";
102
+ }
103
+ }
104
+
105
+ TEST(Geometry, AllGeometriesHaveValidVertices) {
106
+ for (int i = 0; i < kTotalGeometries; ++i) {
107
+ auto vertices = generateGeometry(i, kDefaultRes);
108
+ for (size_t j = 0; j < vertices.size(); ++j) {
109
+ EXPECT_FALSE(std::isnan(vertices[j].x))
110
+ << "Geometry " << i << " vertex " << j << " has NaN x";
111
+ EXPECT_FALSE(std::isnan(vertices[j].y))
112
+ << "Geometry " << i << " vertex " << j << " has NaN y";
113
+ EXPECT_FALSE(std::isnan(vertices[j].z))
114
+ << "Geometry " << i << " vertex " << j << " has NaN z";
115
+ EXPECT_FALSE(std::isnan(vertices[j].w))
116
+ << "Geometry " << i << " vertex " << j << " has NaN w";
117
+ EXPECT_FALSE(std::isinf(vertices[j].x))
118
+ << "Geometry " << i << " vertex " << j << " has Inf x";
119
+ EXPECT_FALSE(std::isinf(vertices[j].y))
120
+ << "Geometry " << i << " vertex " << j << " has Inf y";
121
+ EXPECT_FALSE(std::isinf(vertices[j].z))
122
+ << "Geometry " << i << " vertex " << j << " has Inf z";
123
+ EXPECT_FALSE(std::isinf(vertices[j].w))
124
+ << "Geometry " << i << " vertex " << j << " has Inf w";
125
+ }
126
+ }
127
+ }
128
+
129
+ // ============================================================================
130
+ // Core Type Offset Encoding
131
+ // ============================================================================
132
+
133
+ TEST(Geometry, BaseGeometriesRange0to7) {
134
+ // Base geometries (core_type=0) are indices 0-7
135
+ for (int i = 0; i <= 7; ++i) {
136
+ auto vertices = generateGeometry(i, kDefaultRes);
137
+ EXPECT_GT(vertices.size(), 0u)
138
+ << "Base geometry " << i << " should produce vertices";
139
+ }
140
+ }
141
+
142
+ TEST(Geometry, HypersphereGeometriesRange8to15) {
143
+ // Hypersphere warped geometries (core_type=1) are indices 8-15
144
+ for (int i = 8; i <= 15; ++i) {
145
+ auto vertices = generateGeometry(i, kDefaultRes);
146
+ EXPECT_GT(vertices.size(), 0u)
147
+ << "Hypersphere geometry " << i << " should produce vertices";
148
+ }
149
+ }
150
+
151
+ TEST(Geometry, HypertetraGeometriesRange16to23) {
152
+ // Hypertetrahedron warped geometries (core_type=2) are indices 16-23
153
+ for (int i = 16; i <= 23; ++i) {
154
+ auto vertices = generateGeometry(i, kDefaultRes);
155
+ EXPECT_GT(vertices.size(), 0u)
156
+ << "Hypertetra geometry " << i << " should produce vertices";
157
+ }
158
+ }
159
+
160
+ // ============================================================================
161
+ // Specific Geometry Properties
162
+ // ============================================================================
163
+
164
+ TEST(Geometry, SphereVerticesAreApproximatelyOnUnitSphere) {
165
+ auto vertices = generateGeometry(kSphereBase, kDefaultRes);
166
+ EXPECT_GT(vertices.size(), 0u);
167
+
168
+ // For a unit sphere, most vertices should have length close to 1.0
169
+ int onSphere = 0;
170
+ for (const auto& v : vertices) {
171
+ float len = v.length();
172
+ if (std::abs(len - 1.0f) < 0.2f) {
173
+ ++onSphere;
174
+ }
175
+ }
176
+ // At least half should be near the unit sphere
177
+ EXPECT_GT(onSphere, static_cast<int>(vertices.size()) / 2)
178
+ << "Most sphere vertices should be near unit length";
179
+ }
180
+
181
+ TEST(Geometry, HypercubeVerticesHaveSymmetry) {
182
+ // Tesseract vertices should be symmetric: if (x,y,z,w) is a vertex,
183
+ // then (-x,y,z,w) should also be (approximately) a vertex
184
+ auto vertices = generateGeometry(kHypercubeBase, kDefaultRes);
185
+ EXPECT_GE(vertices.size(), 16u);
186
+
187
+ // Count vertices with positive x and negative x
188
+ int positiveX = 0, negativeX = 0;
189
+ for (const auto& v : vertices) {
190
+ if (v.x > 0.01f) ++positiveX;
191
+ if (v.x < -0.01f) ++negativeX;
192
+ }
193
+ // Should be roughly balanced
194
+ EXPECT_GT(positiveX, 0) << "Should have vertices with positive x";
195
+ EXPECT_GT(negativeX, 0) << "Should have vertices with negative x";
196
+ }
197
+
198
+ // ============================================================================
199
+ // Same Base Geometry Across Core Types
200
+ // ============================================================================
201
+
202
+ TEST(Geometry, WarpedVersionsHaveSameOrMoreVertices) {
203
+ // Warped versions should have at least as many vertices as the base
204
+ for (int base = 0; base < 8; ++base) {
205
+ auto baseVertices = generateGeometry(base, kDefaultRes);
206
+ auto hypersphereVertices = generateGeometry(base + kHypersphereOffset, kDefaultRes);
207
+ auto hypertetraVertices = generateGeometry(base + kHypertetraOffset, kDefaultRes);
208
+
209
+ EXPECT_GT(baseVertices.size(), 0u);
210
+ EXPECT_GT(hypersphereVertices.size(), 0u);
211
+ EXPECT_GT(hypertetraVertices.size(), 0u);
212
+ }
213
+ }
@@ -0,0 +1,494 @@
1
+ /**
2
+ * Mat4x4_test.cpp - Unit tests for Mat4x4 4x4 matrix class
3
+ *
4
+ * Tests identity construction, matrix-vector multiplication, matrix-matrix
5
+ * multiplication, determinant, transpose, rotation factories, and inversion.
6
+ */
7
+
8
+ #include <gtest/gtest.h>
9
+ #include "math/Mat4x4.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
+ // Construction
22
+ // ============================================================================
23
+
24
+ TEST(Mat4x4, DefaultConstructorIsIdentity) {
25
+ Mat4x4 m;
26
+ EXPECT_TRUE(m.isIdentity());
27
+ }
28
+
29
+ TEST(Mat4x4, IdentityFactory) {
30
+ Mat4x4 m = Mat4x4::identity();
31
+ EXPECT_TRUE(m.isIdentity());
32
+
33
+ // Verify diagonal is 1, off-diagonal is 0
34
+ for (size_t r = 0; r < 4; ++r) {
35
+ for (size_t c = 0; c < 4; ++c) {
36
+ if (r == c) {
37
+ EXPECT_FLOAT_EQ(m.at(r, c), 1.0f);
38
+ } else {
39
+ EXPECT_FLOAT_EQ(m.at(r, c), 0.0f);
40
+ }
41
+ }
42
+ }
43
+ }
44
+
45
+ TEST(Mat4x4, ZeroFactory) {
46
+ Mat4x4 m = Mat4x4::zero();
47
+ for (size_t r = 0; r < 4; ++r) {
48
+ for (size_t c = 0; c < 4; ++c) {
49
+ EXPECT_FLOAT_EQ(m.at(r, c), 0.0f);
50
+ }
51
+ }
52
+ }
53
+
54
+ TEST(Mat4x4, DiagonalConstructor) {
55
+ Mat4x4 m(3.0f);
56
+ EXPECT_FLOAT_EQ(m.at(0, 0), 3.0f);
57
+ EXPECT_FLOAT_EQ(m.at(1, 1), 3.0f);
58
+ EXPECT_FLOAT_EQ(m.at(2, 2), 3.0f);
59
+ EXPECT_FLOAT_EQ(m.at(3, 3), 3.0f);
60
+ EXPECT_FLOAT_EQ(m.at(0, 1), 0.0f);
61
+ EXPECT_FLOAT_EQ(m.at(1, 0), 0.0f);
62
+ }
63
+
64
+ TEST(Mat4x4, ColumnConstructor) {
65
+ Vec4 c0(1.0f, 0.0f, 0.0f, 0.0f);
66
+ Vec4 c1(0.0f, 2.0f, 0.0f, 0.0f);
67
+ Vec4 c2(0.0f, 0.0f, 3.0f, 0.0f);
68
+ Vec4 c3(0.0f, 0.0f, 0.0f, 4.0f);
69
+ Mat4x4 m(c0, c1, c2, c3);
70
+ EXPECT_FLOAT_EQ(m.at(0, 0), 1.0f);
71
+ EXPECT_FLOAT_EQ(m.at(1, 1), 2.0f);
72
+ EXPECT_FLOAT_EQ(m.at(2, 2), 3.0f);
73
+ EXPECT_FLOAT_EQ(m.at(3, 3), 4.0f);
74
+ }
75
+
76
+ // ============================================================================
77
+ // Element Access
78
+ // ============================================================================
79
+
80
+ TEST(Mat4x4, ColumnAccess) {
81
+ Mat4x4 m = Mat4x4::identity();
82
+ Vec4 col0 = m.column(0);
83
+ EXPECT_FLOAT_EQ(col0.x, 1.0f);
84
+ EXPECT_FLOAT_EQ(col0.y, 0.0f);
85
+ EXPECT_FLOAT_EQ(col0.z, 0.0f);
86
+ EXPECT_FLOAT_EQ(col0.w, 0.0f);
87
+ }
88
+
89
+ TEST(Mat4x4, RowAccess) {
90
+ Mat4x4 m = Mat4x4::identity();
91
+ Vec4 row0 = m.row(0);
92
+ EXPECT_FLOAT_EQ(row0.x, 1.0f);
93
+ EXPECT_FLOAT_EQ(row0.y, 0.0f);
94
+ EXPECT_FLOAT_EQ(row0.z, 0.0f);
95
+ EXPECT_FLOAT_EQ(row0.w, 0.0f);
96
+ }
97
+
98
+ TEST(Mat4x4, SetColumn) {
99
+ Mat4x4 m = Mat4x4::zero();
100
+ m.setColumn(1, Vec4(10.0f, 20.0f, 30.0f, 40.0f));
101
+ EXPECT_FLOAT_EQ(m.at(0, 1), 10.0f);
102
+ EXPECT_FLOAT_EQ(m.at(1, 1), 20.0f);
103
+ EXPECT_FLOAT_EQ(m.at(2, 1), 30.0f);
104
+ EXPECT_FLOAT_EQ(m.at(3, 1), 40.0f);
105
+ }
106
+
107
+ TEST(Mat4x4, SetRow) {
108
+ Mat4x4 m = Mat4x4::zero();
109
+ m.setRow(2, Vec4(10.0f, 20.0f, 30.0f, 40.0f));
110
+ EXPECT_FLOAT_EQ(m.at(2, 0), 10.0f);
111
+ EXPECT_FLOAT_EQ(m.at(2, 1), 20.0f);
112
+ EXPECT_FLOAT_EQ(m.at(2, 2), 30.0f);
113
+ EXPECT_FLOAT_EQ(m.at(2, 3), 40.0f);
114
+ }
115
+
116
+ // ============================================================================
117
+ // Matrix-Vector Multiplication
118
+ // ============================================================================
119
+
120
+ TEST(Mat4x4, IdentityTimesVectorIsVector) {
121
+ Mat4x4 m = Mat4x4::identity();
122
+ Vec4 v(1.0f, 2.0f, 3.0f, 4.0f);
123
+ Vec4 result = m * v;
124
+ EXPECT_FLOAT_EQ(result.x, 1.0f);
125
+ EXPECT_FLOAT_EQ(result.y, 2.0f);
126
+ EXPECT_FLOAT_EQ(result.z, 3.0f);
127
+ EXPECT_FLOAT_EQ(result.w, 4.0f);
128
+ }
129
+
130
+ TEST(Mat4x4, ZeroMatrixTimesVectorIsZero) {
131
+ Mat4x4 m = Mat4x4::zero();
132
+ Vec4 v(1.0f, 2.0f, 3.0f, 4.0f);
133
+ Vec4 result = m * v;
134
+ EXPECT_FLOAT_EQ(result.x, 0.0f);
135
+ EXPECT_FLOAT_EQ(result.y, 0.0f);
136
+ EXPECT_FLOAT_EQ(result.z, 0.0f);
137
+ EXPECT_FLOAT_EQ(result.w, 0.0f);
138
+ }
139
+
140
+ TEST(Mat4x4, MultiplyVec4MatchesOperator) {
141
+ Mat4x4 m = Mat4x4::identity();
142
+ Vec4 v(1.0f, 2.0f, 3.0f, 4.0f);
143
+ Vec4 r1 = m * v;
144
+ Vec4 r2 = m.multiplyVec4(v);
145
+ EXPECT_FLOAT_EQ(r1.x, r2.x);
146
+ EXPECT_FLOAT_EQ(r1.y, r2.y);
147
+ EXPECT_FLOAT_EQ(r1.z, r2.z);
148
+ EXPECT_FLOAT_EQ(r1.w, r2.w);
149
+ }
150
+
151
+ TEST(Mat4x4, ScaleMatrixTimesVector) {
152
+ Mat4x4 m = Mat4x4::scale(2.0f, 3.0f, 4.0f, 5.0f);
153
+ Vec4 v(1.0f, 1.0f, 1.0f, 1.0f);
154
+ Vec4 result = m * v;
155
+ EXPECT_NEAR(result.x, 2.0f, kEpsilon);
156
+ EXPECT_NEAR(result.y, 3.0f, kEpsilon);
157
+ EXPECT_NEAR(result.z, 4.0f, kEpsilon);
158
+ EXPECT_NEAR(result.w, 5.0f, kEpsilon);
159
+ }
160
+
161
+ // ============================================================================
162
+ // Matrix-Matrix Multiplication
163
+ // ============================================================================
164
+
165
+ TEST(Mat4x4, IdentityTimesIdentityIsIdentity) {
166
+ Mat4x4 a = Mat4x4::identity();
167
+ Mat4x4 b = Mat4x4::identity();
168
+ Mat4x4 result = a * b;
169
+ EXPECT_TRUE(result.isIdentity());
170
+ }
171
+
172
+ TEST(Mat4x4, IdentityTimesMatrixIsMatrix) {
173
+ Mat4x4 m = Mat4x4::scale(2.0f);
174
+ Mat4x4 result = Mat4x4::identity() * m;
175
+ for (size_t r = 0; r < 4; ++r) {
176
+ for (size_t c = 0; c < 4; ++c) {
177
+ EXPECT_NEAR(result.at(r, c), m.at(r, c), kEpsilon);
178
+ }
179
+ }
180
+ }
181
+
182
+ TEST(Mat4x4, MatrixTimesIdentityIsMatrix) {
183
+ Mat4x4 m = Mat4x4::scale(3.0f);
184
+ Mat4x4 result = m * Mat4x4::identity();
185
+ for (size_t r = 0; r < 4; ++r) {
186
+ for (size_t c = 0; c < 4; ++c) {
187
+ EXPECT_NEAR(result.at(r, c), m.at(r, c), kEpsilon);
188
+ }
189
+ }
190
+ }
191
+
192
+ TEST(Mat4x4, ScaleMatrixMultiplication) {
193
+ Mat4x4 s2 = Mat4x4::scale(2.0f);
194
+ Mat4x4 s3 = Mat4x4::scale(3.0f);
195
+ Mat4x4 result = s2 * s3;
196
+ // scale(2) * scale(3) = scale(6)
197
+ EXPECT_NEAR(result.at(0, 0), 6.0f, kEpsilon);
198
+ EXPECT_NEAR(result.at(1, 1), 6.0f, kEpsilon);
199
+ EXPECT_NEAR(result.at(2, 2), 6.0f, kEpsilon);
200
+ EXPECT_NEAR(result.at(3, 3), 6.0f, kEpsilon);
201
+ }
202
+
203
+ TEST(Mat4x4, CompoundMultiplication) {
204
+ Mat4x4 m = Mat4x4::identity();
205
+ m *= Mat4x4::scale(2.0f);
206
+ EXPECT_NEAR(m.at(0, 0), 2.0f, kEpsilon);
207
+ EXPECT_NEAR(m.at(1, 1), 2.0f, kEpsilon);
208
+ }
209
+
210
+ // ============================================================================
211
+ // Scalar Operations
212
+ // ============================================================================
213
+
214
+ TEST(Mat4x4, ScalarMultiplication) {
215
+ Mat4x4 m = Mat4x4::identity();
216
+ Mat4x4 result = m * 3.0f;
217
+ EXPECT_NEAR(result.at(0, 0), 3.0f, kEpsilon);
218
+ EXPECT_NEAR(result.at(1, 1), 3.0f, kEpsilon);
219
+ EXPECT_NEAR(result.at(0, 1), 0.0f, kEpsilon);
220
+ }
221
+
222
+ TEST(Mat4x4, ScalarMultiplicationFreeFunction) {
223
+ Mat4x4 m = Mat4x4::identity();
224
+ Mat4x4 result = 5.0f * m;
225
+ EXPECT_NEAR(result.at(0, 0), 5.0f, kEpsilon);
226
+ }
227
+
228
+ // ============================================================================
229
+ // Addition and Subtraction
230
+ // ============================================================================
231
+
232
+ TEST(Mat4x4, MatrixAddition) {
233
+ Mat4x4 a = Mat4x4::identity();
234
+ Mat4x4 b = Mat4x4::identity();
235
+ Mat4x4 result = a + b;
236
+ EXPECT_NEAR(result.at(0, 0), 2.0f, kEpsilon);
237
+ EXPECT_NEAR(result.at(1, 1), 2.0f, kEpsilon);
238
+ EXPECT_NEAR(result.at(0, 1), 0.0f, kEpsilon);
239
+ }
240
+
241
+ TEST(Mat4x4, MatrixSubtraction) {
242
+ Mat4x4 a = Mat4x4::identity();
243
+ Mat4x4 b = Mat4x4::identity();
244
+ Mat4x4 result = a - b;
245
+ // Should be zero matrix
246
+ for (size_t r = 0; r < 4; ++r) {
247
+ for (size_t c = 0; c < 4; ++c) {
248
+ EXPECT_NEAR(result.at(r, c), 0.0f, kEpsilon);
249
+ }
250
+ }
251
+ }
252
+
253
+ // ============================================================================
254
+ // Determinant
255
+ // ============================================================================
256
+
257
+ TEST(Mat4x4, DeterminantOfIdentityIsOne) {
258
+ Mat4x4 m = Mat4x4::identity();
259
+ EXPECT_NEAR(m.determinant(), 1.0f, kEpsilon);
260
+ }
261
+
262
+ TEST(Mat4x4, DeterminantOfZeroIsZero) {
263
+ Mat4x4 m = Mat4x4::zero();
264
+ EXPECT_NEAR(m.determinant(), 0.0f, kEpsilon);
265
+ }
266
+
267
+ TEST(Mat4x4, DeterminantOfScaleMatrix) {
268
+ Mat4x4 m = Mat4x4::scale(2.0f, 3.0f, 4.0f, 5.0f);
269
+ // det(diag(2,3,4,5)) = 2*3*4*5 = 120
270
+ EXPECT_NEAR(m.determinant(), 120.0f, kEpsilon);
271
+ }
272
+
273
+ TEST(Mat4x4, DeterminantOfUniformScale) {
274
+ Mat4x4 m = Mat4x4::scale(2.0f);
275
+ // det(2*I) = 2^4 = 16
276
+ EXPECT_NEAR(m.determinant(), 16.0f, kEpsilon);
277
+ }
278
+
279
+ TEST(Mat4x4, DeterminantOfRotationIsOne) {
280
+ // Rotation matrices have determinant 1
281
+ Mat4x4 m = Mat4x4::rotationXY(0.5f);
282
+ EXPECT_NEAR(m.determinant(), 1.0f, kEpsilon);
283
+ }
284
+
285
+ // ============================================================================
286
+ // Transpose
287
+ // ============================================================================
288
+
289
+ TEST(Mat4x4, TransposeOfIdentityIsIdentity) {
290
+ Mat4x4 m = Mat4x4::identity();
291
+ Mat4x4 t = m.transposed();
292
+ EXPECT_TRUE(t.isIdentity());
293
+ }
294
+
295
+ TEST(Mat4x4, TransposeSwapsElements) {
296
+ Mat4x4 m = Mat4x4::zero();
297
+ m.at(0, 1) = 5.0f;
298
+ m.at(1, 0) = 10.0f;
299
+ Mat4x4 t = m.transposed();
300
+ EXPECT_FLOAT_EQ(t.at(0, 1), 10.0f);
301
+ EXPECT_FLOAT_EQ(t.at(1, 0), 5.0f);
302
+ }
303
+
304
+ TEST(Mat4x4, DoubleTransposeIsOriginal) {
305
+ Mat4x4 m = Mat4x4::rotationXY(0.7f);
306
+ Mat4x4 tt = m.transposed().transposed();
307
+ for (size_t r = 0; r < 4; ++r) {
308
+ for (size_t c = 0; c < 4; ++c) {
309
+ EXPECT_NEAR(tt.at(r, c), m.at(r, c), kEpsilon);
310
+ }
311
+ }
312
+ }
313
+
314
+ TEST(Mat4x4, TransposeInPlace) {
315
+ Mat4x4 m = Mat4x4::zero();
316
+ m.at(0, 2) = 7.0f;
317
+ m.transpose();
318
+ EXPECT_FLOAT_EQ(m.at(2, 0), 7.0f);
319
+ EXPECT_FLOAT_EQ(m.at(0, 2), 0.0f);
320
+ }
321
+
322
+ // ============================================================================
323
+ // Inverse
324
+ // ============================================================================
325
+
326
+ TEST(Mat4x4, InverseOfIdentityIsIdentity) {
327
+ Mat4x4 m = Mat4x4::identity();
328
+ Mat4x4 inv = m.inverse();
329
+ EXPECT_TRUE(inv.isIdentity());
330
+ }
331
+
332
+ TEST(Mat4x4, InverseOfScaleMatrix) {
333
+ Mat4x4 m = Mat4x4::scale(2.0f);
334
+ Mat4x4 inv = m.inverse();
335
+ // inverse of scale(2) should be scale(0.5)
336
+ EXPECT_NEAR(inv.at(0, 0), 0.5f, kEpsilon);
337
+ EXPECT_NEAR(inv.at(1, 1), 0.5f, kEpsilon);
338
+ EXPECT_NEAR(inv.at(2, 2), 0.5f, kEpsilon);
339
+ EXPECT_NEAR(inv.at(3, 3), 0.5f, kEpsilon);
340
+ }
341
+
342
+ TEST(Mat4x4, MatrixTimesInverseIsIdentity) {
343
+ Mat4x4 m = Mat4x4::rotationXY(0.8f);
344
+ Mat4x4 inv = m.inverse();
345
+ Mat4x4 product = m * inv;
346
+ EXPECT_TRUE(product.isIdentity(1e-4f));
347
+ }
348
+
349
+ // ============================================================================
350
+ // Rotation Matrices
351
+ // ============================================================================
352
+
353
+ TEST(Mat4x4, RotationXY_RotatesXtoY) {
354
+ Mat4x4 m = Mat4x4::rotationXY(kHalfPi);
355
+ Vec4 result = m * Vec4::unitX();
356
+ EXPECT_NEAR(result.x, 0.0f, kEpsilon);
357
+ EXPECT_NEAR(result.y, 1.0f, kEpsilon);
358
+ EXPECT_NEAR(result.z, 0.0f, kEpsilon);
359
+ EXPECT_NEAR(result.w, 0.0f, kEpsilon);
360
+ }
361
+
362
+ TEST(Mat4x4, RotationXZ_RotatesXtoZ) {
363
+ Mat4x4 m = Mat4x4::rotationXZ(kHalfPi);
364
+ Vec4 result = m * Vec4::unitX();
365
+ EXPECT_NEAR(result.x, 0.0f, kEpsilon);
366
+ EXPECT_NEAR(result.y, 0.0f, kEpsilon);
367
+ EXPECT_NEAR(result.z, 1.0f, kEpsilon);
368
+ EXPECT_NEAR(result.w, 0.0f, kEpsilon);
369
+ }
370
+
371
+ TEST(Mat4x4, RotationYZ_RotatesYtoZ) {
372
+ Mat4x4 m = Mat4x4::rotationYZ(kHalfPi);
373
+ Vec4 result = m * Vec4::unitY();
374
+ EXPECT_NEAR(result.x, 0.0f, kEpsilon);
375
+ EXPECT_NEAR(result.y, 0.0f, kEpsilon);
376
+ EXPECT_NEAR(result.z, 1.0f, kEpsilon);
377
+ EXPECT_NEAR(result.w, 0.0f, kEpsilon);
378
+ }
379
+
380
+ TEST(Mat4x4, RotationXW_RotatesXtoW) {
381
+ Mat4x4 m = Mat4x4::rotationXW(kHalfPi);
382
+ Vec4 result = m * Vec4::unitX();
383
+ EXPECT_NEAR(result.x, 0.0f, kEpsilon);
384
+ EXPECT_NEAR(result.y, 0.0f, kEpsilon);
385
+ EXPECT_NEAR(result.z, 0.0f, kEpsilon);
386
+ EXPECT_NEAR(result.w, 1.0f, kEpsilon);
387
+ }
388
+
389
+ TEST(Mat4x4, RotationYW_RotatesYtoW) {
390
+ Mat4x4 m = Mat4x4::rotationYW(kHalfPi);
391
+ Vec4 result = m * Vec4::unitY();
392
+ EXPECT_NEAR(result.x, 0.0f, kEpsilon);
393
+ EXPECT_NEAR(result.y, 0.0f, kEpsilon);
394
+ EXPECT_NEAR(result.z, 0.0f, kEpsilon);
395
+ EXPECT_NEAR(result.w, 1.0f, kEpsilon);
396
+ }
397
+
398
+ TEST(Mat4x4, RotationZW_RotatesZtoW) {
399
+ Mat4x4 m = Mat4x4::rotationZW(kHalfPi);
400
+ Vec4 result = m * Vec4::unitZ();
401
+ EXPECT_NEAR(result.x, 0.0f, kEpsilon);
402
+ EXPECT_NEAR(result.y, 0.0f, kEpsilon);
403
+ EXPECT_NEAR(result.z, 0.0f, kEpsilon);
404
+ EXPECT_NEAR(result.w, 1.0f, kEpsilon);
405
+ }
406
+
407
+ TEST(Mat4x4, RotationZeroAngleIsIdentity) {
408
+ Mat4x4 m = Mat4x4::rotationXY(0.0f);
409
+ EXPECT_TRUE(m.isIdentity());
410
+ }
411
+
412
+ TEST(Mat4x4, RotationFromAnglesAllZerosIsIdentity) {
413
+ Mat4x4 m = Mat4x4::rotationFromAngles(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
414
+ EXPECT_TRUE(m.isIdentity());
415
+ }
416
+
417
+ TEST(Mat4x4, RotationFromAnglesArrayOverload) {
418
+ std::array<float, 6> angles = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
419
+ Mat4x4 m = Mat4x4::rotationFromAngles(angles);
420
+ EXPECT_TRUE(m.isIdentity());
421
+ }
422
+
423
+ TEST(Mat4x4, RotationIsOrthogonal) {
424
+ Mat4x4 m = Mat4x4::rotationFromAngles(0.3f, 0.5f, 0.7f, 0.1f, 0.2f, 0.4f);
425
+ EXPECT_TRUE(m.isOrthogonal(1e-3f));
426
+ }
427
+
428
+ TEST(Mat4x4, RotationDeterminantIsOne) {
429
+ Mat4x4 m = Mat4x4::rotationFromAngles(0.3f, 0.5f, 0.7f, 0.1f, 0.2f, 0.4f);
430
+ EXPECT_NEAR(m.determinant(), 1.0f, kEpsilon);
431
+ }
432
+
433
+ TEST(Mat4x4, RotationPreservesVectorLength) {
434
+ Mat4x4 m = Mat4x4::rotationFromAngles(0.5f, 0.3f, 0.7f, 0.2f, 0.4f, 0.1f);
435
+ Vec4 v(1.0f, 2.0f, 3.0f, 4.0f);
436
+ Vec4 rotated = m * v;
437
+ EXPECT_NEAR(rotated.length(), v.length(), kEpsilon);
438
+ }
439
+
440
+ // ============================================================================
441
+ // Scale
442
+ // ============================================================================
443
+
444
+ TEST(Mat4x4, UniformScaleMatrix) {
445
+ Mat4x4 m = Mat4x4::scale(3.0f);
446
+ EXPECT_FLOAT_EQ(m.at(0, 0), 3.0f);
447
+ EXPECT_FLOAT_EQ(m.at(1, 1), 3.0f);
448
+ EXPECT_FLOAT_EQ(m.at(2, 2), 3.0f);
449
+ EXPECT_FLOAT_EQ(m.at(3, 3), 3.0f);
450
+ }
451
+
452
+ TEST(Mat4x4, ScaleFromVec4) {
453
+ Vec4 s(2.0f, 3.0f, 4.0f, 5.0f);
454
+ Mat4x4 m = Mat4x4::scale(s);
455
+ EXPECT_FLOAT_EQ(m.at(0, 0), 2.0f);
456
+ EXPECT_FLOAT_EQ(m.at(1, 1), 3.0f);
457
+ EXPECT_FLOAT_EQ(m.at(2, 2), 4.0f);
458
+ EXPECT_FLOAT_EQ(m.at(3, 3), 5.0f);
459
+ }
460
+
461
+ // ============================================================================
462
+ // Comparison
463
+ // ============================================================================
464
+
465
+ TEST(Mat4x4, EqualityOperator) {
466
+ Mat4x4 a = Mat4x4::identity();
467
+ Mat4x4 b = Mat4x4::identity();
468
+ EXPECT_TRUE(a == b);
469
+ EXPECT_FALSE(a != b);
470
+ }
471
+
472
+ TEST(Mat4x4, InequalityOperator) {
473
+ Mat4x4 a = Mat4x4::identity();
474
+ Mat4x4 b = Mat4x4::scale(2.0f);
475
+ EXPECT_FALSE(a == b);
476
+ EXPECT_TRUE(a != b);
477
+ }
478
+
479
+ // ============================================================================
480
+ // Data Pointer
481
+ // ============================================================================
482
+
483
+ TEST(Mat4x4, DataPointerAccess) {
484
+ Mat4x4 m = Mat4x4::identity();
485
+ const float* p = m.ptr();
486
+ // Column-major: first column is [1,0,0,0]
487
+ EXPECT_FLOAT_EQ(p[0], 1.0f);
488
+ EXPECT_FLOAT_EQ(p[1], 0.0f);
489
+ EXPECT_FLOAT_EQ(p[2], 0.0f);
490
+ EXPECT_FLOAT_EQ(p[3], 0.0f);
491
+ // Second column starts at index 4: [0,1,0,0]
492
+ EXPECT_FLOAT_EQ(p[4], 0.0f);
493
+ EXPECT_FLOAT_EQ(p[5], 1.0f);
494
+ }