@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
@@ -9,7 +9,8 @@ uniform vec2 u_resolution;
9
9
  uniform float u_time;
10
10
  uniform vec2 u_mouse;
11
11
  uniform float u_geometry;
12
- uniform float u_density;
12
+ uniform float u_density; // Used by inline visualizer (scaled 0.3-2.5)
13
+ uniform float u_gridDensity; // Used by bridge/external (raw 5-100)
13
14
  uniform float u_speed;
14
15
  uniform vec3 u_color;
15
16
  uniform float u_intensity;
@@ -40,6 +41,9 @@ uniform float u_rot4dXW;
40
41
  uniform float u_rot4dYW;
41
42
  uniform float u_rot4dZW;
42
43
 
44
+ // EXHALE FEATURE: Breathing uniform
45
+ uniform float u_breath;
46
+
43
47
  // 6D rotation matrices - 3D space rotations (XY, XZ, YZ)
44
48
  mat4 rotateXY(float theta) {
45
49
  float c = cos(theta);
@@ -78,9 +82,12 @@ mat4 rotateZW(float theta) {
78
82
  return mat4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, c, -s, 0, 0, s, c);
79
83
  }
80
84
 
81
- // 4D to 3D projection
85
+ // 4D to 3D projection - BREATHING EFFECT
82
86
  vec3 project4Dto3D(vec4 p) {
83
- float w = 2.5 / (2.5 + p.w);
87
+ // Modulate projection distance with breath for "exhale" effect (expansion/contraction)
88
+ float baseDim = 2.5;
89
+ float dim = baseDim + u_breath * 0.5; // Expands on exhale
90
+ float w = dim / (dim + p.w);
84
91
  return vec3(p.x * w, p.y * w, p.z * w);
85
92
  }
86
93
 
@@ -227,10 +234,10 @@ float torusLattice(vec3 p, float gridSize) {
227
234
 
228
235
  float kleinLattice(vec3 p, float gridSize) {
229
236
  vec3 q = fract(p * gridSize);
230
- float ku = q.x * 2.0 * 3.14159;
231
- float kv = q.y * 2.0 * 3.14159;
232
- float kx = cos(ku) * (3.0 + cos(ku/2.0) * sin(kv) - sin(ku/2.0) * sin(2.0*kv));
233
- float klein = length(vec2(kx, q.z)) - 0.1;
237
+ float u = q.x * 2.0 * 3.14159;
238
+ float v = q.y * 2.0 * 3.14159;
239
+ float x = cos(u) * (3.0 + cos(u/2.0) * sin(v) - sin(u/2.0) * sin(2.0*v));
240
+ float klein = length(vec2(x, q.z)) - 0.1;
234
241
  return 1.0 - smoothstep(0.0, 0.05, abs(klein));
235
242
  }
236
243
 
@@ -345,8 +352,17 @@ void main() {
345
352
 
346
353
  float scrollDensityMod = 1.0 + u_gridDensityShift * 0.3;
347
354
  float audioDensityMod = 1.0 + u_audioDensityBoost * 0.5;
348
- // Controlled density calculation
349
- float baseDensity = u_density * u_roleDensity;
355
+
356
+ // SCALE FIX: Support raw gridDensity (5-100) or pre-scaled density (0.3-2.5)
357
+ float effectiveDensity = u_density;
358
+ if (u_gridDensity > 0.1) {
359
+ // Convert 5-100 to 0.3-2.5
360
+ effectiveDensity = 0.3 + (u_gridDensity - 5.0) / 95.0 * 2.2;
361
+ }
362
+
363
+ float breathDensityMod = 1.0 + u_breath * 0.1;
364
+ float baseDensity = effectiveDensity * u_roleDensity * breathDensityMod;
365
+
350
366
  float densityVariations = (u_densityVariation * 0.3 + (scrollDensityMod - 1.0) * 0.4 + (audioDensityMod - 1.0) * 0.2);
351
367
  float roleDensity = baseDensity + densityVariations;
352
368
 
@@ -357,6 +373,9 @@ void main() {
357
373
  vec3 baseColor = u_color;
358
374
  float latticeIntensity = lattice * u_intensity;
359
375
 
376
+ // Breathing glow effect
377
+ latticeIntensity *= (1.0 + u_breath * 0.2);
378
+
360
379
  // Multi-layer color composition for higher fidelity
361
380
  vec3 color = baseColor * (0.2 + latticeIntensity * 0.8);
362
381
 
@@ -1,6 +1,6 @@
1
1
  // VIB3+ Holographic System Fragment Shader (WGSL)
2
2
  // 5-layer glassmorphic audio-reactive effects
3
- // Port of the GLSL HolographicVisualizer shader
3
+ // Port of the GLSL HolographicVisualizer shader with Exhale feature
4
4
 
5
5
  struct VIB3Uniforms {
6
6
  time: f32,
@@ -28,7 +28,7 @@ struct VIB3Uniforms {
28
28
  high: f32,
29
29
  layerScale: f32,
30
30
  layerOpacity: f32,
31
- _pad1: f32,
31
+ breath: f32,
32
32
  layerColor: vec3<f32>,
33
33
  densityMult: f32,
34
34
  speedMult: f32,
@@ -91,42 +91,91 @@ fn apply6DRot(pos: vec4<f32>) -> vec4<f32> {
91
91
  return p;
92
92
  }
93
93
 
94
- // ========== 24 Geometry SDFs ==========
95
- fn baseGeometry(p: vec4<f32>, t: f32) -> f32 {
96
- if (t < 0.5) {
94
+ // ========== 4D to 3D Projection (Breathing) ==========
95
+ fn project4Dto3D_w(p: vec4<f32>) -> vec3<f32> {
96
+ // Modulate projection with breath
97
+ let baseDim = 2.5;
98
+ let dim = baseDim + u.breath * 0.5;
99
+ let w = dim / (dim + p.w);
100
+ return vec3<f32>(p.x * w, p.y * w, p.z * w);
101
+ }
102
+
103
+ // ========== Polytope Core Warp Functions ==========
104
+ fn warpHypersphereCore_w(p: vec3<f32>, geomIdx: i32) -> vec3<f32> {
105
+ let radius = length(p);
106
+ let morphBlend = clamp(u.morphFactor * 0.6 + 0.3, 0.0, 2.0);
107
+ let w = sin(radius * (1.3 + f32(geomIdx) * 0.12) + u.time * 0.0008 * u.speed)
108
+ * (0.4 + morphBlend * 0.45);
109
+
110
+ var p4d = vec4<f32>(p * (1.0 + morphBlend * 0.2), w);
111
+ p4d = apply6DRot(p4d); // Reuse rotation helper
112
+
113
+ let proj = project4Dto3D_w(p4d);
114
+ return mix(p, proj, clamp(0.45 + morphBlend * 0.35, 0.0, 1.0));
115
+ }
116
+
117
+ fn warpHypertetraCore_w(p: vec3<f32>, geomIdx: i32) -> vec3<f32> {
118
+ let c1 = normalize(vec3<f32>(1.0, 1.0, 1.0));
119
+ let c2 = normalize(vec3<f32>(-1.0, -1.0, 1.0));
120
+ let c3 = normalize(vec3<f32>(-1.0, 1.0, -1.0));
121
+ let c4 = normalize(vec3<f32>(1.0, -1.0, -1.0));
122
+
123
+ let morphBlend = clamp(u.morphFactor * 0.8 + 0.2, 0.0, 2.0);
124
+ let basisMix = dot(p, c1) * 0.14 + dot(p, c2) * 0.1 + dot(p, c3) * 0.08;
125
+ let w = sin(basisMix * 5.5 + u.time * 0.0009 * u.speed)
126
+ * cos(dot(p, c4) * 4.2 - u.time * 0.0007 * u.speed)
127
+ * (0.5 + morphBlend * 0.4);
128
+
129
+ let offset = vec3<f32>(dot(p, c1), dot(p, c2), dot(p, c3)) * 0.1 * morphBlend;
130
+ var p4d = vec4<f32>(p + offset, w);
131
+ p4d = apply6DRot(p4d);
132
+
133
+ let proj = project4Dto3D_w(p4d);
134
+
135
+ let planeInf = min(min(abs(dot(p, c1)), abs(dot(p, c2))),
136
+ min(abs(dot(p, c3)), abs(dot(p, c4))));
137
+ let blended = mix(p, proj, clamp(0.45 + morphBlend * 0.35, 0.0, 1.0));
138
+ return mix(blended, blended * (1.0 - planeInf * 0.55), 0.2 + morphBlend * 0.2);
139
+ }
140
+
141
+ fn applyCoreWarp_w(p: vec3<f32>, geomType: f32) -> vec3<f32> {
142
+ let totalBase = 8.0;
143
+ let coreFloat = floor(geomType / totalBase);
144
+ let coreIndex = i32(clamp(coreFloat, 0.0, 2.0));
145
+ let baseFloat = geomType - floor(geomType / totalBase) * totalBase;
146
+ let geomIdx = i32(clamp(floor(baseFloat + 0.5), 0.0, totalBase - 1.0));
147
+
148
+ if (coreIndex == 1) { return warpHypersphereCore_w(p, geomIdx); }
149
+ if (coreIndex == 2) { return warpHypertetraCore_w(p, geomIdx); }
150
+ return p;
151
+ }
152
+
153
+ // ========== 24 Geometry SDFs (Base 8) ==========
154
+ fn baseGeometry(p: vec3<f32>, t: f32) -> f32 {
155
+ let totalBase = 8.0;
156
+ let baseFloat = t - floor(t / totalBase) * totalBase;
157
+ // We use integer comparison here since WGSL doesn't like float switches
158
+ if (baseFloat < 0.5) { // Tetrahedron
97
159
  return max(max(max(abs(p.x + p.y) - p.z, abs(p.x - p.y) - p.z),
98
160
  abs(p.x + p.y) + p.z), abs(p.x - p.y) + p.z) / sqrt(3.0);
99
- } else if (t < 1.5) {
100
- let q = abs(p) - vec4<f32>(0.8);
101
- return length(max(q, vec4<f32>(0.0))) + min(max(max(max(q.x, q.y), q.z), q.w), 0.0);
102
- } else if (t < 2.5) {
161
+ } else if (baseFloat < 1.5) { // Hypercube (projected)
162
+ let q = abs(p) - vec3<f32>(0.8);
163
+ return length(max(q, vec3<f32>(0.0))) + min(max(max(q.x, q.y), q.z), 0.0);
164
+ } else if (baseFloat < 2.5) { // Sphere
103
165
  return length(p) - 1.0;
104
- } else if (t < 3.5) {
166
+ } else if (baseFloat < 3.5) { // Torus
105
167
  let t2 = vec2<f32>(length(p.xy) - 0.8, p.z);
106
168
  return length(t2) - 0.3;
107
- } else if (t < 4.5) {
169
+ } else if (baseFloat < 4.5) { // Klein
108
170
  let r = length(p.xy);
109
171
  return abs(r - 0.7) - 0.2 + sin(atan2(p.y, p.x) * 3.0 + p.z * 5.0) * 0.1;
110
- } else if (t < 5.5) {
172
+ } else if (baseFloat < 5.5) { // Fractal
111
173
  return length(p) - 0.8 + sin(p.x * 5.0) * sin(p.y * 5.0) * sin(p.z * 5.0) * 0.2;
112
- } else if (t < 6.5) {
174
+ } else if (baseFloat < 6.5) { // Wave
113
175
  return abs(p.z - sin(p.x * 5.0 + u.time) * cos(p.y * 5.0 + u.time) * 0.3) - 0.1;
114
- } else {
176
+ } else { // Crystal
115
177
  let q = abs(p);
116
- return max(max(max(q.x, q.y), q.z), q.w) - 0.8;
117
- }
118
- }
119
-
120
- fn geom(p: vec4<f32>, t: f32) -> f32 {
121
- if (t < 8.0) { return baseGeometry(p, t); }
122
- else if (t < 16.0) { return max(baseGeometry(p, t - 8.0), length(p) - 1.2); }
123
- else {
124
- let tf = max(max(max(
125
- abs(p.x + p.y) - p.z - p.w,
126
- abs(p.x - p.y) - p.z + p.w),
127
- abs(p.x + p.y) + p.z - p.w),
128
- abs(p.x - p.y) + p.z + p.w) / sqrt(4.0);
129
- return max(baseGeometry(p, t - 16.0), tf);
178
+ return max(max(q.x, q.y), q.z) - 0.8;
130
179
  }
131
180
  }
132
181
 
@@ -161,25 +210,45 @@ fn main(input: VertexOutput) -> @location(0) vec4<f32> {
161
210
  let density = u.densityMult;
162
211
  let spd = u.speedMult;
163
212
 
164
- // Create 4D point
165
- var pos = vec4<f32>(uv * 2.0 * density, sin(u.time * 0.3 * spd) * 0.5, cos(u.time * 0.2 * spd) * 0.5);
166
- pos = apply6DRot(pos);
167
- pos *= u.morphFactor;
168
- pos += vec4<f32>(sin(u.time * 0.1), cos(u.time * 0.15), sin(u.time * 0.12), cos(u.time * 0.18)) * u.chaos;
213
+ // Create 4D point (simplified to 3D base + W)
214
+ // SCALE FIX: Apply gridDensity scaling if densityMult is weak?
215
+ // WGSL density logic usually relies on densityMult passed by bridge.
216
+ // But let's apply the same scaling logic just in case:
217
+ var effectiveDensity = density;
218
+ if (u.gridDensity > 0.1 && density < 0.5) {
219
+ effectiveDensity = 0.3 + (u.gridDensity - 5.0) / 95.0 * 2.2;
220
+ }
221
+
222
+ var pos4d = vec4<f32>(uv * 2.0 * effectiveDensity, sin(u.time * 0.3 * spd) * 0.5, cos(u.time * 0.2 * spd) * 0.5);
223
+
224
+ // Rotate
225
+ pos4d = apply6DRot(pos4d);
226
+
227
+ // Project to 3D (with breath)
228
+ let pos3d = project4Dto3D_w(pos4d);
229
+
230
+ // Warp (Polytope logic)
231
+ var warpedPos = applyCoreWarp_w(pos3d, u.geometry);
232
+
233
+ // Apply morph/chaos to warped point
234
+ warpedPos *= u.morphFactor;
235
+ warpedPos += vec3<f32>(sin(u.time * 0.1), cos(u.time * 0.15), sin(u.time * 0.12)) * u.chaos;
236
+
237
+ // Geometry evaluation on warped 3D point
238
+ let dist = baseGeometry(warpedPos, u.geometry);
169
239
 
170
- // Geometry evaluation
171
- let dist = geom(pos, u.geometry);
172
240
  let edge = smoothstep(0.02, 0.0, abs(dist));
173
241
  let fill = smoothstep(0.1, 0.0, dist) * 0.3;
174
242
 
175
243
  // Color from HSL
176
- // colorShift is baked into layerColor; use hue directly
177
244
  let hueVal = u.hue / 360.0;
178
245
  let sat = clamp(u.saturation, 0.0, 1.0);
179
246
  let lightness = clamp(u.intensity, 0.2, 0.8);
180
247
  let color = hslToRgb(hueVal, sat, lightness);
181
248
 
182
- // Final alpha
183
- let alpha = (edge + fill) * u.intensity * u.layerOpacity;
249
+ // Final alpha - modulate with breath
250
+ let breathAlpha = 1.0 + u.breath * 0.2;
251
+ let alpha = (edge + fill) * u.intensity * u.layerOpacity * breathAlpha;
252
+
184
253
  return vec4<f32>(color * alpha, alpha);
185
254
  }
@@ -30,6 +30,7 @@ uniform float u_rot4dZW;
30
30
  uniform float u_mouseIntensity;
31
31
  uniform float u_clickIntensity;
32
32
  uniform float u_roleIntensity;
33
+ uniform float u_breath; // Vitality System exhale (0.0 - 1.0)
33
34
 
34
35
  // 6D rotation matrices - 3D space rotations (XY, XZ, YZ)
35
36
  mat4 rotateXY(float theta) {
@@ -28,7 +28,7 @@ struct VIB3Uniforms {
28
28
  high: f32,
29
29
  layerScale: f32,
30
30
  layerOpacity: f32,
31
- _pad1: f32,
31
+ breath: f32,
32
32
  layerColor: vec3<f32>,
33
33
  densityMult: f32,
34
34
  speedMult: f32,
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  export { ViewerPortal, ViewerMode, ProjectionMode } from './ViewerPortal.js';
8
- export { ReactivityManager } from './ReactivityManager.js';
8
+ export { ReactivityManager } from './ViewerInputHandler.js';
9
9
  export { CardBending, BendPreset } from './CardBending.js';
10
10
  export { TradingCardExporter, CardSize, FrameStyle, Rarity } from './TradingCardExporter.js';
11
11
  export { AudioReactivity, AudioSource, FrequencyBand } from './AudioReactivity.js';
@@ -0,0 +1,258 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * headless-renderer.js - VIB3+ Headless Frame Capture Utility
5
+ *
6
+ * Uses Puppeteer to render VIB3+ visualizations in a headless browser and
7
+ * capture screenshots. Designed for:
8
+ * - Agent visual feedback loops (capture → analyze → refine)
9
+ * - CI visual regression testing
10
+ * - Thumbnail generation for gallery entries
11
+ * - Offline batch rendering
12
+ *
13
+ * Usage:
14
+ * node tools/headless-renderer.js [options]
15
+ *
16
+ * Options:
17
+ * --url <url> Dev server URL (default: http://localhost:5173)
18
+ * --output <path> Output directory (default: ./renders)
19
+ * --width <px> Viewport width (default: 512)
20
+ * --height <px> Viewport height (default: 512)
21
+ * --params <json> JSON parameter object to apply
22
+ * --system <name> System: quantum|faceted|holographic
23
+ * --geometry <index> Geometry index 0-23
24
+ * --delay <ms> Wait time after parameter set (default: 500)
25
+ * --frames <count> Number of frames to capture (default: 1)
26
+ * --interval <ms> Interval between frames (default: 100)
27
+ * --format <type> png|jpeg|webp (default: png)
28
+ * --base64 Output base64 to stdout instead of file
29
+ *
30
+ * Examples:
31
+ * # Single frame with custom params
32
+ * node tools/headless-renderer.js --system quantum --geometry 11 --params '{"hue":200,"chaos":0.3}'
33
+ *
34
+ * # 10 frames of animation
35
+ * node tools/headless-renderer.js --frames 10 --interval 200 --output ./animation
36
+ *
37
+ * # Base64 output for piping to agents
38
+ * node tools/headless-renderer.js --base64 --params '{"hue":120}'
39
+ *
40
+ * @module tools/headless-renderer
41
+ * @version 1.0.0
42
+ */
43
+
44
+ import { existsSync, mkdirSync } from 'fs';
45
+ import { join, resolve } from 'path';
46
+
47
+ // Parse CLI arguments
48
+ function parseArgs(argv) {
49
+ const args = {
50
+ url: 'http://localhost:5173',
51
+ output: './renders',
52
+ width: 512,
53
+ height: 512,
54
+ params: null,
55
+ system: null,
56
+ geometry: null,
57
+ delay: 500,
58
+ frames: 1,
59
+ interval: 100,
60
+ format: 'png',
61
+ base64: false
62
+ };
63
+
64
+ for (let i = 2; i < argv.length; i++) {
65
+ const flag = argv[i];
66
+ const next = argv[i + 1];
67
+
68
+ switch (flag) {
69
+ case '--url': args.url = next; i++; break;
70
+ case '--output': args.output = next; i++; break;
71
+ case '--width': args.width = parseInt(next, 10); i++; break;
72
+ case '--height': args.height = parseInt(next, 10); i++; break;
73
+ case '--params': args.params = JSON.parse(next); i++; break;
74
+ case '--system': args.system = next; i++; break;
75
+ case '--geometry': args.geometry = parseInt(next, 10); i++; break;
76
+ case '--delay': args.delay = parseInt(next, 10); i++; break;
77
+ case '--frames': args.frames = parseInt(next, 10); i++; break;
78
+ case '--interval': args.interval = parseInt(next, 10); i++; break;
79
+ case '--format': args.format = next; i++; break;
80
+ case '--base64': args.base64 = true; break;
81
+ case '--help':
82
+ console.log('Usage: node tools/headless-renderer.js [options]');
83
+ console.log('See file header for full option list.');
84
+ process.exit(0);
85
+ }
86
+ }
87
+
88
+ return args;
89
+ }
90
+
91
+ async function main() {
92
+ const args = parseArgs(process.argv);
93
+
94
+ // Dynamic import puppeteer (devDependency)
95
+ let puppeteer;
96
+ try {
97
+ puppeteer = await import('puppeteer-core');
98
+ if (puppeteer.default) puppeteer = puppeteer.default;
99
+ } catch {
100
+ try {
101
+ puppeteer = await import('puppeteer');
102
+ if (puppeteer.default) puppeteer = puppeteer.default;
103
+ } catch {
104
+ console.error('Error: puppeteer or puppeteer-core is required.');
105
+ console.error('Install with: npm install puppeteer-core');
106
+ process.exit(1);
107
+ }
108
+ }
109
+
110
+ // Find Chrome/Chromium
111
+ const executablePath = findChromium();
112
+
113
+ console.log(`[headless-renderer] Launching browser...`);
114
+ const browser = await puppeteer.launch({
115
+ headless: 'new',
116
+ executablePath,
117
+ args: [
118
+ '--no-sandbox',
119
+ '--disable-setuid-sandbox',
120
+ '--disable-dev-shm-usage',
121
+ '--use-gl=swiftshader', // Software GPU for headless
122
+ `--window-size=${args.width},${args.height}`
123
+ ]
124
+ });
125
+
126
+ const page = await browser.newPage();
127
+ await page.setViewport({ width: args.width, height: args.height });
128
+
129
+ console.log(`[headless-renderer] Loading ${args.url}...`);
130
+ try {
131
+ await page.goto(args.url, { waitUntil: 'networkidle2', timeout: 30000 });
132
+ } catch (err) {
133
+ console.error(`Error: Could not load ${args.url}`);
134
+ console.error('Is the dev server running? Start it with: npm run dev');
135
+ await browser.close();
136
+ process.exit(1);
137
+ }
138
+
139
+ // Wait for VIB3+ engine to initialize
140
+ await page.waitForFunction(() => {
141
+ return typeof window.engine !== 'undefined' ||
142
+ document.querySelector('canvas') !== null;
143
+ }, { timeout: 10000 }).catch(() => {
144
+ console.warn('[headless-renderer] Warning: Engine not detected, capturing anyway');
145
+ });
146
+
147
+ // Apply system switch
148
+ if (args.system) {
149
+ console.log(`[headless-renderer] Switching to system: ${args.system}`);
150
+ await page.evaluate((system) => {
151
+ if (window.switchSystem) window.switchSystem(system);
152
+ else if (window.engine?.switchSystem) window.engine.switchSystem(system);
153
+ }, args.system);
154
+ await sleep(200);
155
+ }
156
+
157
+ // Apply geometry
158
+ if (args.geometry !== null) {
159
+ console.log(`[headless-renderer] Setting geometry: ${args.geometry}`);
160
+ await page.evaluate((geo) => {
161
+ if (window.selectGeometry) window.selectGeometry(geo);
162
+ else if (window.engine?.setParameter) window.engine.setParameter('geometry', geo);
163
+ }, args.geometry);
164
+ await sleep(100);
165
+ }
166
+
167
+ // Apply custom parameters
168
+ if (args.params) {
169
+ console.log(`[headless-renderer] Applying parameters:`, JSON.stringify(args.params));
170
+ await page.evaluate((params) => {
171
+ for (const [key, value] of Object.entries(params)) {
172
+ if (window.updateParameter) window.updateParameter(key, value);
173
+ else if (window.engine?.setParameter) window.engine.setParameter(key, value);
174
+ }
175
+ }, args.params);
176
+ }
177
+
178
+ // Wait for render to settle
179
+ console.log(`[headless-renderer] Waiting ${args.delay}ms for render...`);
180
+ await sleep(args.delay);
181
+
182
+ // Wait one animation frame
183
+ await page.evaluate(() => new Promise(r => requestAnimationFrame(r)));
184
+
185
+ // Capture frames
186
+ if (!args.base64 && !existsSync(args.output)) {
187
+ mkdirSync(args.output, { recursive: true });
188
+ }
189
+
190
+ for (let i = 0; i < args.frames; i++) {
191
+ const screenshot = await page.screenshot({
192
+ type: args.format === 'jpeg' ? 'jpeg' : 'png',
193
+ encoding: args.base64 ? 'base64' : 'binary',
194
+ fullPage: false
195
+ });
196
+
197
+ if (args.base64) {
198
+ // Output base64 to stdout (for agent consumption)
199
+ if (args.frames === 1) {
200
+ process.stdout.write(screenshot);
201
+ } else {
202
+ console.log(JSON.stringify({
203
+ frame: i,
204
+ format: args.format,
205
+ data: screenshot
206
+ }));
207
+ }
208
+ } else {
209
+ const ext = args.format === 'jpeg' ? 'jpg' : args.format;
210
+ const filename = args.frames === 1
211
+ ? `capture.${ext}`
212
+ : `frame_${String(i).padStart(4, '0')}.${ext}`;
213
+ const filepath = join(resolve(args.output), filename);
214
+
215
+ const fs = await import('fs');
216
+ fs.writeFileSync(filepath, screenshot);
217
+ console.log(`[headless-renderer] Saved: ${filepath}`);
218
+ }
219
+
220
+ // Wait between frames
221
+ if (i < args.frames - 1 && args.interval > 0) {
222
+ await sleep(args.interval);
223
+ await page.evaluate(() => new Promise(r => requestAnimationFrame(r)));
224
+ }
225
+ }
226
+
227
+ await browser.close();
228
+ console.log(`[headless-renderer] Done.`);
229
+ }
230
+
231
+ function sleep(ms) {
232
+ return new Promise(r => setTimeout(r, ms));
233
+ }
234
+
235
+ function findChromium() {
236
+ const candidates = [
237
+ '/usr/bin/chromium-browser',
238
+ '/usr/bin/chromium',
239
+ '/usr/bin/google-chrome',
240
+ '/usr/bin/google-chrome-stable',
241
+ '/snap/bin/chromium',
242
+ '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
243
+ 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
244
+ 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe'
245
+ ];
246
+
247
+ for (const path of candidates) {
248
+ if (existsSync(path)) return path;
249
+ }
250
+
251
+ // Let puppeteer figure it out
252
+ return undefined;
253
+ }
254
+
255
+ main().catch(err => {
256
+ console.error('[headless-renderer] Fatal error:', err.message);
257
+ process.exit(1);
258
+ });
@@ -34,7 +34,8 @@ export class ShaderSyncVerifier {
34
34
  'u_rot4dXY', 'u_rot4dXZ', 'u_rot4dYZ',
35
35
  'u_rot4dXW', 'u_rot4dYW', 'u_rot4dZW',
36
36
  'u_gridDensity', 'u_morphFactor', 'u_chaos',
37
- 'u_speed', 'u_hue', 'u_intensity', 'u_dimension'
37
+ 'u_speed', 'u_hue', 'u_intensity', 'u_dimension',
38
+ 'u_breath' // Exhale feature
38
39
  ],
39
40
  recommended: [
40
41
  'u_saturation', 'u_mouseIntensity', 'u_clickIntensity',
@@ -590,6 +591,7 @@ uniform float u_rot4dZW;
590
591
  uniform float u_mouseIntensity;
591
592
  uniform float u_clickIntensity;
592
593
  uniform float u_roleIntensity;
594
+ uniform float u_breath;
593
595
  void main() { gl_FragColor = vec4(0.0); }
594
596
  `;
595
597
 
@@ -620,6 +622,7 @@ uniform float u_clickIntensity;
620
622
  uniform float u_bass;
621
623
  uniform float u_mid;
622
624
  uniform float u_high;
625
+ uniform float u_breath;
623
626
  void main() { gl_FragColor = vec4(0.0); }
624
627
  `;
625
628
 
@@ -657,7 +660,7 @@ struct VIB3Uniforms {
657
660
  layerColor: vec3<f32>,
658
661
  densityMult: f32,
659
662
  speedMult: f32,
660
- _pad2: vec3<f32>,
663
+ breath: f32,
661
664
  };
662
665
  @group(0) @binding(0) var<uniform> u: VIB3Uniforms;
663
666
  `;
@@ -702,6 +705,7 @@ uniform float u_rot4dYZ;
702
705
  uniform float u_rot4dXW;
703
706
  uniform float u_rot4dYW;
704
707
  uniform float u_rot4dZW;
708
+ uniform float u_breath;
705
709
  void main() { gl_FragColor = vec4(0.0); }
706
710
  `;
707
711
 
@@ -739,7 +743,7 @@ struct VIB3Uniforms {
739
743
  layerColor: vec3<f32>,
740
744
  densityMult: f32,
741
745
  speedMult: f32,
742
- _pad2: vec3<f32>,
746
+ breath: f32,
743
747
  };
744
748
  @group(0) @binding(0) var<uniform> u: VIB3Uniforms;
745
749
  `;
@@ -778,7 +782,7 @@ struct VIB3Uniforms {
778
782
  layerColor: vec3<f32>,
779
783
  densityMult: f32,
780
784
  speedMult: f32,
781
- _pad2: vec3<f32>,
785
+ breath: f32,
782
786
  };
783
787
  @group(0) @binding(0) var<uniform> u: VIB3Uniforms;
784
788
  `;
@@ -0,0 +1,32 @@
1
+ [
2
+ {
3
+ "site": "clickerss",
4
+ "url": "https://www.clickerss.com/",
5
+ "error": "page.goto: net::ERR_TUNNEL_CONNECTION_FAILED at https://www.clickerss.com/\nCall log:\n - navigating to \"https://www.clickerss.com/\", waiting until \"networkidle\"\n"
6
+ },
7
+ {
8
+ "site": "facetad",
9
+ "url": "https://www.facetad.com/",
10
+ "error": "page.goto: net::ERR_TUNNEL_CONNECTION_FAILED at https://www.facetad.com/\nCall log:\n - navigating to \"https://www.facetad.com/\", waiting until \"networkidle\"\n"
11
+ },
12
+ {
13
+ "site": "wix-template-3630",
14
+ "url": "https://www.wix.com/website-template/view/html/3630",
15
+ "error": "page.goto: net::ERR_TUNNEL_CONNECTION_FAILED at https://www.wix.com/website-template/view/html/3630\nCall log:\n - navigating to \"https://www.wix.com/website-template/view/html/3630\", waiting until \"networkidle\"\n"
16
+ },
17
+ {
18
+ "site": "tableside",
19
+ "url": "https://www.tableside.com.au/",
20
+ "error": "page.goto: net::ERR_TUNNEL_CONNECTION_FAILED at https://www.tableside.com.au/\nCall log:\n - navigating to \"https://www.tableside.com.au/\", waiting until \"networkidle\"\n"
21
+ },
22
+ {
23
+ "site": "wix-studio-space",
24
+ "url": "https://www.wix.com/studio/design/inspiration/space",
25
+ "error": "page.goto: net::ERR_TUNNEL_CONNECTION_FAILED at https://www.wix.com/studio/design/inspiration/space\nCall log:\n - navigating to \"https://www.wix.com/studio/design/inspiration/space\", waiting until \"networkidle\"\n"
26
+ },
27
+ {
28
+ "site": "simone-webflow",
29
+ "url": "https://weare-simone.webflow.io/",
30
+ "error": "page.goto: net::ERR_TUNNEL_CONNECTION_FAILED at https://weare-simone.webflow.io/\nCall log:\n - navigating to \"https://weare-simone.webflow.io/\", waiting until \"networkidle\"\n"
31
+ }
32
+ ]