@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.
- package/DOCS/AGENT_HARNESS_ARCHITECTURE.md +2 -0
- package/DOCS/ANDROID_DEPLOYMENT.md +59 -0
- package/DOCS/ARCHITECTURE.md +1 -0
- package/DOCS/CI_TESTING.md +2 -0
- package/DOCS/CLI_ONBOARDING.md +2 -0
- package/DOCS/CONTROL_REFERENCE.md +2 -0
- package/DOCS/CROSS_SITE_DESIGN_PATTERNS.md +2 -0
- package/DOCS/ENV_SETUP.md +2 -0
- package/DOCS/EPIC_SCROLL_EVENTS.md +2 -0
- package/DOCS/EXPANSION_DESIGN.md +979 -0
- package/DOCS/EXPANSION_DESIGN_ULTRA.md +389 -0
- package/DOCS/EXPORT_FORMATS.md +2 -0
- package/DOCS/GPU_DISPOSAL_GUIDE.md +2 -0
- package/DOCS/HANDOFF_LANDING_PAGE.md +2 -0
- package/DOCS/HANDOFF_SDK_DEVELOPMENT.md +2 -0
- package/DOCS/LICENSING_TIERS.md +2 -0
- package/DOCS/MASTER_PLAN_2026-01-31.md +4 -2
- package/DOCS/MULTIVIZ_CHOREOGRAPHY_PATTERNS.md +3 -1
- package/DOCS/OBS_SETUP_GUIDE.md +2 -0
- package/DOCS/OPTIMIZATION_PLAN_MATH.md +119 -0
- package/DOCS/PRODUCT_STRATEGY.md +2 -0
- package/DOCS/PROJECT_SETUP.md +2 -0
- package/DOCS/README.md +5 -3
- package/DOCS/REFERENCE_SCROLL_ANALYSIS.md +2 -0
- package/DOCS/RENDERER_LIFECYCLE.md +2 -0
- package/DOCS/REPO_MANIFEST.md +2 -0
- package/DOCS/ROADMAP.md +2 -0
- package/DOCS/SCROLL_TIMELINE_v3.md +2 -0
- package/DOCS/SITE_REFACTOR_PLAN.md +2 -0
- package/DOCS/STATUS.md +2 -0
- package/DOCS/SYSTEM_INVENTORY.md +4 -2
- package/DOCS/TELEMETRY_EXPORTS.md +2 -0
- package/DOCS/VISUAL_ANALYSIS_CLICKERSS.md +2 -0
- package/DOCS/VISUAL_ANALYSIS_FACETAD.md +2 -0
- package/DOCS/VISUAL_ANALYSIS_SIMONE.md +2 -0
- package/DOCS/VISUAL_ANALYSIS_TABLESIDE.md +2 -0
- package/DOCS/WEBGPU_STATUS.md +121 -38
- package/DOCS/XR_BENCHMARKS.md +2 -0
- package/DOCS/archive/BLUEPRINT_EXECUTION_PLAN_2026-01-07.md +1 -34
- package/DOCS/archive/DEV_TRACK_ANALYSIS.md +1 -80
- package/DOCS/archive/DEV_TRACK_PLAN_2026-01-07.md +1 -42
- package/DOCS/archive/SESSION_014_PLAN.md +1 -195
- package/DOCS/archive/SESSION_LOG_2026-01-07.md +1 -56
- package/DOCS/archive/STRATEGIC_BLUEPRINT_2026-01-07.md +1 -72
- package/DOCS/archive/SYSTEM_AUDIT_2026-01-30.md +1 -741
- package/DOCS/archive/WEBGPU_STATUS_2026-02-15_STALE.md +1 -0
- package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-01-31.md +2 -0
- package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-06.md +2 -0
- package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-13.md +15 -0
- package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-15.md +144 -0
- package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-16.md +110 -0
- package/DOCS/dev-tracks/PERF_UPGRADE_2026-02-16.md +310 -0
- package/DOCS/dev-tracks/README.md +2 -0
- package/docs/webgpu-live.html +1 -1
- package/package.json +11 -4
- package/src/agent/index.js +1 -3
- package/src/agent/mcp/MCPServer.js +542 -188
- package/src/agent/mcp/index.js +1 -1
- package/src/agent/mcp/tools.js +132 -32
- package/src/cli/index.js +431 -47
- package/src/core/VIB3Engine.js +55 -3
- package/src/core/index.js +18 -0
- package/src/core/renderers/FacetedRendererAdapter.js +10 -9
- package/src/core/renderers/HolographicRendererAdapter.js +11 -7
- package/src/core/renderers/QuantumRendererAdapter.js +11 -7
- package/src/creative/index.js +11 -0
- package/src/experimental/GameLoop.js +72 -0
- package/src/experimental/LatticePhysics.js +100 -0
- package/src/experimental/LiveDirector.js +143 -0
- package/src/experimental/PlayerController4D.js +154 -0
- package/src/experimental/VIB3Actor.js +138 -0
- package/src/experimental/VIB3Compositor.js +117 -0
- package/src/experimental/VIB3Link.js +122 -0
- package/src/experimental/VIB3Orchestrator.js +146 -0
- package/src/experimental/VIB3Universe.js +109 -0
- package/src/experimental/demos/CrystalLabyrinth.js +202 -0
- package/src/export/SVGExporter.js +9 -5
- package/src/export/index.js +11 -1
- package/src/faceted/FacetedSystem.js +27 -10
- package/src/games/glyph-war/GlyphWarVisualizer.js +641 -0
- package/src/geometry/generators/Crystal.js +2 -2
- package/src/geometry/warp/HypersphereCore.js +53 -24
- package/src/holograms/HolographicVisualizer.js +58 -89
- package/src/holograms/RealHolographicSystem.js +126 -31
- package/src/math/Mat4x4.js +372 -140
- package/src/math/Projection.js +39 -4
- package/src/math/Rotor4D.js +157 -67
- package/src/math/Vec4.js +265 -111
- package/src/math/index.js +7 -7
- package/src/quantum/QuantumVisualizer.js +24 -20
- 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/ShaderLoader.js +38 -0
- package/src/render/ShaderProgram.js +4 -4
- package/src/render/UnifiedRenderBridge.js +1 -1
- package/src/render/backends/WebGPUBackend.js +8 -4
- package/src/render/index.js +27 -2
- package/src/scene/Node4D.js +74 -24
- package/src/scene/index.js +4 -4
- package/src/shaders/common/geometry24.glsl +65 -0
- package/src/shaders/common/geometry24.wgsl +54 -0
- package/src/shaders/common/rotation4d.glsl +4 -4
- package/src/shaders/common/rotation4d.wgsl +2 -2
- package/src/shaders/common/uniforms.wgsl +15 -8
- package/src/shaders/faceted/faceted.frag.wgsl +19 -6
- package/src/shaders/holographic/holographic.frag.wgsl +7 -5
- package/src/shaders/quantum/quantum.frag.wgsl +7 -5
- package/src/testing/ParallelTestFramework.js +2 -2
- package/src/testing/ProjectionClass.test.js +38 -0
- package/src/ui/adaptive/renderers/webgpu/WebGPURenderer.ts +2 -2
- package/src/viewer/GalleryUI.js +17 -0
- package/src/viewer/ViewerPortal.js +2 -2
- package/tools/shader-sync-verify.js +6 -4
- package/tools/update_projection.py +109 -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
|
@@ -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 ${
|
|
49
|
+
console.error(`WebGL not supported for ${this._canvasLabel}`);
|
|
38
50
|
if (window.mobileDebug) {
|
|
39
|
-
window.mobileDebug.log(`❌ ${
|
|
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(`✅ ${
|
|
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 ${
|
|
74
|
+
console.warn(`WebGL context lost for ${this._canvasLabel}`);
|
|
63
75
|
};
|
|
64
76
|
this._onContextRestored = () => {
|
|
65
|
-
console.log(`WebGL context restored for ${
|
|
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 ${
|
|
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
|
|
693
|
-
else if (u_roleIntensity
|
|
694
|
-
else if (u_roleIntensity
|
|
695
|
-
else if (u_roleIntensity
|
|
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,
|
|
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);
|
package/src/reactivity/index.js
CHANGED
|
@@ -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
|
|
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;
|