@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.
Files changed (137) 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/DEV_TRACK_SESSION_2026-02-15.md +142 -0
  28. package/DOCS/dev-tracks/README.md +10 -0
  29. package/README.md +26 -13
  30. package/cpp/CMakeLists.txt +236 -0
  31. package/cpp/bindings/embind.cpp +269 -0
  32. package/cpp/build.sh +129 -0
  33. package/cpp/geometry/Crystal.cpp +103 -0
  34. package/cpp/geometry/Fractal.cpp +136 -0
  35. package/cpp/geometry/GeometryGenerator.cpp +262 -0
  36. package/cpp/geometry/KleinBottle.cpp +71 -0
  37. package/cpp/geometry/Sphere.cpp +134 -0
  38. package/cpp/geometry/Tesseract.cpp +94 -0
  39. package/cpp/geometry/Tetrahedron.cpp +83 -0
  40. package/cpp/geometry/Torus.cpp +65 -0
  41. package/cpp/geometry/WarpFunctions.cpp +238 -0
  42. package/cpp/geometry/Wave.cpp +85 -0
  43. package/cpp/include/vib3_ffi.h +238 -0
  44. package/cpp/math/Mat4x4.cpp +409 -0
  45. package/cpp/math/Mat4x4.hpp +209 -0
  46. package/cpp/math/Projection.cpp +142 -0
  47. package/cpp/math/Projection.hpp +148 -0
  48. package/cpp/math/Rotor4D.cpp +322 -0
  49. package/cpp/math/Rotor4D.hpp +204 -0
  50. package/cpp/math/Vec4.cpp +303 -0
  51. package/cpp/math/Vec4.hpp +225 -0
  52. package/cpp/src/vib3_ffi.cpp +607 -0
  53. package/cpp/tests/Geometry_test.cpp +213 -0
  54. package/cpp/tests/Mat4x4_test.cpp +494 -0
  55. package/cpp/tests/Projection_test.cpp +298 -0
  56. package/cpp/tests/Rotor4D_test.cpp +423 -0
  57. package/cpp/tests/Vec4_test.cpp +489 -0
  58. package/package.json +40 -27
  59. package/src/agent/index.js +1 -3
  60. package/src/agent/mcp/MCPServer.js +1024 -7
  61. package/src/agent/mcp/index.js +1 -1
  62. package/src/agent/mcp/stdio-server.js +264 -0
  63. package/src/agent/mcp/tools.js +454 -0
  64. package/src/cli/index.js +374 -44
  65. package/src/core/CanvasManager.js +97 -204
  66. package/src/core/ErrorReporter.js +1 -1
  67. package/src/core/Parameters.js +1 -1
  68. package/src/core/VIB3Engine.js +93 -4
  69. package/src/core/VitalitySystem.js +53 -0
  70. package/src/core/index.js +18 -0
  71. package/src/core/renderers/FacetedRendererAdapter.js +10 -9
  72. package/src/core/renderers/HolographicRendererAdapter.js +13 -9
  73. package/src/core/renderers/QuantumRendererAdapter.js +11 -7
  74. package/src/creative/AestheticMapper.js +628 -0
  75. package/src/creative/ChoreographyPlayer.js +481 -0
  76. package/src/creative/index.js +11 -0
  77. package/src/export/TradingCardManager.js +3 -4
  78. package/src/export/index.js +11 -1
  79. package/src/faceted/FacetedSystem.js +241 -388
  80. package/src/games/glyph-war/GlyphWarVisualizer.js +641 -0
  81. package/src/holograms/HolographicVisualizer.js +29 -12
  82. package/src/holograms/RealHolographicSystem.js +194 -43
  83. package/src/math/index.js +7 -7
  84. package/src/polychora/PolychoraSystem.js +77 -0
  85. package/src/quantum/QuantumEngine.js +103 -66
  86. package/src/quantum/QuantumVisualizer.js +7 -2
  87. package/src/reactivity/index.js +3 -5
  88. package/src/render/LayerPresetManager.js +372 -0
  89. package/src/render/LayerReactivityBridge.js +344 -0
  90. package/src/render/LayerRelationshipGraph.js +610 -0
  91. package/src/render/MultiCanvasBridge.js +148 -25
  92. package/src/render/UnifiedRenderBridge.js +3 -0
  93. package/src/render/index.js +27 -2
  94. package/src/scene/index.js +4 -4
  95. package/src/shaders/faceted/faceted.frag.glsl +220 -80
  96. package/src/shaders/faceted/faceted.frag.wgsl +138 -97
  97. package/src/shaders/holographic/holographic.frag.glsl +28 -9
  98. package/src/shaders/holographic/holographic.frag.wgsl +107 -38
  99. package/src/shaders/quantum/quantum.frag.glsl +1 -0
  100. package/src/shaders/quantum/quantum.frag.wgsl +1 -1
  101. package/src/testing/ParallelTestFramework.js +2 -2
  102. package/src/viewer/GalleryUI.js +17 -0
  103. package/src/viewer/ViewerPortal.js +2 -2
  104. package/src/viewer/index.js +1 -1
  105. package/tools/headless-renderer.js +258 -0
  106. package/tools/shader-sync-verify.js +8 -4
  107. package/tools/site-analysis/all-reports.json +32 -0
  108. package/tools/site-analysis/combined-analysis.md +50 -0
  109. package/tools/site-analyzer.mjs +779 -0
  110. package/tools/visual-catalog/capture.js +276 -0
  111. package/tools/visual-catalog/composite.js +138 -0
  112. package/types/adaptive-sdk.d.ts +204 -5
  113. package/types/agent/cli.d.ts +78 -0
  114. package/types/agent/index.d.ts +18 -0
  115. package/types/agent/mcp.d.ts +87 -0
  116. package/types/agent/telemetry.d.ts +190 -0
  117. package/types/core/VIB3Engine.d.ts +26 -0
  118. package/types/core/index.d.ts +261 -0
  119. package/types/creative/AestheticMapper.d.ts +72 -0
  120. package/types/creative/ChoreographyPlayer.d.ts +96 -0
  121. package/types/creative/index.d.ts +17 -0
  122. package/types/export/index.d.ts +243 -0
  123. package/types/geometry/index.d.ts +164 -0
  124. package/types/math/index.d.ts +214 -0
  125. package/types/render/LayerPresetManager.d.ts +78 -0
  126. package/types/render/LayerReactivityBridge.d.ts +85 -0
  127. package/types/render/LayerRelationshipGraph.d.ts +174 -0
  128. package/types/render/index.d.ts +3 -0
  129. package/types/scene/index.d.ts +204 -0
  130. package/types/systems/index.d.ts +244 -0
  131. package/types/variations/index.d.ts +62 -0
  132. package/types/viewer/index.d.ts +225 -0
  133. /package/DOCS/{DEV_TRACK_PLAN_2026-01-07.md → archive/DEV_TRACK_PLAN_2026-01-07.md} +0 -0
  134. /package/DOCS/{SESSION_014_PLAN.md → archive/SESSION_014_PLAN.md} +0 -0
  135. /package/DOCS/{SESSION_LOG_2026-01-07.md → archive/SESSION_LOG_2026-01-07.md} +0 -0
  136. /package/DOCS/{STRATEGIC_BLUEPRINT_2026-01-07.md → archive/STRATEGIC_BLUEPRINT_2026-01-07.md} +0 -0
  137. /package/src/viewer/{ReactivityManager.js → ViewerInputHandler.js} +0 -0
@@ -0,0 +1,344 @@
1
+ /**
2
+ * LayerReactivityBridge — Connect audio/tilt/input reactivity to layer relationship modulation
3
+ *
4
+ * Instead of reactivity inputs only affecting the keystone parameters (which all layers see),
5
+ * this bridge lets reactivity inputs *modulate the relationship configurations themselves*.
6
+ *
7
+ * For example:
8
+ * - Audio bass can increase the `gain` on reactive relationships (layers react more to beats)
9
+ * - Device tilt can shift the `hueAngle` on harmonic relationships (tilt changes color harmony)
10
+ * - Click intensity can spike the `lerpRate` on chase relationships (layers snap to attention)
11
+ *
12
+ * This creates a second level of dynamics: the relationships between layers change in real-time
13
+ * based on user input, not just the parameters themselves.
14
+ *
15
+ * Usage:
16
+ * const bridge = new LayerReactivityBridge(graph, presetManager);
17
+ * bridge.addModulation('audio', 'bass', 'shadow', 'gain', { scale: 2.0, baseline: 1.5 });
18
+ * bridge.addModulation('tilt', 'gamma', 'highlight', 'hueAngle', { scale: 50, baseline: 137.508 });
19
+ * bridge.update({ audio: { bass: 0.8, mid: 0.3 }, tilt: { gamma: 15 } });
20
+ *
21
+ * Pre-built profiles:
22
+ * bridge.loadModulationProfile('audioStorm');
23
+ * bridge.loadModulationProfile('tiltHarmonic');
24
+ */
25
+
26
+ import { PRESET_REGISTRY } from './LayerRelationshipGraph.js';
27
+
28
+ /**
29
+ * @typedef {object} ModulationMapping
30
+ * @property {string} source - Input source category ('audio', 'tilt', 'mouse', 'click', 'custom')
31
+ * @property {string} channel - Input channel within source ('bass', 'mid', 'high', 'alpha', 'beta', 'gamma', 'x', 'y')
32
+ * @property {string} layerName - Target layer to modulate
33
+ * @property {string} configKey - Relationship config key to modulate ('opacity', 'gain', 'hueAngle', 'lerpRate', etc.)
34
+ * @property {number} scale - How much the input affects the config value
35
+ * @property {number} baseline - Default value when input is 0
36
+ * @property {number} [min] - Clamp minimum
37
+ * @property {number} [max] - Clamp maximum
38
+ * @property {string} [mode='add'] - 'add' (baseline + input*scale) or 'multiply' (baseline * (1 + input*scale))
39
+ */
40
+
41
+ /**
42
+ * Pre-built modulation profiles that configure multiple mappings at once.
43
+ */
44
+ const MODULATION_PROFILES = {
45
+ /** Audio drives reactive gain and chase speed across all layers */
46
+ audioStorm: [
47
+ { source: 'audio', channel: 'bass', layerName: 'background', configKey: 'gain', scale: 3.0, baseline: 1.5, min: 0.5, max: 8.0, mode: 'add' },
48
+ { source: 'audio', channel: 'bass', layerName: 'shadow', configKey: 'gain', scale: 4.0, baseline: 2.0, min: 0.5, max: 10.0, mode: 'add' },
49
+ { source: 'audio', channel: 'mid', layerName: 'highlight', configKey: 'gain', scale: 3.0, baseline: 3.0, min: 1.0, max: 8.0, mode: 'add' },
50
+ { source: 'audio', channel: 'high', layerName: 'accent', configKey: 'gain', scale: 5.0, baseline: 4.0, min: 1.0, max: 12.0, mode: 'add' },
51
+ { source: 'audio', channel: 'bass', layerName: 'accent', configKey: 'opacity', scale: 0.4, baseline: 0.3, min: 0.1, max: 1.0, mode: 'add' }
52
+ ],
53
+
54
+ /** Tilt shifts harmonic hue angles and density ratios */
55
+ tiltHarmonic: [
56
+ { source: 'tilt', channel: 'gamma', layerName: 'shadow', configKey: 'hueAngle', scale: 3.0, baseline: 137.508, min: 0, max: 360, mode: 'add' },
57
+ { source: 'tilt', channel: 'beta', layerName: 'highlight', configKey: 'densityHarmonic', scale: 0.05, baseline: 2.0, min: 0.5, max: 5.0, mode: 'add' },
58
+ { source: 'tilt', channel: 'gamma', layerName: 'accent', configKey: 'hueAngle', scale: -2.0, baseline: 52.524, min: 0, max: 360, mode: 'add' },
59
+ { source: 'tilt', channel: 'alpha', layerName: 'background', configKey: 'speedScale', scale: 0.01, baseline: 0.2, min: 0.05, max: 1.0, mode: 'add' }
60
+ ],
61
+
62
+ /** Mouse position drives chase lerp rate and echo density */
63
+ mouseChase: [
64
+ { source: 'mouse', channel: 'x', layerName: 'shadow', configKey: 'lerpRate', scale: 0.2, baseline: 0.05, min: 0.01, max: 0.5, mode: 'add' },
65
+ { source: 'mouse', channel: 'y', layerName: 'accent', configKey: 'lerpRate', scale: 0.3, baseline: 0.1, min: 0.01, max: 0.8, mode: 'add' },
66
+ { source: 'mouse', channel: 'x', layerName: 'highlight', configKey: 'densityScale', scale: 1.5, baseline: 1.0, min: 0.2, max: 3.0, mode: 'add' }
67
+ ],
68
+
69
+ /** Audio beats pulse layer opacities */
70
+ audioPulse: [
71
+ { source: 'audio', channel: 'bass', layerName: 'background', configKey: 'opacity', scale: 0.3, baseline: 0.2, min: 0.05, max: 0.8, mode: 'add' },
72
+ { source: 'audio', channel: 'mid', layerName: 'shadow', configKey: 'opacity', scale: 0.3, baseline: 0.4, min: 0.1, max: 0.9, mode: 'add' },
73
+ { source: 'audio', channel: 'high', layerName: 'highlight', configKey: 'opacity', scale: 0.3, baseline: 0.6, min: 0.2, max: 1.0, mode: 'add' },
74
+ { source: 'audio', channel: 'bass', layerName: 'accent', configKey: 'opacity', scale: 0.5, baseline: 0.3, min: 0.1, max: 1.0, mode: 'add' }
75
+ ],
76
+
77
+ /** Combined: audio drives gains, tilt drives hue shifts */
78
+ fullReactive: [
79
+ // Audio → gain/opacity
80
+ { source: 'audio', channel: 'bass', layerName: 'shadow', configKey: 'gain', scale: 3.0, baseline: 2.0, min: 0.5, max: 8.0, mode: 'add' },
81
+ { source: 'audio', channel: 'mid', layerName: 'highlight', configKey: 'gain', scale: 2.5, baseline: 3.0, min: 1.0, max: 8.0, mode: 'add' },
82
+ { source: 'audio', channel: 'high', layerName: 'accent', configKey: 'gain', scale: 4.0, baseline: 4.0, min: 1.0, max: 12.0, mode: 'add' },
83
+ { source: 'audio', channel: 'bass', layerName: 'accent', configKey: 'opacity', scale: 0.4, baseline: 0.35, min: 0.1, max: 1.0, mode: 'add' },
84
+ // Tilt → hue/speed
85
+ { source: 'tilt', channel: 'gamma', layerName: 'shadow', configKey: 'hueAngle', scale: 2.0, baseline: 137.508, min: 0, max: 360, mode: 'add' },
86
+ { source: 'tilt', channel: 'beta', layerName: 'highlight', configKey: 'speedRatio', scale: 0.02, baseline: 0.5, min: 0.1, max: 2.0, mode: 'add' },
87
+ // Mouse → chase
88
+ { source: 'mouse', channel: 'x', layerName: 'accent', configKey: 'lerpRate', scale: 0.2, baseline: 0.1, min: 0.01, max: 0.5, mode: 'add' }
89
+ ]
90
+ };
91
+
92
+ export class LayerReactivityBridge {
93
+ /**
94
+ * @param {import('./LayerRelationshipGraph.js').LayerRelationshipGraph} graph - Live graph
95
+ * @param {import('./LayerPresetManager.js').LayerPresetManager} [presetManager] - Optional preset manager for save/restore
96
+ */
97
+ constructor(graph, presetManager = null) {
98
+ /** @type {import('./LayerRelationshipGraph.js').LayerRelationshipGraph} */
99
+ this._graph = graph;
100
+
101
+ /** @type {import('./LayerPresetManager.js').LayerPresetManager|null} */
102
+ this._presetManager = presetManager;
103
+
104
+ /** @type {ModulationMapping[]} Active modulation mappings */
105
+ this._mappings = [];
106
+
107
+ /** @type {string|null} Active modulation profile name */
108
+ this._activeProfile = null;
109
+
110
+ /** @type {boolean} Whether modulation is active */
111
+ this._active = false;
112
+ }
113
+
114
+ // ========================================================================
115
+ // Modulation Mappings
116
+ // ========================================================================
117
+
118
+ /**
119
+ * Add a single modulation mapping.
120
+ *
121
+ * @param {string} source - Input source ('audio', 'tilt', 'mouse', 'click', 'custom')
122
+ * @param {string} channel - Channel within source ('bass', 'mid', 'high', 'gamma', 'x', etc.)
123
+ * @param {string} layerName - Target layer
124
+ * @param {string} configKey - Relationship config key to modulate
125
+ * @param {object} [options]
126
+ * @param {number} [options.scale=1.0]
127
+ * @param {number} [options.baseline=0]
128
+ * @param {number} [options.min] - Clamp minimum
129
+ * @param {number} [options.max] - Clamp maximum
130
+ * @param {string} [options.mode='add'] - 'add' or 'multiply'
131
+ */
132
+ addModulation(source, channel, layerName, configKey, options = {}) {
133
+ this._mappings.push({
134
+ source,
135
+ channel,
136
+ layerName,
137
+ configKey,
138
+ scale: options.scale ?? 1.0,
139
+ baseline: options.baseline ?? 0,
140
+ min: options.min,
141
+ max: options.max,
142
+ mode: options.mode || 'add'
143
+ });
144
+ this._activeProfile = null;
145
+ }
146
+
147
+ /**
148
+ * Remove all modulation mappings for a specific layer.
149
+ *
150
+ * @param {string} layerName
151
+ * @returns {number} Number of mappings removed
152
+ */
153
+ removeModulationsForLayer(layerName) {
154
+ const before = this._mappings.length;
155
+ this._mappings = this._mappings.filter(m => m.layerName !== layerName);
156
+ return before - this._mappings.length;
157
+ }
158
+
159
+ /**
160
+ * Remove all modulation mappings.
161
+ */
162
+ clearModulations() {
163
+ this._mappings = [];
164
+ this._activeProfile = null;
165
+ }
166
+
167
+ /**
168
+ * Load a pre-built modulation profile.
169
+ *
170
+ * @param {string} profileName - One of: audioStorm, tiltHarmonic, mouseChase, audioPulse, fullReactive
171
+ * @returns {boolean} True if loaded
172
+ */
173
+ loadModulationProfile(profileName) {
174
+ const profile = MODULATION_PROFILES[profileName];
175
+ if (!profile) return false;
176
+
177
+ this._mappings = profile.map(m => ({ ...m }));
178
+ this._activeProfile = profileName;
179
+ return true;
180
+ }
181
+
182
+ /**
183
+ * Get available modulation profile names.
184
+ *
185
+ * @returns {string[]}
186
+ */
187
+ static get profileNames() {
188
+ return Object.keys(MODULATION_PROFILES);
189
+ }
190
+
191
+ /**
192
+ * Get the active modulation profile name.
193
+ *
194
+ * @returns {string|null}
195
+ */
196
+ get activeProfile() {
197
+ return this._activeProfile;
198
+ }
199
+
200
+ /**
201
+ * Get all current modulation mappings.
202
+ *
203
+ * @returns {ModulationMapping[]}
204
+ */
205
+ get mappings() {
206
+ return [...this._mappings];
207
+ }
208
+
209
+ // ========================================================================
210
+ // Activation
211
+ // ========================================================================
212
+
213
+ /**
214
+ * Enable modulation processing.
215
+ */
216
+ activate() {
217
+ this._active = true;
218
+ }
219
+
220
+ /**
221
+ * Disable modulation processing.
222
+ */
223
+ deactivate() {
224
+ this._active = false;
225
+ }
226
+
227
+ /**
228
+ * Check if modulation is active.
229
+ *
230
+ * @returns {boolean}
231
+ */
232
+ get isActive() {
233
+ return this._active;
234
+ }
235
+
236
+ // ========================================================================
237
+ // Update (called each frame or on input change)
238
+ // ========================================================================
239
+
240
+ /**
241
+ * Process input state and apply modulations to the layer relationship graph.
242
+ *
243
+ * Call this from the render loop or when input state changes.
244
+ *
245
+ * @param {object} inputState - Current input values
246
+ * @param {object} [inputState.audio] - { bass, mid, high, energy }
247
+ * @param {object} [inputState.tilt] - { alpha, beta, gamma }
248
+ * @param {object} [inputState.mouse] - { x, y, velocityX, velocityY }
249
+ * @param {object} [inputState.click] - { intensity }
250
+ * @param {object} [inputState.custom] - Custom named channels
251
+ * @returns {object} Map of layerName → applied config overrides
252
+ */
253
+ update(inputState) {
254
+ if (!this._active || this._mappings.length === 0) return {};
255
+
256
+ // Group mappings by layer to batch-apply
257
+ const overridesByLayer = {};
258
+
259
+ for (const mapping of this._mappings) {
260
+ const sourceData = inputState[mapping.source];
261
+ if (!sourceData) continue;
262
+
263
+ const inputValue = sourceData[mapping.channel];
264
+ if (inputValue === undefined || inputValue === null) continue;
265
+
266
+ // Compute modulated value
267
+ let value;
268
+ if (mapping.mode === 'multiply') {
269
+ value = mapping.baseline * (1 + inputValue * mapping.scale);
270
+ } else {
271
+ value = mapping.baseline + inputValue * mapping.scale;
272
+ }
273
+
274
+ // Clamp
275
+ if (mapping.min !== undefined) value = Math.max(mapping.min, value);
276
+ if (mapping.max !== undefined) value = Math.min(mapping.max, value);
277
+
278
+ // Accumulate overrides per layer
279
+ if (!overridesByLayer[mapping.layerName]) {
280
+ overridesByLayer[mapping.layerName] = {};
281
+ }
282
+ overridesByLayer[mapping.layerName][mapping.configKey] = value;
283
+ }
284
+
285
+ // Apply overrides to the graph via tune
286
+ for (const [layerName, overrides] of Object.entries(overridesByLayer)) {
287
+ const graphConfig = this._graph.exportConfig();
288
+ const currentRel = graphConfig.relationships[layerName];
289
+
290
+ if (currentRel && currentRel.preset) {
291
+ const factory = PRESET_REGISTRY[currentRel.preset];
292
+ if (factory) {
293
+ const newConfig = { ...(currentRel.config || {}), ...overrides };
294
+ this._graph.setRelationship(layerName, {
295
+ preset: currentRel.preset,
296
+ config: newConfig
297
+ });
298
+ }
299
+ }
300
+ }
301
+
302
+ return overridesByLayer;
303
+ }
304
+
305
+ // ========================================================================
306
+ // Serialization
307
+ // ========================================================================
308
+
309
+ /**
310
+ * Export the current modulation configuration.
311
+ *
312
+ * @returns {object}
313
+ */
314
+ exportConfig() {
315
+ return {
316
+ profile: this._activeProfile,
317
+ mappings: this._mappings.map(m => ({ ...m })),
318
+ active: this._active
319
+ };
320
+ }
321
+
322
+ /**
323
+ * Import a modulation configuration.
324
+ *
325
+ * @param {object} config
326
+ */
327
+ importConfig(config) {
328
+ if (!config) return;
329
+
330
+ if (config.profile && MODULATION_PROFILES[config.profile]) {
331
+ this.loadModulationProfile(config.profile);
332
+ } else if (Array.isArray(config.mappings)) {
333
+ this._mappings = config.mappings.map(m => ({ ...m }));
334
+ this._activeProfile = null;
335
+ }
336
+
337
+ if (config.active !== undefined) {
338
+ this._active = config.active;
339
+ }
340
+ }
341
+ }
342
+
343
+ export { MODULATION_PROFILES };
344
+ export default LayerReactivityBridge;