@vib3code/sdk 2.0.3-canary.91a95f3 → 2.0.3-canary.98b84da

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 (138) hide show
  1. package/DOCS/AGENT_HARNESS_ARCHITECTURE.md +2 -0
  2. package/DOCS/ANDROID_DEPLOYMENT.md +59 -0
  3. package/DOCS/ARCHITECTURE.md +1 -0
  4. package/DOCS/CI_TESTING.md +2 -0
  5. package/DOCS/CLI_ONBOARDING.md +2 -0
  6. package/DOCS/CONTROL_REFERENCE.md +2 -0
  7. package/DOCS/CROSS_SITE_DESIGN_PATTERNS.md +2 -0
  8. package/DOCS/ENV_SETUP.md +2 -0
  9. package/DOCS/EPIC_SCROLL_EVENTS.md +2 -0
  10. package/DOCS/EXPANSION_DESIGN.md +979 -0
  11. package/DOCS/EXPANSION_DESIGN_ULTRA.md +389 -0
  12. package/DOCS/EXPORT_FORMATS.md +2 -0
  13. package/DOCS/GPU_DISPOSAL_GUIDE.md +2 -0
  14. package/DOCS/HANDOFF_LANDING_PAGE.md +2 -0
  15. package/DOCS/HANDOFF_SDK_DEVELOPMENT.md +2 -0
  16. package/DOCS/LICENSING_TIERS.md +2 -0
  17. package/DOCS/MASTER_PLAN_2026-01-31.md +4 -2
  18. package/DOCS/MULTIVIZ_CHOREOGRAPHY_PATTERNS.md +3 -1
  19. package/DOCS/OBS_SETUP_GUIDE.md +2 -0
  20. package/DOCS/OPTIMIZATION_PLAN_MATH.md +119 -0
  21. package/DOCS/PRODUCT_STRATEGY.md +2 -0
  22. package/DOCS/PROJECT_SETUP.md +2 -0
  23. package/DOCS/README.md +5 -3
  24. package/DOCS/REFERENCE_SCROLL_ANALYSIS.md +2 -0
  25. package/DOCS/RENDERER_LIFECYCLE.md +2 -0
  26. package/DOCS/REPO_MANIFEST.md +2 -0
  27. package/DOCS/ROADMAP.md +2 -0
  28. package/DOCS/SCROLL_TIMELINE_v3.md +2 -0
  29. package/DOCS/SITE_REFACTOR_PLAN.md +2 -0
  30. package/DOCS/STATUS.md +2 -0
  31. package/DOCS/SYSTEM_INVENTORY.md +4 -2
  32. package/DOCS/TELEMETRY_EXPORTS.md +2 -0
  33. package/DOCS/VISUAL_ANALYSIS_CLICKERSS.md +2 -0
  34. package/DOCS/VISUAL_ANALYSIS_FACETAD.md +2 -0
  35. package/DOCS/VISUAL_ANALYSIS_SIMONE.md +2 -0
  36. package/DOCS/VISUAL_ANALYSIS_TABLESIDE.md +2 -0
  37. package/DOCS/WEBGPU_STATUS.md +121 -38
  38. package/DOCS/XR_BENCHMARKS.md +2 -0
  39. package/DOCS/archive/BLUEPRINT_EXECUTION_PLAN_2026-01-07.md +1 -34
  40. package/DOCS/archive/DEV_TRACK_ANALYSIS.md +1 -80
  41. package/DOCS/archive/DEV_TRACK_PLAN_2026-01-07.md +1 -42
  42. package/DOCS/archive/SESSION_014_PLAN.md +1 -195
  43. package/DOCS/archive/SESSION_LOG_2026-01-07.md +1 -56
  44. package/DOCS/archive/STRATEGIC_BLUEPRINT_2026-01-07.md +1 -72
  45. package/DOCS/archive/SYSTEM_AUDIT_2026-01-30.md +1 -741
  46. package/DOCS/archive/WEBGPU_STATUS_2026-02-15_STALE.md +1 -0
  47. package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-01-31.md +2 -0
  48. package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-06.md +2 -0
  49. package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-13.md +15 -0
  50. package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-15.md +144 -0
  51. package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-16.md +110 -0
  52. package/DOCS/dev-tracks/PERF_UPGRADE_2026-02-16.md +310 -0
  53. package/DOCS/dev-tracks/README.md +2 -0
  54. package/docs/webgpu-live.html +1 -1
  55. package/package.json +11 -4
  56. package/src/agent/index.js +1 -3
  57. package/src/agent/mcp/MCPServer.js +542 -188
  58. package/src/agent/mcp/index.js +1 -1
  59. package/src/agent/mcp/tools.js +132 -32
  60. package/src/cli/index.js +431 -47
  61. package/src/core/VIB3Engine.js +55 -3
  62. package/src/core/index.js +18 -0
  63. package/src/core/renderers/FacetedRendererAdapter.js +10 -9
  64. package/src/core/renderers/HolographicRendererAdapter.js +11 -7
  65. package/src/core/renderers/QuantumRendererAdapter.js +11 -7
  66. package/src/creative/index.js +11 -0
  67. package/src/experimental/GameLoop.js +72 -0
  68. package/src/experimental/LatticePhysics.js +100 -0
  69. package/src/experimental/LiveDirector.js +143 -0
  70. package/src/experimental/PlayerController4D.js +154 -0
  71. package/src/experimental/VIB3Actor.js +138 -0
  72. package/src/experimental/VIB3Compositor.js +117 -0
  73. package/src/experimental/VIB3Link.js +122 -0
  74. package/src/experimental/VIB3Orchestrator.js +146 -0
  75. package/src/experimental/VIB3Universe.js +109 -0
  76. package/src/experimental/demos/CrystalLabyrinth.js +202 -0
  77. package/src/export/SVGExporter.js +9 -5
  78. package/src/export/index.js +11 -1
  79. package/src/faceted/FacetedSystem.js +27 -10
  80. package/src/games/glyph-war/GlyphWarVisualizer.js +641 -0
  81. package/src/geometry/generators/Crystal.js +2 -2
  82. package/src/geometry/warp/HypersphereCore.js +53 -24
  83. package/src/holograms/HolographicVisualizer.js +58 -89
  84. package/src/holograms/RealHolographicSystem.js +126 -31
  85. package/src/math/Mat4x4.js +372 -140
  86. package/src/math/Projection.js +39 -4
  87. package/src/math/Rotor4D.js +157 -67
  88. package/src/math/Vec4.js +265 -111
  89. package/src/math/index.js +7 -7
  90. package/src/quantum/QuantumVisualizer.js +24 -20
  91. package/src/reactivity/index.js +3 -5
  92. package/src/render/LayerPresetManager.js +372 -0
  93. package/src/render/LayerReactivityBridge.js +344 -0
  94. package/src/render/LayerRelationshipGraph.js +610 -0
  95. package/src/render/MultiCanvasBridge.js +148 -25
  96. package/src/render/ShaderLoader.js +38 -0
  97. package/src/render/ShaderProgram.js +4 -4
  98. package/src/render/UnifiedRenderBridge.js +1 -1
  99. package/src/render/backends/WebGPUBackend.js +8 -4
  100. package/src/render/index.js +27 -2
  101. package/src/scene/Node4D.js +74 -24
  102. package/src/scene/index.js +4 -4
  103. package/src/shaders/common/geometry24.glsl +65 -0
  104. package/src/shaders/common/geometry24.wgsl +54 -0
  105. package/src/shaders/common/rotation4d.glsl +4 -4
  106. package/src/shaders/common/rotation4d.wgsl +2 -2
  107. package/src/shaders/common/uniforms.wgsl +15 -8
  108. package/src/shaders/faceted/faceted.frag.wgsl +19 -6
  109. package/src/shaders/holographic/holographic.frag.wgsl +7 -5
  110. package/src/shaders/quantum/quantum.frag.wgsl +7 -5
  111. package/src/testing/ParallelTestFramework.js +2 -2
  112. package/src/testing/ProjectionClass.test.js +38 -0
  113. package/src/ui/adaptive/renderers/webgpu/WebGPURenderer.ts +2 -2
  114. package/src/viewer/GalleryUI.js +17 -0
  115. package/src/viewer/ViewerPortal.js +2 -2
  116. package/tools/shader-sync-verify.js +6 -4
  117. package/tools/update_projection.py +109 -0
  118. package/types/adaptive-sdk.d.ts +204 -5
  119. package/types/agent/cli.d.ts +78 -0
  120. package/types/agent/index.d.ts +18 -0
  121. package/types/agent/mcp.d.ts +87 -0
  122. package/types/agent/telemetry.d.ts +190 -0
  123. package/types/core/VIB3Engine.d.ts +26 -0
  124. package/types/core/index.d.ts +261 -0
  125. package/types/creative/AestheticMapper.d.ts +72 -0
  126. package/types/creative/ChoreographyPlayer.d.ts +96 -0
  127. package/types/creative/index.d.ts +17 -0
  128. package/types/export/index.d.ts +243 -0
  129. package/types/geometry/index.d.ts +164 -0
  130. package/types/math/index.d.ts +214 -0
  131. package/types/render/LayerPresetManager.d.ts +78 -0
  132. package/types/render/LayerReactivityBridge.d.ts +85 -0
  133. package/types/render/LayerRelationshipGraph.d.ts +174 -0
  134. package/types/render/index.d.ts +3 -0
  135. package/types/scene/index.d.ts +204 -0
  136. package/types/systems/index.d.ts +244 -0
  137. package/types/variations/index.d.ts +62 -0
  138. package/types/viewer/index.d.ts +225 -0
@@ -6,6 +6,17 @@
6
6
 
7
7
  import { GeometryLibrary } from '../geometry/GeometryLibrary.js';
8
8
 
9
+ // Role-specific intensity values for 5-layer canvas architecture.
10
+ // IMPORTANT: Must stay in sync with shader epsilon comparisons in the fragment shader
11
+ // at the "LAYER-BY-LAYER COLOR SYSTEM" section (search for layerIndex).
12
+ const ROLE_INTENSITIES = {
13
+ 'background': 0.4,
14
+ 'shadow': 0.6,
15
+ 'content': 1.0,
16
+ 'highlight': 1.3,
17
+ 'accent': 1.6
18
+ };
19
+
9
20
  export class QuantumHolographicVisualizer {
10
21
  constructor(canvasIdOrElement, role, reactivity, variant) {
11
22
  this.canvas = (canvasIdOrElement instanceof HTMLCanvasElement)
@@ -14,6 +25,7 @@ export class QuantumHolographicVisualizer {
14
25
  this.role = role;
15
26
  this.reactivity = reactivity;
16
27
  this.variant = variant;
28
+ this._canvasLabel = typeof canvasIdOrElement === 'string' ? canvasIdOrElement : canvasIdOrElement?.id || 'unknown';
17
29
 
18
30
  // CRITICAL FIX: Define contextOptions as instance property to match SmartCanvasPool
19
31
  this.contextOptions = {
@@ -34,9 +46,9 @@ export class QuantumHolographicVisualizer {
34
46
  this.canvas.getContext('experimental-webgl', this.contextOptions);
35
47
 
36
48
  if (!this.gl) {
37
- console.error(`WebGL not supported for ${canvasId}`);
49
+ console.error(`WebGL not supported for ${this._canvasLabel}`);
38
50
  if (window.mobileDebug) {
39
- window.mobileDebug.log(`❌ ${canvasId}: WebGL context creation failed`);
51
+ window.mobileDebug.log(`❌ ${this._canvasLabel}: WebGL context creation failed`);
40
52
  }
41
53
  // Show user-friendly error instead of white screen
42
54
  this.showWebGLError();
@@ -44,7 +56,7 @@ export class QuantumHolographicVisualizer {
44
56
  } else {
45
57
  if (window.mobileDebug) {
46
58
  const version = this.gl.getParameter(this.gl.VERSION);
47
- window.mobileDebug.log(`✅ ${canvasId}: WebGL context created - ${version}`);
59
+ window.mobileDebug.log(`✅ ${this._canvasLabel}: WebGL context created - ${version}`);
48
60
  }
49
61
  }
50
62
 
@@ -59,15 +71,15 @@ export class QuantumHolographicVisualizer {
59
71
  this._onContextLost = (e) => {
60
72
  e.preventDefault();
61
73
  this._contextLost = true;
62
- console.warn(`WebGL context lost for ${canvasId}`);
74
+ console.warn(`WebGL context lost for ${this._canvasLabel}`);
63
75
  };
64
76
  this._onContextRestored = () => {
65
- console.log(`WebGL context restored for ${canvasId}`);
77
+ console.log(`WebGL context restored for ${this._canvasLabel}`);
66
78
  this._contextLost = false;
67
79
  try {
68
80
  this.init();
69
81
  } catch (err) {
70
- console.error(`Failed to reinit after context restore for ${canvasId}:`, err);
82
+ console.error(`Failed to reinit after context restore for ${this._canvasLabel}:`, err);
71
83
  }
72
84
  };
73
85
  this.canvas.addEventListener('webglcontextlost', this._onContextLost);
@@ -688,11 +700,12 @@ void main() {
688
700
 
689
701
  // LAYER-BY-LAYER COLOR SYSTEM with user hue/saturation/intensity controls
690
702
  // Determine canvas layer from role/variant (0=background, 1=shadow, 2=content, 3=highlight, 4=accent)
703
+ // Values must match ROLE_INTENSITIES in JS: bg=0.4, shadow=0.6, content=1.0, highlight=1.3, accent=1.6
691
704
  int layerIndex = 0;
692
- if (u_roleIntensity == 0.7) layerIndex = 1; // shadow layer
693
- else if (u_roleIntensity == 1.0) layerIndex = 2; // content layer
694
- else if (u_roleIntensity == 0.85) layerIndex = 3; // highlight layer
695
- else if (u_roleIntensity == 0.6) layerIndex = 4; // accent layer
705
+ if (abs(u_roleIntensity - 0.6) < 0.05) layerIndex = 1; // shadow layer
706
+ else if (abs(u_roleIntensity - 1.0) < 0.05) layerIndex = 2; // content layer
707
+ else if (abs(u_roleIntensity - 1.3) < 0.05) layerIndex = 3; // highlight layer
708
+ else if (abs(u_roleIntensity - 1.6) < 0.05) layerIndex = 4; // accent layer
696
709
 
697
710
  // Get layer-specific base color using user hue/saturation controls
698
711
  float colorTime = timeSpeed * 2.0 + value * 3.0;
@@ -1016,15 +1029,6 @@ void main() {
1016
1029
  this._renderParamsLogged = true;
1017
1030
  }
1018
1031
 
1019
- // Role-specific intensity for quantum effects
1020
- const roleIntensities = {
1021
- 'background': 0.4,
1022
- 'shadow': 0.6,
1023
- 'content': 1.0,
1024
- 'highlight': 1.3,
1025
- 'accent': 1.6
1026
- };
1027
-
1028
1032
  const time = Date.now() - this.startTime;
1029
1033
 
1030
1034
  // Set uniforms
@@ -1068,7 +1072,7 @@ void main() {
1068
1072
  this.gl.uniform1f(this.uniforms.rot4dZW, this.params.rot4dZW || 0.0);
1069
1073
  this.gl.uniform1f(this.uniforms.mouseIntensity, this.mouseIntensity);
1070
1074
  this.gl.uniform1f(this.uniforms.clickIntensity, this.clickIntensity);
1071
- this.gl.uniform1f(this.uniforms.roleIntensity, roleIntensities[this.role] || 1.0);
1075
+ this.gl.uniform1f(this.uniforms.roleIntensity, ROLE_INTENSITIES[this.role] || 1.0);
1072
1076
  this.gl.uniform1f(this.uniforms.breath, this.params.breath || 0.0);
1073
1077
 
1074
1078
  this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
@@ -23,10 +23,11 @@ export { ReactivityManager } from './ReactivityManager.js';
23
23
  /**
24
24
  * Create a pre-configured ReactivityManager with common settings
25
25
  */
26
- export function createReactivityManager(options = {}) {
26
+ export async function createReactivityManager(options = {}) {
27
27
  const { config, parameterUpdateFn } = options;
28
28
 
29
- const manager = new (await import('./ReactivityManager.js')).ReactivityManager(parameterUpdateFn);
29
+ const { ReactivityManager } = await import('./ReactivityManager.js');
30
+ const manager = new ReactivityManager(parameterUpdateFn);
30
31
 
31
32
  if (config) {
32
33
  manager.loadConfig(config);
@@ -39,7 +40,6 @@ export function createReactivityManager(options = {}) {
39
40
  * Create ReactivityConfig from a preset name
40
41
  */
41
42
  export function createPresetConfig(presetName) {
42
- const { ReactivityConfig } = require('./ReactivityConfig.js');
43
43
  const config = new ReactivityConfig();
44
44
 
45
45
  switch (presetName) {
@@ -89,5 +89,3 @@ export function createPresetConfig(presetName) {
89
89
 
90
90
  return config;
91
91
  }
92
-
93
- console.log('🎛️ VIB3+ Reactivity System loaded');
@@ -0,0 +1,372 @@
1
+ /**
2
+ * LayerPresetManager — Save, load, tune, and share layer relationship presets
3
+ *
4
+ * Works with LayerRelationshipGraph to persist custom layer configurations.
5
+ * Users and agents can:
6
+ * - Save the current graph state as a named preset
7
+ * - Load presets by name
8
+ * - Tune individual relationship parameters at runtime
9
+ * - Import/export preset libraries as JSON
10
+ * - List available presets (built-in + user)
11
+ *
12
+ * Storage: localStorage by default, configurable via constructor.
13
+ *
14
+ * Usage:
15
+ * const presets = new LayerPresetManager(graph);
16
+ * presets.save('my-look'); // save current graph
17
+ * presets.load('my-look'); // restore to graph
18
+ * presets.tune('shadow', { opacity: 0.6, gain: 3 }); // tweak a layer
19
+ * const lib = presets.exportLibrary(); // full JSON export
20
+ * presets.importLibrary(lib); // bulk import
21
+ */
22
+
23
+ import { LayerRelationshipGraph, PROFILES, PRESET_REGISTRY } from './LayerRelationshipGraph.js';
24
+
25
+ const STORAGE_KEY = 'vib3_layer_presets';
26
+
27
+ export class LayerPresetManager {
28
+ /**
29
+ * @param {LayerRelationshipGraph} graph - The live graph to save from / load into
30
+ * @param {object} [options]
31
+ * @param {Storage} [options.storage] - Storage backend (default: localStorage)
32
+ * @param {string} [options.storageKey] - Key prefix for storage
33
+ */
34
+ constructor(graph, options = {}) {
35
+ if (!(graph instanceof LayerRelationshipGraph)) {
36
+ throw new Error('LayerPresetManager requires a LayerRelationshipGraph instance');
37
+ }
38
+
39
+ /** @type {LayerRelationshipGraph} */
40
+ this._graph = graph;
41
+
42
+ /** @type {Storage|null} */
43
+ this._storage = options.storage !== undefined ? options.storage : this._getStorage();
44
+
45
+ /** @type {string} */
46
+ this._storageKey = options.storageKey || STORAGE_KEY;
47
+
48
+ /** @type {Map<string, object>} In-memory preset cache */
49
+ this._presets = new Map();
50
+
51
+ // Load persisted presets into memory
52
+ this._loadFromStorage();
53
+ }
54
+
55
+ /**
56
+ * Get localStorage safely (returns null in non-browser / restricted environments).
57
+ * @private
58
+ * @returns {Storage|null}
59
+ */
60
+ _getStorage() {
61
+ try {
62
+ if (typeof localStorage !== 'undefined') {
63
+ // Verify it works
64
+ localStorage.setItem('__vib3_test', '1');
65
+ localStorage.removeItem('__vib3_test');
66
+ return localStorage;
67
+ }
68
+ } catch (_e) { /* restricted */ }
69
+ return null;
70
+ }
71
+
72
+ /**
73
+ * Load all presets from storage into the in-memory cache.
74
+ * @private
75
+ */
76
+ _loadFromStorage() {
77
+ if (!this._storage) return;
78
+
79
+ try {
80
+ const raw = this._storage.getItem(this._storageKey);
81
+ if (raw) {
82
+ const data = JSON.parse(raw);
83
+ if (data && typeof data === 'object') {
84
+ for (const [name, preset] of Object.entries(data)) {
85
+ this._presets.set(name, preset);
86
+ }
87
+ }
88
+ }
89
+ } catch (e) {
90
+ console.warn('LayerPresetManager: failed to load presets from storage:', e.message);
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Persist the in-memory preset cache to storage.
96
+ * @private
97
+ */
98
+ _saveToStorage() {
99
+ if (!this._storage) return;
100
+
101
+ try {
102
+ const data = {};
103
+ for (const [name, preset] of this._presets) {
104
+ data[name] = preset;
105
+ }
106
+ this._storage.setItem(this._storageKey, JSON.stringify(data));
107
+ } catch (e) {
108
+ console.warn('LayerPresetManager: failed to save presets to storage:', e.message);
109
+ }
110
+ }
111
+
112
+ // ========================================================================
113
+ // Save / Load
114
+ // ========================================================================
115
+
116
+ /**
117
+ * Save the current graph state as a named preset.
118
+ *
119
+ * @param {string} name - Preset name (must not collide with built-in profile names)
120
+ * @param {object} [metadata] - Optional metadata (description, author, tags)
121
+ * @returns {object} The saved preset data
122
+ */
123
+ save(name, metadata = {}) {
124
+ if (!name || typeof name !== 'string') {
125
+ throw new Error('Preset name must be a non-empty string');
126
+ }
127
+
128
+ if (PROFILES[name]) {
129
+ throw new Error(`Cannot overwrite built-in profile "${name}". Choose a different name.`);
130
+ }
131
+
132
+ const config = this._graph.exportConfig();
133
+ const preset = {
134
+ name,
135
+ config,
136
+ metadata: {
137
+ ...metadata,
138
+ createdAt: new Date().toISOString(),
139
+ updatedAt: new Date().toISOString()
140
+ }
141
+ };
142
+
143
+ this._presets.set(name, preset);
144
+ this._saveToStorage();
145
+
146
+ return preset;
147
+ }
148
+
149
+ /**
150
+ * Load a preset by name into the live graph.
151
+ * Checks user presets first, then built-in profiles.
152
+ *
153
+ * @param {string} name - Preset or profile name
154
+ * @returns {boolean} True if loaded successfully
155
+ */
156
+ load(name) {
157
+ // User preset
158
+ const preset = this._presets.get(name);
159
+ if (preset && preset.config) {
160
+ this._graph.importConfig(preset.config);
161
+ return true;
162
+ }
163
+
164
+ // Built-in profile
165
+ if (PROFILES[name]) {
166
+ this._graph.loadProfile(name);
167
+ return true;
168
+ }
169
+
170
+ return false;
171
+ }
172
+
173
+ /**
174
+ * Delete a user preset.
175
+ *
176
+ * @param {string} name
177
+ * @returns {boolean} True if deleted
178
+ */
179
+ delete(name) {
180
+ if (PROFILES[name]) {
181
+ throw new Error(`Cannot delete built-in profile "${name}".`);
182
+ }
183
+
184
+ const existed = this._presets.delete(name);
185
+ if (existed) {
186
+ this._saveToStorage();
187
+ }
188
+ return existed;
189
+ }
190
+
191
+ /**
192
+ * Check if a preset exists (user or built-in).
193
+ *
194
+ * @param {string} name
195
+ * @returns {boolean}
196
+ */
197
+ has(name) {
198
+ return this._presets.has(name) || !!PROFILES[name];
199
+ }
200
+
201
+ /**
202
+ * Get a preset's data without loading it.
203
+ *
204
+ * @param {string} name
205
+ * @returns {object|null}
206
+ */
207
+ get(name) {
208
+ return this._presets.get(name) || null;
209
+ }
210
+
211
+ // ========================================================================
212
+ // List / Browse
213
+ // ========================================================================
214
+
215
+ /**
216
+ * List all available presets (user + built-in).
217
+ *
218
+ * @returns {{ user: string[], builtIn: string[] }}
219
+ */
220
+ list() {
221
+ return {
222
+ user: Array.from(this._presets.keys()),
223
+ builtIn: Object.keys(PROFILES)
224
+ };
225
+ }
226
+
227
+ /**
228
+ * List all preset names (user + built-in) as a flat array.
229
+ *
230
+ * @returns {string[]}
231
+ */
232
+ listAll() {
233
+ return [
234
+ ...Object.keys(PROFILES),
235
+ ...Array.from(this._presets.keys())
236
+ ];
237
+ }
238
+
239
+ /**
240
+ * Get the count of user presets.
241
+ *
242
+ * @returns {number}
243
+ */
244
+ get count() {
245
+ return this._presets.size;
246
+ }
247
+
248
+ // ========================================================================
249
+ // Tune
250
+ // ========================================================================
251
+
252
+ /**
253
+ * Tune (hot-patch) a layer's relationship config without replacing the full graph.
254
+ * Re-instantiates the relationship function with updated config.
255
+ *
256
+ * @param {string} layerName - Layer to tune
257
+ * @param {object} configOverrides - Config values to merge (e.g., { opacity: 0.6, gain: 3 })
258
+ * @returns {boolean} True if tuned successfully
259
+ */
260
+ tune(layerName, configOverrides) {
261
+ if (!configOverrides || typeof configOverrides !== 'object') return false;
262
+
263
+ // Read current config for this layer from the graph
264
+ const graphConfig = this._graph.exportConfig();
265
+ const currentRel = graphConfig.relationships[layerName];
266
+
267
+ if (!currentRel || !currentRel.preset) {
268
+ // Can't tune a custom function or missing relationship
269
+ return false;
270
+ }
271
+
272
+ const factory = PRESET_REGISTRY[currentRel.preset];
273
+ if (!factory) return false;
274
+
275
+ // Merge overrides into existing config
276
+ const newConfig = { ...(currentRel.config || {}), ...configOverrides };
277
+
278
+ // Re-set the relationship with updated config
279
+ this._graph.setRelationship(layerName, {
280
+ preset: currentRel.preset,
281
+ config: newConfig
282
+ });
283
+
284
+ return true;
285
+ }
286
+
287
+ /**
288
+ * Get the current tunable config for a layer.
289
+ *
290
+ * @param {string} layerName
291
+ * @returns {object|null} The current { preset, config } or null
292
+ */
293
+ getLayerConfig(layerName) {
294
+ const graphConfig = this._graph.exportConfig();
295
+ return graphConfig.relationships[layerName] || null;
296
+ }
297
+
298
+ // ========================================================================
299
+ // Import / Export
300
+ // ========================================================================
301
+
302
+ /**
303
+ * Export all user presets as a JSON-serializable library.
304
+ *
305
+ * @returns {object} Library object with version and presets
306
+ */
307
+ exportLibrary() {
308
+ const presets = {};
309
+ for (const [name, preset] of this._presets) {
310
+ presets[name] = preset;
311
+ }
312
+
313
+ return {
314
+ version: '1.0',
315
+ type: 'vib3_layer_presets',
316
+ exportedAt: new Date().toISOString(),
317
+ count: this._presets.size,
318
+ presets
319
+ };
320
+ }
321
+
322
+ /**
323
+ * Import presets from a library object.
324
+ * Does not overwrite existing presets unless `overwrite` is true.
325
+ *
326
+ * @param {object} library - Library object from exportLibrary()
327
+ * @param {object} [options]
328
+ * @param {boolean} [options.overwrite=false] - Overwrite existing presets
329
+ * @returns {{ imported: number, skipped: number }}
330
+ */
331
+ importLibrary(library, options = {}) {
332
+ const { overwrite = false } = options;
333
+
334
+ if (!library || !library.presets || typeof library.presets !== 'object') {
335
+ throw new Error('Invalid library format: missing presets object');
336
+ }
337
+
338
+ let imported = 0;
339
+ let skipped = 0;
340
+
341
+ for (const [name, preset] of Object.entries(library.presets)) {
342
+ if (PROFILES[name]) {
343
+ skipped++;
344
+ continue; // Never overwrite built-in profiles
345
+ }
346
+
347
+ if (!overwrite && this._presets.has(name)) {
348
+ skipped++;
349
+ continue;
350
+ }
351
+
352
+ this._presets.set(name, preset);
353
+ imported++;
354
+ }
355
+
356
+ if (imported > 0) {
357
+ this._saveToStorage();
358
+ }
359
+
360
+ return { imported, skipped };
361
+ }
362
+
363
+ /**
364
+ * Clear all user presets.
365
+ */
366
+ clear() {
367
+ this._presets.clear();
368
+ this._saveToStorage();
369
+ }
370
+ }
371
+
372
+ export default LayerPresetManager;