@vib3code/sdk 2.0.1 → 2.0.3-canary.3349130
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.
- package/CHANGELOG.md +36 -0
- package/DOCS/AGENT_HARNESS_ARCHITECTURE.md +243 -0
- package/DOCS/CLI_ONBOARDING.md +1 -1
- package/DOCS/CROSS_SITE_DESIGN_PATTERNS.md +117 -0
- package/DOCS/EPIC_SCROLL_EVENTS.md +773 -0
- package/DOCS/HANDOFF_LANDING_PAGE.md +154 -0
- package/DOCS/HANDOFF_SDK_DEVELOPMENT.md +493 -0
- package/DOCS/MULTIVIZ_CHOREOGRAPHY_PATTERNS.md +937 -0
- package/DOCS/PRODUCT_STRATEGY.md +63 -0
- package/DOCS/README.md +103 -0
- package/DOCS/REFERENCE_SCROLL_ANALYSIS.md +97 -0
- package/DOCS/ROADMAP.md +111 -0
- package/DOCS/SCROLL_TIMELINE_v3.md +269 -0
- package/DOCS/SITE_REFACTOR_PLAN.md +100 -0
- package/DOCS/STATUS.md +24 -0
- package/DOCS/SYSTEM_INVENTORY.md +33 -30
- package/DOCS/VISUAL_ANALYSIS_CLICKERSS.md +85 -0
- package/DOCS/VISUAL_ANALYSIS_FACETAD.md +133 -0
- package/DOCS/VISUAL_ANALYSIS_SIMONE.md +95 -0
- package/DOCS/VISUAL_ANALYSIS_TABLESIDE.md +86 -0
- package/DOCS/{BLUEPRINT_EXECUTION_PLAN_2026-01-07.md → archive/BLUEPRINT_EXECUTION_PLAN_2026-01-07.md} +1 -1
- package/DOCS/{DEV_TRACK_ANALYSIS.md → archive/DEV_TRACK_ANALYSIS.md} +3 -0
- package/DOCS/{SYSTEM_AUDIT_2026-01-30.md → archive/SYSTEM_AUDIT_2026-01-30.md} +3 -0
- package/DOCS/{DEV_TRACK_SESSION_2026-01-31.md → dev-tracks/DEV_TRACK_SESSION_2026-01-31.md} +1 -1
- package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-06.md +231 -0
- package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-13.md +127 -0
- package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-15.md +142 -0
- package/DOCS/dev-tracks/README.md +10 -0
- package/README.md +26 -13
- package/cpp/CMakeLists.txt +236 -0
- package/cpp/bindings/embind.cpp +269 -0
- package/cpp/build.sh +129 -0
- package/cpp/geometry/Crystal.cpp +103 -0
- package/cpp/geometry/Fractal.cpp +136 -0
- package/cpp/geometry/GeometryGenerator.cpp +262 -0
- package/cpp/geometry/KleinBottle.cpp +71 -0
- package/cpp/geometry/Sphere.cpp +134 -0
- package/cpp/geometry/Tesseract.cpp +94 -0
- package/cpp/geometry/Tetrahedron.cpp +83 -0
- package/cpp/geometry/Torus.cpp +65 -0
- package/cpp/geometry/WarpFunctions.cpp +238 -0
- package/cpp/geometry/Wave.cpp +85 -0
- package/cpp/include/vib3_ffi.h +238 -0
- package/cpp/math/Mat4x4.cpp +409 -0
- package/cpp/math/Mat4x4.hpp +209 -0
- package/cpp/math/Projection.cpp +142 -0
- package/cpp/math/Projection.hpp +148 -0
- package/cpp/math/Rotor4D.cpp +322 -0
- package/cpp/math/Rotor4D.hpp +204 -0
- package/cpp/math/Vec4.cpp +303 -0
- package/cpp/math/Vec4.hpp +225 -0
- package/cpp/src/vib3_ffi.cpp +607 -0
- package/cpp/tests/Geometry_test.cpp +213 -0
- package/cpp/tests/Mat4x4_test.cpp +494 -0
- package/cpp/tests/Projection_test.cpp +298 -0
- package/cpp/tests/Rotor4D_test.cpp +423 -0
- package/cpp/tests/Vec4_test.cpp +489 -0
- package/package.json +40 -27
- package/src/agent/index.js +1 -3
- package/src/agent/mcp/MCPServer.js +1024 -7
- package/src/agent/mcp/index.js +1 -1
- package/src/agent/mcp/stdio-server.js +264 -0
- package/src/agent/mcp/tools.js +454 -0
- package/src/cli/index.js +374 -44
- package/src/core/CanvasManager.js +97 -204
- package/src/core/ErrorReporter.js +1 -1
- package/src/core/Parameters.js +1 -1
- package/src/core/VIB3Engine.js +93 -4
- package/src/core/VitalitySystem.js +53 -0
- package/src/core/index.js +18 -0
- package/src/core/renderers/FacetedRendererAdapter.js +10 -9
- package/src/core/renderers/HolographicRendererAdapter.js +13 -9
- package/src/core/renderers/QuantumRendererAdapter.js +11 -7
- package/src/creative/AestheticMapper.js +628 -0
- package/src/creative/ChoreographyPlayer.js +481 -0
- package/src/creative/index.js +11 -0
- package/src/export/TradingCardManager.js +3 -4
- package/src/export/index.js +11 -1
- package/src/faceted/FacetedSystem.js +241 -388
- package/src/games/glyph-war/GlyphWarVisualizer.js +641 -0
- package/src/holograms/HolographicVisualizer.js +29 -12
- package/src/holograms/RealHolographicSystem.js +194 -43
- package/src/math/index.js +7 -7
- package/src/polychora/PolychoraSystem.js +77 -0
- package/src/quantum/QuantumEngine.js +103 -66
- package/src/quantum/QuantumVisualizer.js +7 -2
- package/src/reactivity/index.js +3 -5
- package/src/render/LayerPresetManager.js +372 -0
- package/src/render/LayerReactivityBridge.js +344 -0
- package/src/render/LayerRelationshipGraph.js +610 -0
- package/src/render/MultiCanvasBridge.js +148 -25
- package/src/render/UnifiedRenderBridge.js +3 -0
- package/src/render/index.js +27 -2
- package/src/scene/index.js +4 -4
- package/src/shaders/faceted/faceted.frag.glsl +220 -80
- package/src/shaders/faceted/faceted.frag.wgsl +138 -97
- package/src/shaders/holographic/holographic.frag.glsl +28 -9
- package/src/shaders/holographic/holographic.frag.wgsl +107 -38
- package/src/shaders/quantum/quantum.frag.glsl +1 -0
- package/src/shaders/quantum/quantum.frag.wgsl +1 -1
- package/src/testing/ParallelTestFramework.js +2 -2
- package/src/viewer/GalleryUI.js +17 -0
- package/src/viewer/ViewerPortal.js +2 -2
- package/src/viewer/index.js +1 -1
- package/tools/headless-renderer.js +258 -0
- package/tools/shader-sync-verify.js +8 -4
- package/tools/site-analysis/all-reports.json +32 -0
- package/tools/site-analysis/combined-analysis.md +50 -0
- package/tools/site-analyzer.mjs +779 -0
- package/tools/visual-catalog/capture.js +276 -0
- package/tools/visual-catalog/composite.js +138 -0
- package/types/adaptive-sdk.d.ts +204 -5
- package/types/agent/cli.d.ts +78 -0
- package/types/agent/index.d.ts +18 -0
- package/types/agent/mcp.d.ts +87 -0
- package/types/agent/telemetry.d.ts +190 -0
- package/types/core/VIB3Engine.d.ts +26 -0
- package/types/core/index.d.ts +261 -0
- package/types/creative/AestheticMapper.d.ts +72 -0
- package/types/creative/ChoreographyPlayer.d.ts +96 -0
- package/types/creative/index.d.ts +17 -0
- package/types/export/index.d.ts +243 -0
- package/types/geometry/index.d.ts +164 -0
- package/types/math/index.d.ts +214 -0
- package/types/render/LayerPresetManager.d.ts +78 -0
- package/types/render/LayerReactivityBridge.d.ts +85 -0
- package/types/render/LayerRelationshipGraph.d.ts +174 -0
- package/types/render/index.d.ts +3 -0
- package/types/scene/index.d.ts +204 -0
- package/types/systems/index.d.ts +244 -0
- package/types/variations/index.d.ts +62 -0
- package/types/viewer/index.d.ts +225 -0
- /package/DOCS/{DEV_TRACK_PLAN_2026-01-07.md → archive/DEV_TRACK_PLAN_2026-01-07.md} +0 -0
- /package/DOCS/{SESSION_014_PLAN.md → archive/SESSION_014_PLAN.md} +0 -0
- /package/DOCS/{SESSION_LOG_2026-01-07.md → archive/SESSION_LOG_2026-01-07.md} +0 -0
- /package/DOCS/{STRATEGIC_BLUEPRINT_2026-01-07.md → archive/STRATEGIC_BLUEPRINT_2026-01-07.md} +0 -0
- /package/src/viewer/{ReactivityManager.js → ViewerInputHandler.js} +0 -0
|
@@ -0,0 +1,610 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LayerRelationshipGraph — Keystone-driven inter-layer parameter system for VIB3+
|
|
3
|
+
*
|
|
4
|
+
* Instead of blasting identical parameters to all 5 layers with static multipliers,
|
|
5
|
+
* this system designates one layer as the **keystone** (the driver) and derives
|
|
6
|
+
* every other layer's parameters through configurable **relationship functions**.
|
|
7
|
+
*
|
|
8
|
+
* Each relationship function receives the keystone's resolved parameters and returns
|
|
9
|
+
* the dependent layer's parameters. This allows layers to mirror, complement, chase,
|
|
10
|
+
* harmonize with, or react to the keystone — producing actual inter-layer dynamics
|
|
11
|
+
* instead of "same shader, different opacity."
|
|
12
|
+
*
|
|
13
|
+
* Supports per-layer shader assignment so layers aren't forced to run the same program.
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* const graph = new LayerRelationshipGraph({ keystone: 'content' });
|
|
17
|
+
* graph.setRelationship('background', 'echo'); // attenuated follower
|
|
18
|
+
* graph.setRelationship('shadow', 'complement'); // color complement, density inverse
|
|
19
|
+
* graph.setRelationship('highlight', 'harmonic'); // 2x density, shifted hue
|
|
20
|
+
* graph.setRelationship('accent', 'reactive'); // amplifies delta from keystone
|
|
21
|
+
*
|
|
22
|
+
* // Custom relationship
|
|
23
|
+
* graph.setRelationship('accent', (keystoneParams, time) => ({
|
|
24
|
+
* ...keystoneParams,
|
|
25
|
+
* hue: (keystoneParams.hue + 180) % 360,
|
|
26
|
+
* density: keystoneParams.density * 2.5,
|
|
27
|
+
* speed: keystoneParams.speed * 0.4,
|
|
28
|
+
* }));
|
|
29
|
+
*
|
|
30
|
+
* // Per-layer shader
|
|
31
|
+
* graph.setLayerShader('accent', 'holographic-glitch');
|
|
32
|
+
*
|
|
33
|
+
* // Resolve all layers for a frame
|
|
34
|
+
* const resolved = graph.resolveAll(keystoneParams, time);
|
|
35
|
+
* // => { background: {...}, shadow: {...}, content: keystoneParams, highlight: {...}, accent: {...} }
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/** @type {string[]} Standard layer order (back to front) */
|
|
39
|
+
const LAYER_ORDER = ['background', 'shadow', 'content', 'highlight', 'accent'];
|
|
40
|
+
|
|
41
|
+
// ============================================================================
|
|
42
|
+
// Preset Relationship Functions
|
|
43
|
+
// ============================================================================
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @typedef {function(Object, number): Object} RelationshipFn
|
|
47
|
+
* Takes (keystoneParams, time) and returns derived layer params.
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Echo — attenuated follower.
|
|
52
|
+
* Same parameters as keystone but scaled down. The most basic relationship.
|
|
53
|
+
* Background/shadow layers typically use this.
|
|
54
|
+
*
|
|
55
|
+
* @param {Object} config - Attenuation config
|
|
56
|
+
* @param {number} [config.opacity=0.3] - Opacity multiplier
|
|
57
|
+
* @param {number} [config.densityScale=0.5] - Density multiplier
|
|
58
|
+
* @param {number} [config.speedScale=0.3] - Speed multiplier
|
|
59
|
+
* @param {number} [config.intensityScale=0.4] - Intensity multiplier
|
|
60
|
+
* @returns {RelationshipFn}
|
|
61
|
+
*/
|
|
62
|
+
function echo(config = {}) {
|
|
63
|
+
const {
|
|
64
|
+
opacity = 0.3,
|
|
65
|
+
densityScale = 0.5,
|
|
66
|
+
speedScale = 0.3,
|
|
67
|
+
intensityScale = 0.4
|
|
68
|
+
} = config;
|
|
69
|
+
|
|
70
|
+
return (kp, _time) => ({
|
|
71
|
+
...kp,
|
|
72
|
+
layerOpacity: (kp.layerOpacity || 1.0) * opacity,
|
|
73
|
+
density: (kp.density || 1.0) * densityScale,
|
|
74
|
+
speed: (kp.speed || 1.0) * speedScale,
|
|
75
|
+
intensity: (kp.intensity || 0.5) * intensityScale
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Mirror — inverts spatial parameters relative to keystone.
|
|
81
|
+
* Rotation planes get negated, hue shifts 180°. Creates visual counterpoint.
|
|
82
|
+
*
|
|
83
|
+
* @param {Object} config
|
|
84
|
+
* @param {number} [config.opacity=0.5]
|
|
85
|
+
* @param {boolean} [config.invertRotation=true] - Negate 4D rotation angles
|
|
86
|
+
* @param {number} [config.hueShift=180] - Degrees to shift hue
|
|
87
|
+
* @returns {RelationshipFn}
|
|
88
|
+
*/
|
|
89
|
+
function mirror(config = {}) {
|
|
90
|
+
const {
|
|
91
|
+
opacity = 0.5,
|
|
92
|
+
invertRotation = true,
|
|
93
|
+
hueShift = 180
|
|
94
|
+
} = config;
|
|
95
|
+
|
|
96
|
+
return (kp, _time) => {
|
|
97
|
+
const result = { ...kp, layerOpacity: (kp.layerOpacity || 1.0) * opacity };
|
|
98
|
+
|
|
99
|
+
if (kp.hue !== undefined) {
|
|
100
|
+
result.hue = (kp.hue + hueShift) % 360;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (invertRotation) {
|
|
104
|
+
if (kp.rot4dXW !== undefined) result.rot4dXW = -kp.rot4dXW;
|
|
105
|
+
if (kp.rot4dYW !== undefined) result.rot4dYW = -kp.rot4dYW;
|
|
106
|
+
if (kp.rot4dZW !== undefined) result.rot4dZW = -kp.rot4dZW;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return result;
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Complement — color complement with inverted density curve.
|
|
115
|
+
* Hue opposite, density inverts around midpoint, chaos complementary.
|
|
116
|
+
* Good for shadow/background to create depth contrast.
|
|
117
|
+
*
|
|
118
|
+
* @param {Object} config
|
|
119
|
+
* @param {number} [config.opacity=0.4]
|
|
120
|
+
* @param {number} [config.densityPivot=1.0] - Density value to invert around
|
|
121
|
+
* @param {number} [config.chaosInvert=true] - Invert chaos (high → low, low → high)
|
|
122
|
+
* @returns {RelationshipFn}
|
|
123
|
+
*/
|
|
124
|
+
function complement(config = {}) {
|
|
125
|
+
const {
|
|
126
|
+
opacity = 0.4,
|
|
127
|
+
densityPivot = 1.0,
|
|
128
|
+
chaosInvert = true
|
|
129
|
+
} = config;
|
|
130
|
+
|
|
131
|
+
return (kp, _time) => {
|
|
132
|
+
const result = { ...kp, layerOpacity: (kp.layerOpacity || 1.0) * opacity };
|
|
133
|
+
|
|
134
|
+
if (kp.hue !== undefined) {
|
|
135
|
+
result.hue = (kp.hue + 180) % 360;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (kp.density !== undefined) {
|
|
139
|
+
// Invert around pivot: if keystone density is 0.3, complement is 1.7
|
|
140
|
+
result.density = Math.max(0.1, densityPivot * 2 - kp.density);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (chaosInvert && kp.chaos !== undefined) {
|
|
144
|
+
result.chaos = Math.max(0, 1.0 - kp.chaos);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return result;
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Harmonic — musical harmonic relationships.
|
|
153
|
+
* Density at integer multiples (2x, 3x), hue shifted by golden angle (~137.5°).
|
|
154
|
+
* Speed at fractional ratios. Creates visual "chords."
|
|
155
|
+
*
|
|
156
|
+
* @param {Object} config
|
|
157
|
+
* @param {number} [config.opacity=0.5]
|
|
158
|
+
* @param {number} [config.densityHarmonic=2] - Integer multiple for density
|
|
159
|
+
* @param {number} [config.speedRatio=0.5] - Fractional speed ratio
|
|
160
|
+
* @param {number} [config.hueAngle=137.508] - Golden angle for hue shift
|
|
161
|
+
* @returns {RelationshipFn}
|
|
162
|
+
*/
|
|
163
|
+
function harmonic(config = {}) {
|
|
164
|
+
const {
|
|
165
|
+
opacity = 0.5,
|
|
166
|
+
densityHarmonic = 2,
|
|
167
|
+
speedRatio = 0.5,
|
|
168
|
+
hueAngle = 137.508
|
|
169
|
+
} = config;
|
|
170
|
+
|
|
171
|
+
return (kp, _time) => {
|
|
172
|
+
const result = { ...kp, layerOpacity: (kp.layerOpacity || 1.0) * opacity };
|
|
173
|
+
|
|
174
|
+
if (kp.density !== undefined) {
|
|
175
|
+
result.density = kp.density * densityHarmonic;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (kp.speed !== undefined) {
|
|
179
|
+
result.speed = kp.speed * speedRatio;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (kp.hue !== undefined) {
|
|
183
|
+
result.hue = (kp.hue + hueAngle) % 360;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return result;
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Reactive — amplifies the delta from a baseline.
|
|
192
|
+
* Tracks parameter changes over time and exaggerates them.
|
|
193
|
+
* The more the keystone changes, the more reactive layers diverge.
|
|
194
|
+
*
|
|
195
|
+
* @param {Object} config
|
|
196
|
+
* @param {number} [config.opacity=0.6]
|
|
197
|
+
* @param {number} [config.gain=2.0] - How much to amplify deltas
|
|
198
|
+
* @param {number} [config.decay=0.95] - How fast accumulated delta decays per frame
|
|
199
|
+
* @returns {RelationshipFn}
|
|
200
|
+
*/
|
|
201
|
+
function reactive(config = {}) {
|
|
202
|
+
const {
|
|
203
|
+
opacity = 0.6,
|
|
204
|
+
gain = 2.0,
|
|
205
|
+
decay = 0.95
|
|
206
|
+
} = config;
|
|
207
|
+
|
|
208
|
+
let prevParams = null;
|
|
209
|
+
let accumulatedDelta = {};
|
|
210
|
+
|
|
211
|
+
return (kp, _time) => {
|
|
212
|
+
const result = { ...kp, layerOpacity: (kp.layerOpacity || 1.0) * opacity };
|
|
213
|
+
|
|
214
|
+
if (prevParams) {
|
|
215
|
+
for (const key of Object.keys(kp)) {
|
|
216
|
+
if (typeof kp[key] !== 'number') continue;
|
|
217
|
+
const delta = kp[key] - (prevParams[key] || 0);
|
|
218
|
+
accumulatedDelta[key] = ((accumulatedDelta[key] || 0) + delta * gain) * decay;
|
|
219
|
+
result[key] = kp[key] + (accumulatedDelta[key] || 0);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
prevParams = { ...kp };
|
|
224
|
+
return result;
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Chase — follows the keystone with a time delay (lerp toward keystone each frame).
|
|
230
|
+
* Creates a trailing/ghosting effect. Layers feel like they're "catching up."
|
|
231
|
+
*
|
|
232
|
+
* @param {Object} config
|
|
233
|
+
* @param {number} [config.opacity=0.5]
|
|
234
|
+
* @param {number} [config.lerpRate=0.1] - How fast to chase (0 = frozen, 1 = instant)
|
|
235
|
+
* @returns {RelationshipFn}
|
|
236
|
+
*/
|
|
237
|
+
function chase(config = {}) {
|
|
238
|
+
const {
|
|
239
|
+
opacity = 0.5,
|
|
240
|
+
lerpRate = 0.1
|
|
241
|
+
} = config;
|
|
242
|
+
|
|
243
|
+
let currentParams = null;
|
|
244
|
+
|
|
245
|
+
return (kp, _time) => {
|
|
246
|
+
if (!currentParams) {
|
|
247
|
+
currentParams = { ...kp };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Lerp each numeric parameter toward keystone
|
|
251
|
+
for (const key of Object.keys(kp)) {
|
|
252
|
+
if (typeof kp[key] !== 'number') continue;
|
|
253
|
+
const current = currentParams[key] !== undefined ? currentParams[key] : kp[key];
|
|
254
|
+
currentParams[key] = current + (kp[key] - current) * lerpRate;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return {
|
|
258
|
+
...currentParams,
|
|
259
|
+
layerOpacity: (kp.layerOpacity || 1.0) * opacity
|
|
260
|
+
};
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Registry of preset relationship factories.
|
|
266
|
+
* Each entry is a function that takes optional config and returns a RelationshipFn.
|
|
267
|
+
*/
|
|
268
|
+
const PRESET_REGISTRY = {
|
|
269
|
+
echo,
|
|
270
|
+
mirror,
|
|
271
|
+
complement,
|
|
272
|
+
harmonic,
|
|
273
|
+
reactive,
|
|
274
|
+
chase
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
// ============================================================================
|
|
278
|
+
// Default Layer Relationship Profiles
|
|
279
|
+
// ============================================================================
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Named profiles that configure the full 5-layer graph at once.
|
|
283
|
+
* Each profile sets the keystone and relationships for all layers.
|
|
284
|
+
*/
|
|
285
|
+
const PROFILES = {
|
|
286
|
+
/** Classic holographic — content drives, others echo/complement */
|
|
287
|
+
holographic: {
|
|
288
|
+
keystone: 'content',
|
|
289
|
+
relationships: {
|
|
290
|
+
background: { preset: 'echo', config: { opacity: 0.2, densityScale: 0.4, speedScale: 0.2, intensityScale: 0.3 } },
|
|
291
|
+
shadow: { preset: 'complement', config: { opacity: 0.4, densityPivot: 1.0 } },
|
|
292
|
+
highlight: { preset: 'harmonic', config: { opacity: 0.6, densityHarmonic: 1.5, speedRatio: 0.8, hueAngle: 60 } },
|
|
293
|
+
accent: { preset: 'reactive', config: { opacity: 0.3, gain: 2.5, decay: 0.92 } }
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
|
|
297
|
+
/** Mirror mode — highlight mirrors keystone, accent chases */
|
|
298
|
+
symmetry: {
|
|
299
|
+
keystone: 'content',
|
|
300
|
+
relationships: {
|
|
301
|
+
background: { preset: 'echo', config: { opacity: 0.15, densityScale: 0.3, speedScale: 0.15 } },
|
|
302
|
+
shadow: { preset: 'mirror', config: { opacity: 0.35 } },
|
|
303
|
+
highlight: { preset: 'mirror', config: { opacity: 0.5, hueShift: 120 } },
|
|
304
|
+
accent: { preset: 'chase', config: { opacity: 0.4, lerpRate: 0.05 } }
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
|
|
308
|
+
/** Harmonic chord — layers at musical intervals */
|
|
309
|
+
chord: {
|
|
310
|
+
keystone: 'content',
|
|
311
|
+
relationships: {
|
|
312
|
+
background: { preset: 'harmonic', config: { opacity: 0.2, densityHarmonic: 0.5, speedRatio: 0.25, hueAngle: 0 } },
|
|
313
|
+
shadow: { preset: 'harmonic', config: { opacity: 0.35, densityHarmonic: 1.5, speedRatio: 0.667, hueAngle: 137.508 } },
|
|
314
|
+
highlight: { preset: 'harmonic', config: { opacity: 0.55, densityHarmonic: 2, speedRatio: 0.5, hueAngle: 275.016 } },
|
|
315
|
+
accent: { preset: 'harmonic', config: { opacity: 0.3, densityHarmonic: 3, speedRatio: 0.333, hueAngle: 52.524 } }
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
|
|
319
|
+
/** Reactive storm — all layers react to keystone changes */
|
|
320
|
+
storm: {
|
|
321
|
+
keystone: 'content',
|
|
322
|
+
relationships: {
|
|
323
|
+
background: { preset: 'reactive', config: { opacity: 0.25, gain: 1.5, decay: 0.98 } },
|
|
324
|
+
shadow: { preset: 'reactive', config: { opacity: 0.4, gain: 2.0, decay: 0.95 } },
|
|
325
|
+
highlight: { preset: 'reactive', config: { opacity: 0.6, gain: 3.0, decay: 0.90 } },
|
|
326
|
+
accent: { preset: 'reactive', config: { opacity: 0.35, gain: 4.0, decay: 0.88 } }
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
|
|
330
|
+
/** Legacy — replicates the old static multiplier behavior exactly */
|
|
331
|
+
legacy: {
|
|
332
|
+
keystone: 'content',
|
|
333
|
+
relationships: {
|
|
334
|
+
background: { preset: 'echo', config: { opacity: 0.2, densityScale: 0.4, speedScale: 0.2, intensityScale: 0.4 } },
|
|
335
|
+
shadow: { preset: 'echo', config: { opacity: 0.4, densityScale: 0.8, speedScale: 0.3, intensityScale: 0.6 } },
|
|
336
|
+
highlight: { preset: 'echo', config: { opacity: 0.6, densityScale: 1.5, speedScale: 0.8, intensityScale: 0.8 } },
|
|
337
|
+
accent: { preset: 'echo', config: { opacity: 0.3, densityScale: 2.5, speedScale: 0.4, intensityScale: 0.5 } }
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
// ============================================================================
|
|
343
|
+
// LayerRelationshipGraph
|
|
344
|
+
// ============================================================================
|
|
345
|
+
|
|
346
|
+
export class LayerRelationshipGraph {
|
|
347
|
+
/**
|
|
348
|
+
* @param {Object} [config]
|
|
349
|
+
* @param {string} [config.keystone='content'] - The driver layer
|
|
350
|
+
* @param {string} [config.profile] - Named profile to load (holographic, symmetry, chord, storm, legacy)
|
|
351
|
+
*/
|
|
352
|
+
constructor(config = {}) {
|
|
353
|
+
/** @type {string} The keystone (driver) layer name */
|
|
354
|
+
this._keystone = config.keystone || 'content';
|
|
355
|
+
|
|
356
|
+
/** @type {Map<string, RelationshipFn>} layer name → resolved relationship function */
|
|
357
|
+
this._relationships = new Map();
|
|
358
|
+
|
|
359
|
+
/** @type {Map<string, string>} layer name → shader program name */
|
|
360
|
+
this._layerShaders = new Map();
|
|
361
|
+
|
|
362
|
+
/** @type {Map<string, {preset: string, config: Object}|null>} serializable config per layer */
|
|
363
|
+
this._relationshipConfigs = new Map();
|
|
364
|
+
|
|
365
|
+
/** @type {string|null} Currently active profile name */
|
|
366
|
+
this._activeProfile = null;
|
|
367
|
+
|
|
368
|
+
// Load profile if specified
|
|
369
|
+
if (config.profile && PROFILES[config.profile]) {
|
|
370
|
+
this.loadProfile(config.profile);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// ========================================================================
|
|
375
|
+
// Configuration
|
|
376
|
+
// ========================================================================
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Set the keystone (driver) layer.
|
|
380
|
+
* @param {string} layerName - One of LAYER_ORDER
|
|
381
|
+
*/
|
|
382
|
+
setKeystone(layerName) {
|
|
383
|
+
if (!LAYER_ORDER.includes(layerName)) {
|
|
384
|
+
throw new Error(`Invalid layer name: "${layerName}". Must be one of: ${LAYER_ORDER.join(', ')}`);
|
|
385
|
+
}
|
|
386
|
+
this._keystone = layerName;
|
|
387
|
+
this._activeProfile = null;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/** @returns {string} Current keystone layer name */
|
|
391
|
+
get keystone() {
|
|
392
|
+
return this._keystone;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Set the relationship for a dependent layer.
|
|
397
|
+
*
|
|
398
|
+
* @param {string} layerName - The dependent layer
|
|
399
|
+
* @param {string|RelationshipFn|{preset: string, config?: Object}} relationship
|
|
400
|
+
* - String: preset name ('echo', 'mirror', 'complement', 'harmonic', 'reactive', 'chase')
|
|
401
|
+
* - Function: custom (keystoneParams, time) => layerParams
|
|
402
|
+
* - Object: { preset: 'name', config: {...} }
|
|
403
|
+
*/
|
|
404
|
+
setRelationship(layerName, relationship) {
|
|
405
|
+
if (!LAYER_ORDER.includes(layerName)) {
|
|
406
|
+
throw new Error(`Invalid layer name: "${layerName}". Must be one of: ${LAYER_ORDER.join(', ')}`);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (layerName === this._keystone) {
|
|
410
|
+
throw new Error(`Cannot set relationship on keystone layer "${layerName}". Change the keystone first.`);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (typeof relationship === 'function') {
|
|
414
|
+
this._relationships.set(layerName, relationship);
|
|
415
|
+
this._relationshipConfigs.set(layerName, null); // custom fn, not serializable
|
|
416
|
+
} else if (typeof relationship === 'string') {
|
|
417
|
+
const factory = PRESET_REGISTRY[relationship];
|
|
418
|
+
if (!factory) {
|
|
419
|
+
throw new Error(`Unknown relationship preset: "${relationship}". Available: ${Object.keys(PRESET_REGISTRY).join(', ')}`);
|
|
420
|
+
}
|
|
421
|
+
this._relationships.set(layerName, factory());
|
|
422
|
+
this._relationshipConfigs.set(layerName, { preset: relationship, config: {} });
|
|
423
|
+
} else if (relationship && typeof relationship === 'object' && relationship.preset) {
|
|
424
|
+
const factory = PRESET_REGISTRY[relationship.preset];
|
|
425
|
+
if (!factory) {
|
|
426
|
+
throw new Error(`Unknown relationship preset: "${relationship.preset}". Available: ${Object.keys(PRESET_REGISTRY).join(', ')}`);
|
|
427
|
+
}
|
|
428
|
+
this._relationships.set(layerName, factory(relationship.config || {}));
|
|
429
|
+
this._relationshipConfigs.set(layerName, { preset: relationship.preset, config: relationship.config || {} });
|
|
430
|
+
} else {
|
|
431
|
+
throw new Error('Relationship must be a preset name (string), a function, or { preset, config }.');
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
this._activeProfile = null;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Set the shader program for a specific layer.
|
|
439
|
+
* Layers without an explicit shader use the default (same as keystone).
|
|
440
|
+
*
|
|
441
|
+
* @param {string} layerName
|
|
442
|
+
* @param {string} shaderName - Shader program name to use for this layer
|
|
443
|
+
*/
|
|
444
|
+
setLayerShader(layerName, shaderName) {
|
|
445
|
+
if (!LAYER_ORDER.includes(layerName)) {
|
|
446
|
+
throw new Error(`Invalid layer name: "${layerName}".`);
|
|
447
|
+
}
|
|
448
|
+
this._layerShaders.set(layerName, shaderName);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Get the shader name for a layer (null means use default).
|
|
453
|
+
* @param {string} layerName
|
|
454
|
+
* @returns {string|null}
|
|
455
|
+
*/
|
|
456
|
+
getLayerShader(layerName) {
|
|
457
|
+
return this._layerShaders.get(layerName) || null;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Load a named profile, configuring the full graph at once.
|
|
462
|
+
* @param {string} profileName - One of: holographic, symmetry, chord, storm, legacy
|
|
463
|
+
*/
|
|
464
|
+
loadProfile(profileName) {
|
|
465
|
+
const profile = PROFILES[profileName];
|
|
466
|
+
if (!profile) {
|
|
467
|
+
throw new Error(`Unknown profile: "${profileName}". Available: ${Object.keys(PROFILES).join(', ')}`);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
this._keystone = profile.keystone || 'content';
|
|
471
|
+
this._relationships.clear();
|
|
472
|
+
this._relationshipConfigs.clear();
|
|
473
|
+
|
|
474
|
+
for (const [layerName, rel] of Object.entries(profile.relationships)) {
|
|
475
|
+
const factory = PRESET_REGISTRY[rel.preset];
|
|
476
|
+
if (factory) {
|
|
477
|
+
this._relationships.set(layerName, factory(rel.config || {}));
|
|
478
|
+
this._relationshipConfigs.set(layerName, { preset: rel.preset, config: rel.config || {} });
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
this._activeProfile = profileName;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/** @returns {string|null} Active profile name, or null if custom */
|
|
486
|
+
get activeProfile() {
|
|
487
|
+
return this._activeProfile;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/** @returns {string[]} Available profile names */
|
|
491
|
+
static get profileNames() {
|
|
492
|
+
return Object.keys(PROFILES);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/** @returns {string[]} Available preset relationship names */
|
|
496
|
+
static get presetNames() {
|
|
497
|
+
return Object.keys(PRESET_REGISTRY);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// ========================================================================
|
|
501
|
+
// Resolution
|
|
502
|
+
// ========================================================================
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Resolve parameters for a single dependent layer.
|
|
506
|
+
*
|
|
507
|
+
* @param {Object} keystoneParams - The keystone layer's parameters
|
|
508
|
+
* @param {string} layerName - The layer to resolve
|
|
509
|
+
* @param {number} time - Current time (ms or frame count)
|
|
510
|
+
* @returns {Object} Resolved parameters for the layer
|
|
511
|
+
*/
|
|
512
|
+
resolve(keystoneParams, layerName, time) {
|
|
513
|
+
if (layerName === this._keystone) {
|
|
514
|
+
return { ...keystoneParams };
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const fn = this._relationships.get(layerName);
|
|
518
|
+
if (!fn) {
|
|
519
|
+
// No relationship defined — passthrough keystone params unchanged
|
|
520
|
+
return { ...keystoneParams };
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
return fn(keystoneParams, time);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Resolve parameters for all layers at once.
|
|
528
|
+
*
|
|
529
|
+
* @param {Object} keystoneParams - The keystone layer's parameters
|
|
530
|
+
* @param {number} time - Current time (ms or frame count)
|
|
531
|
+
* @returns {Object<string, Object>} Map of layerName → resolved params
|
|
532
|
+
*/
|
|
533
|
+
resolveAll(keystoneParams, time) {
|
|
534
|
+
const resolved = {};
|
|
535
|
+
for (const layerName of LAYER_ORDER) {
|
|
536
|
+
resolved[layerName] = this.resolve(keystoneParams, layerName, time);
|
|
537
|
+
}
|
|
538
|
+
return resolved;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// ========================================================================
|
|
542
|
+
// Serialization
|
|
543
|
+
// ========================================================================
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Export the graph configuration for saving (gallery, presets).
|
|
547
|
+
* Custom functions are exported as null (not serializable).
|
|
548
|
+
*
|
|
549
|
+
* @returns {Object} Serializable graph configuration
|
|
550
|
+
*/
|
|
551
|
+
exportConfig() {
|
|
552
|
+
const relationships = {};
|
|
553
|
+
for (const [layerName, config] of this._relationshipConfigs) {
|
|
554
|
+
relationships[layerName] = config; // null for custom fns
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const shaders = {};
|
|
558
|
+
for (const [layerName, shaderName] of this._layerShaders) {
|
|
559
|
+
shaders[layerName] = shaderName;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
return {
|
|
563
|
+
keystone: this._keystone,
|
|
564
|
+
profile: this._activeProfile,
|
|
565
|
+
relationships,
|
|
566
|
+
shaders
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Import a graph configuration (from gallery, presets).
|
|
572
|
+
*
|
|
573
|
+
* @param {Object} config - Previously exported config
|
|
574
|
+
*/
|
|
575
|
+
importConfig(config) {
|
|
576
|
+
if (!config) return;
|
|
577
|
+
|
|
578
|
+
if (config.profile && PROFILES[config.profile]) {
|
|
579
|
+
this.loadProfile(config.profile);
|
|
580
|
+
} else {
|
|
581
|
+
this._keystone = config.keystone || 'content';
|
|
582
|
+
this._relationships.clear();
|
|
583
|
+
this._relationshipConfigs.clear();
|
|
584
|
+
|
|
585
|
+
if (config.relationships) {
|
|
586
|
+
for (const [layerName, rel] of Object.entries(config.relationships)) {
|
|
587
|
+
if (rel && rel.preset) {
|
|
588
|
+
this.setRelationship(layerName, rel);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// Restore shader assignments
|
|
595
|
+
this._layerShaders.clear();
|
|
596
|
+
if (config.shaders) {
|
|
597
|
+
for (const [layerName, shaderName] of Object.entries(config.shaders)) {
|
|
598
|
+
this._layerShaders.set(layerName, shaderName);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// ============================================================================
|
|
605
|
+
// Exports
|
|
606
|
+
// ============================================================================
|
|
607
|
+
|
|
608
|
+
export { LAYER_ORDER, PROFILES, PRESET_REGISTRY };
|
|
609
|
+
export { echo, mirror, complement, harmonic, reactive, chase };
|
|
610
|
+
export default LayerRelationshipGraph;
|