@vib3code/sdk 2.0.1 → 2.0.3-canary.0a63e71
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 +245 -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 +3 -1
- package/DOCS/CONTROL_REFERENCE.md +2 -0
- package/DOCS/CROSS_SITE_DESIGN_PATTERNS.md +119 -0
- package/DOCS/ENV_SETUP.md +2 -0
- package/DOCS/EPIC_SCROLL_EVENTS.md +775 -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 +156 -0
- package/DOCS/HANDOFF_SDK_DEVELOPMENT.md +495 -0
- package/DOCS/LICENSING_TIERS.md +2 -0
- package/DOCS/MASTER_PLAN_2026-01-31.md +4 -2
- package/DOCS/MULTIVIZ_CHOREOGRAPHY_PATTERNS.md +939 -0
- package/DOCS/OBS_SETUP_GUIDE.md +2 -0
- package/DOCS/OPTIMIZATION_PLAN_MATH.md +119 -0
- package/DOCS/PRODUCT_STRATEGY.md +65 -0
- package/DOCS/PROJECT_SETUP.md +2 -0
- package/DOCS/README.md +105 -0
- package/DOCS/REFERENCE_SCROLL_ANALYSIS.md +99 -0
- package/DOCS/RENDERER_LIFECYCLE.md +2 -0
- package/DOCS/REPO_MANIFEST.md +2 -0
- package/DOCS/ROADMAP.md +113 -0
- package/DOCS/SCROLL_TIMELINE_v3.md +271 -0
- package/DOCS/SITE_REFACTOR_PLAN.md +102 -0
- package/DOCS/STATUS.md +26 -0
- package/DOCS/SYSTEM_INVENTORY.md +37 -32
- package/DOCS/TELEMETRY_EXPORTS.md +2 -0
- package/DOCS/VISUAL_ANALYSIS_CLICKERSS.md +87 -0
- package/DOCS/VISUAL_ANALYSIS_FACETAD.md +135 -0
- package/DOCS/VISUAL_ANALYSIS_SIMONE.md +97 -0
- package/DOCS/VISUAL_ANALYSIS_TABLESIDE.md +88 -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 -0
- package/DOCS/archive/DEV_TRACK_ANALYSIS.md +1 -0
- package/DOCS/archive/DEV_TRACK_PLAN_2026-01-07.md +1 -0
- package/DOCS/archive/SESSION_014_PLAN.md +1 -0
- package/DOCS/archive/SESSION_LOG_2026-01-07.md +1 -0
- package/DOCS/archive/STRATEGIC_BLUEPRINT_2026-01-07.md +1 -0
- package/DOCS/archive/SYSTEM_AUDIT_2026-01-30.md +1 -0
- package/DOCS/archive/WEBGPU_STATUS_2026-02-15_STALE.md +1 -0
- package/DOCS/{DEV_TRACK_SESSION_2026-01-31.md → dev-tracks/DEV_TRACK_SESSION_2026-01-31.md} +3 -1
- package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-06.md +233 -0
- package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-13.md +129 -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 +12 -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/docs/webgpu-live.html +1 -1
- package/package.json +41 -30
- package/src/agent/index.js +1 -3
- package/src/agent/mcp/MCPServer.js +1220 -144
- package/src/agent/mcp/index.js +1 -1
- package/src/agent/mcp/stdio-server.js +264 -0
- package/src/agent/mcp/tools.js +498 -31
- package/src/cli/index.js +431 -47
- 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/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/TradingCardManager.js +3 -4
- package/src/export/index.js +11 -1
- package/src/faceted/FacetedSystem.js +260 -394
- 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 +84 -98
- package/src/holograms/RealHolographicSystem.js +194 -43
- package/src/math/Mat4x4.js +308 -105
- package/src/math/Rotor4D.js +124 -40
- package/src/math/Vec4.js +200 -103
- 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 +31 -22
- 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 +4 -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.glsl +220 -80
- package/src/shaders/faceted/faceted.frag.wgsl +144 -90
- package/src/shaders/holographic/holographic.frag.glsl +28 -9
- package/src/shaders/holographic/holographic.frag.wgsl +112 -41
- package/src/shaders/quantum/quantum.frag.glsl +1 -0
- package/src/shaders/quantum/quantum.frag.wgsl +6 -4
- package/src/testing/ParallelTestFramework.js +2 -2
- 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/src/viewer/index.js +1 -1
- package/tools/headless-renderer.js +258 -0
- package/tools/shader-sync-verify.js +14 -8
- 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/BLUEPRINT_EXECUTION_PLAN_2026-01-07.md +0 -34
- package/DOCS/DEV_TRACK_ANALYSIS.md +0 -77
- package/DOCS/DEV_TRACK_PLAN_2026-01-07.md +0 -42
- package/DOCS/SESSION_014_PLAN.md +0 -195
- package/DOCS/SESSION_LOG_2026-01-07.md +0 -56
- package/DOCS/STRATEGIC_BLUEPRINT_2026-01-07.md +0 -72
- package/DOCS/SYSTEM_AUDIT_2026-01-30.md +0 -738
- /package/src/viewer/{ReactivityManager.js → ViewerInputHandler.js} +0 -0
|
@@ -6,6 +6,12 @@
|
|
|
6
6
|
import { toolDefinitions, getToolList, validateToolInput } from './tools.js';
|
|
7
7
|
import { schemaRegistry } from '../../schemas/index.js';
|
|
8
8
|
import { telemetry, EventType, withTelemetry } from '../telemetry/index.js';
|
|
9
|
+
import { AestheticMapper } from '../../creative/AestheticMapper.js';
|
|
10
|
+
import { ChoreographyPlayer } from '../../creative/ChoreographyPlayer.js';
|
|
11
|
+
import { ParameterTimeline } from '../../creative/ParameterTimeline.js';
|
|
12
|
+
import { ColorPresetsSystem } from '../../creative/ColorPresetsSystem.js';
|
|
13
|
+
import { TransitionAnimator } from '../../creative/TransitionAnimator.js';
|
|
14
|
+
import { PRESET_REGISTRY } from '../../render/LayerRelationshipGraph.js';
|
|
9
15
|
|
|
10
16
|
/**
|
|
11
17
|
* Generate unique IDs
|
|
@@ -38,6 +44,36 @@ export class MCPServer {
|
|
|
38
44
|
this.engine = engine;
|
|
39
45
|
this.sceneId = null;
|
|
40
46
|
this.initialized = false;
|
|
47
|
+
this._gallerySlots = new Map();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Get or lazily create ColorPresetsSystem instance.
|
|
52
|
+
* Requires engine for the parameter update callback.
|
|
53
|
+
* @returns {ColorPresetsSystem|null}
|
|
54
|
+
*/
|
|
55
|
+
_getColorPresets() {
|
|
56
|
+
if (this._colorPresets) return this._colorPresets;
|
|
57
|
+
if (!this.engine) return null;
|
|
58
|
+
this._colorPresets = new ColorPresetsSystem(
|
|
59
|
+
(name, value) => this.engine.setParameter(name, value)
|
|
60
|
+
);
|
|
61
|
+
return this._colorPresets;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get or lazily create TransitionAnimator instance.
|
|
66
|
+
* Requires engine for the parameter update/get callbacks.
|
|
67
|
+
* @returns {TransitionAnimator|null}
|
|
68
|
+
*/
|
|
69
|
+
_getTransitionAnimator() {
|
|
70
|
+
if (this._transitionAnimator) return this._transitionAnimator;
|
|
71
|
+
if (!this.engine) return null;
|
|
72
|
+
this._transitionAnimator = new TransitionAnimator(
|
|
73
|
+
(name, value) => this.engine.setParameter(name, value),
|
|
74
|
+
(name) => this.engine.getParameter(name)
|
|
75
|
+
);
|
|
76
|
+
return this._transitionAnimator;
|
|
41
77
|
}
|
|
42
78
|
|
|
43
79
|
buildResponse(operation, data, options = {}) {
|
|
@@ -137,10 +173,13 @@ export class MCPServer {
|
|
|
137
173
|
result = this.getParameterSchema();
|
|
138
174
|
break;
|
|
139
175
|
case 'get_sdk_context':
|
|
140
|
-
result = this.getSDKContext();
|
|
176
|
+
result = this.getSDKContext(args);
|
|
177
|
+
break;
|
|
178
|
+
case 'inspect_layers':
|
|
179
|
+
result = this.inspectLayers(args);
|
|
141
180
|
break;
|
|
142
|
-
case '
|
|
143
|
-
result = this.
|
|
181
|
+
case 'set_holographic_layer':
|
|
182
|
+
result = this.setHolographicLayer(args);
|
|
144
183
|
break;
|
|
145
184
|
// Reactivity tools (Phase 6.5)
|
|
146
185
|
case 'set_reactivity_config':
|
|
@@ -163,6 +202,60 @@ export class MCPServer {
|
|
|
163
202
|
case 'list_behavior_presets':
|
|
164
203
|
result = this.listBehaviorPresets();
|
|
165
204
|
break;
|
|
205
|
+
// Agent-power tools (Phase 7)
|
|
206
|
+
case 'describe_visual_state':
|
|
207
|
+
result = this.describeVisualState();
|
|
208
|
+
break;
|
|
209
|
+
case 'batch_set_parameters':
|
|
210
|
+
result = await this.batchSetParameters(args);
|
|
211
|
+
break;
|
|
212
|
+
case 'create_timeline':
|
|
213
|
+
result = this.createTimeline(args);
|
|
214
|
+
break;
|
|
215
|
+
case 'play_transition':
|
|
216
|
+
result = this.playTransition(args);
|
|
217
|
+
break;
|
|
218
|
+
case 'apply_color_preset':
|
|
219
|
+
result = this.applyColorPreset(args);
|
|
220
|
+
break;
|
|
221
|
+
case 'set_post_processing':
|
|
222
|
+
result = this.setPostProcessing(args);
|
|
223
|
+
break;
|
|
224
|
+
case 'create_choreography':
|
|
225
|
+
result = this.createChoreography(args);
|
|
226
|
+
break;
|
|
227
|
+
// Visual feedback tools (Phase 7.1)
|
|
228
|
+
case 'capture_screenshot':
|
|
229
|
+
result = await this.captureScreenshot(args);
|
|
230
|
+
break;
|
|
231
|
+
case 'design_from_description':
|
|
232
|
+
result = await this.designFromDescription(args);
|
|
233
|
+
break;
|
|
234
|
+
case 'get_aesthetic_vocabulary':
|
|
235
|
+
result = this.getAestheticVocabulary();
|
|
236
|
+
break;
|
|
237
|
+
case 'play_choreography':
|
|
238
|
+
result = await this.playChoreographyTool(args);
|
|
239
|
+
break;
|
|
240
|
+
case 'control_timeline':
|
|
241
|
+
result = this.controlTimeline(args);
|
|
242
|
+
break;
|
|
243
|
+
// Layer relationship tools (Phase 8)
|
|
244
|
+
case 'set_layer_profile':
|
|
245
|
+
result = this.setLayerProfile(args);
|
|
246
|
+
break;
|
|
247
|
+
case 'set_layer_relationship':
|
|
248
|
+
result = this.setLayerRelationship(args);
|
|
249
|
+
break;
|
|
250
|
+
case 'set_layer_keystone':
|
|
251
|
+
result = this.setLayerKeystone(args);
|
|
252
|
+
break;
|
|
253
|
+
case 'get_layer_config':
|
|
254
|
+
result = this.getLayerConfig();
|
|
255
|
+
break;
|
|
256
|
+
case 'tune_layer_relationship':
|
|
257
|
+
result = this.tuneLayerRelationship(args);
|
|
258
|
+
break;
|
|
166
259
|
default:
|
|
167
260
|
throw new Error(`Unknown tool: ${toolName}`);
|
|
168
261
|
}
|
|
@@ -426,32 +519,68 @@ export class MCPServer {
|
|
|
426
519
|
|
|
427
520
|
telemetry.recordEvent(EventType.GALLERY_SAVE, { slot });
|
|
428
521
|
|
|
522
|
+
// Persist actual engine state if available
|
|
523
|
+
if (this.engine) {
|
|
524
|
+
const state = this.engine.exportState();
|
|
525
|
+
this._gallerySlots.set(slot, {
|
|
526
|
+
name: name || `Variation ${slot}`,
|
|
527
|
+
saved_at: new Date().toISOString(),
|
|
528
|
+
state
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
|
|
429
532
|
return {
|
|
430
533
|
slot,
|
|
431
534
|
name: name || `Variation ${slot}`,
|
|
432
535
|
saved_at: new Date().toISOString(),
|
|
536
|
+
persisted: !!this.engine,
|
|
537
|
+
gallery_size: this._gallerySlots.size,
|
|
433
538
|
suggested_next_actions: ['load_from_gallery', 'randomize_parameters']
|
|
434
539
|
};
|
|
435
540
|
}
|
|
436
541
|
|
|
437
542
|
/**
|
|
438
|
-
* Load from gallery
|
|
543
|
+
* Load from gallery — restores previously saved state
|
|
439
544
|
*/
|
|
440
545
|
loadFromGallery(args) {
|
|
441
546
|
const { slot } = args;
|
|
442
547
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
548
|
+
telemetry.recordEvent(EventType.GALLERY_LOAD, { slot });
|
|
549
|
+
|
|
550
|
+
const saved = this._gallerySlots.get(slot);
|
|
551
|
+
if (saved && this.engine) {
|
|
552
|
+
// Restore saved state
|
|
553
|
+
this.engine.importState(saved.state);
|
|
554
|
+
return {
|
|
555
|
+
slot,
|
|
556
|
+
name: saved.name,
|
|
557
|
+
saved_at: saved.saved_at,
|
|
558
|
+
loaded_at: new Date().toISOString(),
|
|
559
|
+
restored: true,
|
|
560
|
+
...this.getState()
|
|
561
|
+
};
|
|
447
562
|
}
|
|
448
563
|
|
|
449
|
-
|
|
564
|
+
if (!saved) {
|
|
565
|
+
// No saved state — fall back to random variation
|
|
566
|
+
if (this.engine) {
|
|
567
|
+
const params = this.engine.parameters?.generateVariationParameters?.(slot) || {};
|
|
568
|
+
this.engine.setParameters(params);
|
|
569
|
+
}
|
|
570
|
+
return {
|
|
571
|
+
slot,
|
|
572
|
+
loaded_at: new Date().toISOString(),
|
|
573
|
+
restored: false,
|
|
574
|
+
note: 'No saved state in this slot — generated random variation',
|
|
575
|
+
...this.getState()
|
|
576
|
+
};
|
|
577
|
+
}
|
|
450
578
|
|
|
451
579
|
return {
|
|
452
580
|
slot,
|
|
453
581
|
loaded_at: new Date().toISOString(),
|
|
454
|
-
|
|
582
|
+
restored: false,
|
|
583
|
+
note: 'Engine not initialized — cannot apply state'
|
|
455
584
|
};
|
|
456
585
|
}
|
|
457
586
|
|
|
@@ -524,162 +653,218 @@ export class MCPServer {
|
|
|
524
653
|
/**
|
|
525
654
|
* Get SDK context for agent onboarding
|
|
526
655
|
*/
|
|
527
|
-
getSDKContext() {
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
656
|
+
getSDKContext(args = {}) {
|
|
657
|
+
const { include_state = true, include_tools = false } = args;
|
|
658
|
+
|
|
659
|
+
const context = {
|
|
660
|
+
sdk: 'VIB3+ 4D Visualization Engine',
|
|
661
|
+
version: '2.0.3',
|
|
662
|
+
|
|
663
|
+
// Capability manifest — what this engine can do
|
|
664
|
+
capabilities: {
|
|
665
|
+
systems: ['quantum', 'faceted', 'holographic'],
|
|
666
|
+
geometries: { count: 24, formula: 'core_type * 8 + base_geometry', base: 8, warps: 3 },
|
|
667
|
+
rotation: { planes: 6, '3D': ['XY', 'XZ', 'YZ'], '4D': ['XW', 'YW', 'ZW'], range: '±6.28 rad' },
|
|
668
|
+
layers: { count: 5, roles: ['background', 'shadow', 'content', 'highlight', 'accent'], addressable: true },
|
|
669
|
+
audio: { bands: ['bass', 'mid', 'high'], modes: ['add', 'multiply', 'replace', 'max', 'min'] },
|
|
670
|
+
input: { sources: ['deviceTilt', 'mousePosition', 'gyroscope', 'gamepad', 'perspective', 'programmatic', 'audio', 'midi'] },
|
|
671
|
+
creative: {
|
|
672
|
+
color_presets: 22,
|
|
673
|
+
easing_functions: 14,
|
|
674
|
+
post_effects: 14,
|
|
675
|
+
aesthetic_keywords: '130+',
|
|
676
|
+
choreography: true,
|
|
677
|
+
timeline_bpm_sync: true
|
|
678
|
+
},
|
|
679
|
+
environment: {
|
|
680
|
+
browser: typeof document !== 'undefined',
|
|
681
|
+
screenshot: typeof document !== 'undefined',
|
|
682
|
+
webgpu: typeof navigator !== 'undefined' && !!navigator.gpu,
|
|
683
|
+
wasm: typeof WebAssembly !== 'undefined'
|
|
684
|
+
}
|
|
552
685
|
},
|
|
553
686
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
687
|
+
// Parameter ranges — the agent needs these to generate valid values
|
|
688
|
+
parameter_ranges: {
|
|
689
|
+
geometry: { min: 0, max: 23, type: 'integer' },
|
|
690
|
+
hue: { min: 0, max: 360, type: 'integer' },
|
|
691
|
+
saturation: { min: 0, max: 1 },
|
|
692
|
+
intensity: { min: 0, max: 1 },
|
|
693
|
+
speed: { min: 0.1, max: 3 },
|
|
694
|
+
chaos: { min: 0, max: 1 },
|
|
695
|
+
morphFactor: { min: 0, max: 2 },
|
|
696
|
+
gridDensity: { min: 4, max: 100 },
|
|
697
|
+
dimension: { min: 3.0, max: 4.5 },
|
|
698
|
+
rot4dXY: { min: -6.28, max: 6.28 },
|
|
699
|
+
rot4dXW: { min: -6.28, max: 6.28 }
|
|
559
700
|
},
|
|
560
701
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
702
|
+
// Workflow hints — what tool sequences accomplish creative goals
|
|
703
|
+
workflows: {
|
|
704
|
+
quick_design: 'design_from_description → describe_visual_state → batch_set_parameters',
|
|
705
|
+
choreography: 'create_choreography → play_choreography → describe_visual_state',
|
|
706
|
+
evolve: 'batch_set_parameters → describe_visual_state → batch_set_parameters (iterate)',
|
|
707
|
+
layer_control: 'inspect_layers → set_holographic_layer → inspect_layers',
|
|
708
|
+
audio_reactive: 'configure_audio_band → apply_behavior_preset → describe_visual_state'
|
|
566
709
|
},
|
|
567
710
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
711
|
+
// Geometry quick-reference
|
|
712
|
+
geometry_map: {
|
|
713
|
+
'sphere': 2, 'hypersphere+sphere': 10, 'hypertetra+sphere': 18,
|
|
714
|
+
'torus': 3, 'hypersphere+torus': 11, 'hypertetra+torus': 19,
|
|
715
|
+
'fractal': 5, 'hypersphere+fractal': 13, 'hypertetra+fractal': 21,
|
|
716
|
+
'crystal': 7, 'wave': 6, 'klein_bottle': 4, 'hypercube': 1, 'tetrahedron': 0
|
|
717
|
+
}
|
|
718
|
+
};
|
|
572
719
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
'Q1: How many rotation planes? a)3 b)4 c)6 d)8',
|
|
577
|
-
'Q2: Geometry encoding formula? a)base*3+core b)core*8+base c)base+core d)core*base',
|
|
578
|
-
'Q3: Canvas layers per system? a)3 b)4 c)5 d)6',
|
|
579
|
-
'Q4: Which are the 3 ACTIVE systems? a)quantum,faceted,holographic b)quantum,faceted,polychora c)all four d)none',
|
|
580
|
-
'Q5: How many base geometry types? a)6 b)8 c)10 d)24',
|
|
581
|
-
'Q6: Core warp types? a)base,sphere,cube b)base,hypersphere,hypertetrahedron c)2D,3D,4D d)none'
|
|
582
|
-
]
|
|
583
|
-
},
|
|
720
|
+
if (include_state) {
|
|
721
|
+
context.current_state = this.getState();
|
|
722
|
+
}
|
|
584
723
|
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
724
|
+
if (include_tools) {
|
|
725
|
+
context.tool_summary = Object.entries(toolDefinitions).map(([name, def]) => ({
|
|
726
|
+
name,
|
|
727
|
+
description: def.description.split('.')[0] // First sentence only
|
|
728
|
+
}));
|
|
729
|
+
}
|
|
591
730
|
|
|
592
|
-
|
|
593
|
-
};
|
|
731
|
+
return context;
|
|
594
732
|
}
|
|
595
733
|
|
|
596
734
|
/**
|
|
597
|
-
*
|
|
735
|
+
* Inspect holographic layer state — returns per-layer metadata in JSON format.
|
|
736
|
+
* This replaces the old verify_knowledge quiz with something actually useful.
|
|
598
737
|
*/
|
|
599
|
-
|
|
600
|
-
const
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
738
|
+
inspectLayers(args = {}) {
|
|
739
|
+
const { layer = 'all' } = args;
|
|
740
|
+
|
|
741
|
+
const systemName = this.engine?.currentSystemName || 'unknown';
|
|
742
|
+
const isHolographic = systemName === 'holographic';
|
|
743
|
+
|
|
744
|
+
if (!isHolographic) {
|
|
745
|
+
return {
|
|
746
|
+
system: systemName,
|
|
747
|
+
note: 'Layer inspection is most detailed for the holographic system (5 independent canvas layers). Switch with switch_system("holographic").',
|
|
748
|
+
layers: [{
|
|
749
|
+
role: 'main',
|
|
750
|
+
system: systemName,
|
|
751
|
+
opacity: 1.0,
|
|
752
|
+
enabled: true,
|
|
753
|
+
description: `Single canvas for ${systemName} system`
|
|
754
|
+
}],
|
|
755
|
+
suggested_next_actions: ['switch_system', 'describe_visual_state']
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
// Build layer metadata from the holographic system
|
|
760
|
+
const system = this.engine?.currentSystem;
|
|
761
|
+
const layerRoles = ['background', 'shadow', 'content', 'highlight', 'accent'];
|
|
762
|
+
const defaultConfigs = {
|
|
763
|
+
background: { densityMult: 0.4, speedMult: 0.2, colorShift: 0, intensity: 0.2, reactivity: 0.5 },
|
|
764
|
+
shadow: { densityMult: 0.8, speedMult: 0.3, colorShift: 180, intensity: 0.4, reactivity: 0.7 },
|
|
765
|
+
content: { densityMult: 1.0, speedMult: 1.0, colorShift: 0, intensity: 1.0, reactivity: 0.9 },
|
|
766
|
+
highlight: { densityMult: 1.5, speedMult: 0.8, colorShift: 60, intensity: 0.6, reactivity: 1.1 },
|
|
767
|
+
accent: { densityMult: 2.5, speedMult: 0.4, colorShift: 300, intensity: 0.3, reactivity: 1.5 }
|
|
607
768
|
};
|
|
608
769
|
|
|
609
|
-
const
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
topic: 'ACTIVE VS PLACEHOLDER SYSTEMS',
|
|
627
|
-
doc: 'DOCS/SYSTEM_INVENTORY.md',
|
|
628
|
-
reason: 'Only 3 ACTIVE: quantum, faceted, holographic. Polychora is TBD/placeholder!'
|
|
629
|
-
},
|
|
630
|
-
q5_base_geometries: {
|
|
631
|
-
topic: 'BASE GEOMETRY TYPES',
|
|
632
|
-
doc: '24-GEOMETRY-6D-ROTATION-SUMMARY.md',
|
|
633
|
-
reason: '8 base: tetrahedron, hypercube, sphere, torus, klein, fractal, wave, crystal'
|
|
634
|
-
},
|
|
635
|
-
q6_core_types: {
|
|
636
|
-
topic: 'CORE WARP TYPES',
|
|
637
|
-
doc: '24-GEOMETRY-6D-ROTATION-SUMMARY.md',
|
|
638
|
-
reason: '3 cores: base (no warp), hypersphere, hypertetrahedron'
|
|
639
|
-
}
|
|
770
|
+
const buildLayerInfo = (role) => {
|
|
771
|
+
const config = defaultConfigs[role] || {};
|
|
772
|
+
const visualizer = system?.visualizers?.find?.(v => v?.role === role);
|
|
773
|
+
const overrides = this._layerOverrides?.get(role) || {};
|
|
774
|
+
|
|
775
|
+
return {
|
|
776
|
+
role,
|
|
777
|
+
enabled: overrides.enabled !== undefined ? overrides.enabled : true,
|
|
778
|
+
opacity: overrides.opacity !== undefined ? overrides.opacity : 1.0,
|
|
779
|
+
blendMode: overrides.blendMode || 'normal',
|
|
780
|
+
densityMult: overrides.densityMult ?? config.densityMult,
|
|
781
|
+
speedMult: overrides.speedMult ?? config.speedMult,
|
|
782
|
+
colorShift: overrides.colorShift ?? config.colorShift,
|
|
783
|
+
intensity: overrides.intensity ?? config.intensity,
|
|
784
|
+
reactivity: overrides.reactivity ?? config.reactivity,
|
|
785
|
+
has_visualizer: !!visualizer
|
|
786
|
+
};
|
|
640
787
|
};
|
|
641
788
|
|
|
642
|
-
const
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
details: [],
|
|
646
|
-
REVIEW_REQUIRED: []
|
|
647
|
-
};
|
|
648
|
-
|
|
649
|
-
// Check each answer
|
|
650
|
-
for (const [question, correct] of Object.entries(correctAnswers)) {
|
|
651
|
-
const given = answers[question]?.toLowerCase?.() || answers[question];
|
|
652
|
-
if (given === correct) {
|
|
653
|
-
results.score++;
|
|
654
|
-
results.details.push({ question, status: '✓ CORRECT' });
|
|
655
|
-
} else if (given !== undefined) {
|
|
656
|
-
results.details.push({
|
|
657
|
-
question,
|
|
658
|
-
status: '✗ WRONG',
|
|
659
|
-
your_answer: given,
|
|
660
|
-
correct_answer: correct
|
|
661
|
-
});
|
|
662
|
-
results.REVIEW_REQUIRED.push(docReferences[question]);
|
|
663
|
-
}
|
|
664
|
-
}
|
|
789
|
+
const layers = layer === 'all'
|
|
790
|
+
? layerRoles.map(buildLayerInfo)
|
|
791
|
+
: [buildLayerInfo(layer)];
|
|
665
792
|
|
|
666
|
-
|
|
793
|
+
return {
|
|
794
|
+
system: 'holographic',
|
|
795
|
+
layer_count: layerRoles.length,
|
|
796
|
+
layers,
|
|
797
|
+
suggested_next_actions: ['set_holographic_layer', 'batch_set_parameters', 'describe_visual_state']
|
|
798
|
+
};
|
|
799
|
+
}
|
|
667
800
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
801
|
+
/**
|
|
802
|
+
* Set properties on an individual holographic layer.
|
|
803
|
+
* Stores overrides in a layer override map and applies them to the visualizer.
|
|
804
|
+
*/
|
|
805
|
+
setHolographicLayer(args) {
|
|
806
|
+
const { layer, opacity, blendMode, enabled, colorShift, densityMult, speedMult, reactivity } = args;
|
|
807
|
+
|
|
808
|
+
const systemName = this.engine?.currentSystemName || 'unknown';
|
|
809
|
+
if (systemName !== 'holographic') {
|
|
810
|
+
return {
|
|
811
|
+
error: {
|
|
812
|
+
type: 'SystemError',
|
|
813
|
+
code: 'NOT_HOLOGRAPHIC',
|
|
814
|
+
message: `set_holographic_layer requires holographic system (current: ${systemName})`,
|
|
815
|
+
suggestion: 'Call switch_system("holographic") first'
|
|
816
|
+
}
|
|
817
|
+
};
|
|
678
818
|
}
|
|
679
819
|
|
|
680
|
-
|
|
820
|
+
// Initialize layer override storage
|
|
821
|
+
if (!this._layerOverrides) this._layerOverrides = new Map();
|
|
822
|
+
const existing = this._layerOverrides.get(layer) || {};
|
|
823
|
+
const updates = {};
|
|
824
|
+
|
|
825
|
+
if (opacity !== undefined) { existing.opacity = opacity; updates.opacity = opacity; }
|
|
826
|
+
if (blendMode !== undefined) { existing.blendMode = blendMode; updates.blendMode = blendMode; }
|
|
827
|
+
if (enabled !== undefined) { existing.enabled = enabled; updates.enabled = enabled; }
|
|
828
|
+
if (colorShift !== undefined) { existing.colorShift = colorShift; updates.colorShift = colorShift; }
|
|
829
|
+
if (densityMult !== undefined) { existing.densityMult = densityMult; updates.densityMult = densityMult; }
|
|
830
|
+
if (speedMult !== undefined) { existing.speedMult = speedMult; updates.speedMult = speedMult; }
|
|
831
|
+
if (reactivity !== undefined) { existing.reactivity = reactivity; updates.reactivity = reactivity; }
|
|
832
|
+
|
|
833
|
+
this._layerOverrides.set(layer, existing);
|
|
834
|
+
|
|
835
|
+
// Apply to visualizer if available
|
|
836
|
+
const system = this.engine?.currentSystem;
|
|
837
|
+
const visualizer = system?.visualizers?.find?.(v => v?.role === layer);
|
|
838
|
+
if (visualizer) {
|
|
839
|
+
if (opacity !== undefined && visualizer.canvas) {
|
|
840
|
+
visualizer.canvas.style.opacity = String(opacity);
|
|
841
|
+
}
|
|
842
|
+
if (blendMode !== undefined && visualizer.canvas) {
|
|
843
|
+
visualizer.canvas.style.mixBlendMode = blendMode;
|
|
844
|
+
}
|
|
845
|
+
if (enabled !== undefined && visualizer.canvas) {
|
|
846
|
+
visualizer.canvas.style.display = enabled ? '' : 'none';
|
|
847
|
+
}
|
|
848
|
+
if (colorShift !== undefined && visualizer.roleParams) {
|
|
849
|
+
visualizer.roleParams.colorShift = colorShift;
|
|
850
|
+
}
|
|
851
|
+
if (densityMult !== undefined && visualizer.roleParams) {
|
|
852
|
+
visualizer.roleParams.densityMult = densityMult;
|
|
853
|
+
}
|
|
854
|
+
if (speedMult !== undefined && visualizer.roleParams) {
|
|
855
|
+
visualizer.roleParams.speedMult = speedMult;
|
|
856
|
+
}
|
|
857
|
+
if (reactivity !== undefined) {
|
|
858
|
+
visualizer.reactivity = reactivity;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
681
861
|
|
|
682
|
-
return
|
|
862
|
+
return {
|
|
863
|
+
layer,
|
|
864
|
+
applied: updates,
|
|
865
|
+
current_state: this.inspectLayers({ layer }).layers[0],
|
|
866
|
+
suggested_next_actions: ['inspect_layers', 'set_holographic_layer', 'describe_visual_state']
|
|
867
|
+
};
|
|
683
868
|
}
|
|
684
869
|
|
|
685
870
|
/**
|
|
@@ -943,6 +1128,897 @@ export class MCPServer {
|
|
|
943
1128
|
suggested_next_actions: ['apply_behavior_preset']
|
|
944
1129
|
};
|
|
945
1130
|
}
|
|
1131
|
+
|
|
1132
|
+
// ===== AGENT-POWER TOOLS (Phase 7 — Agent Harness) =====
|
|
1133
|
+
|
|
1134
|
+
/**
|
|
1135
|
+
* Generate a natural-language description of the current visual state.
|
|
1136
|
+
* Enables text-only agents to "see" what the visualization looks like.
|
|
1137
|
+
*/
|
|
1138
|
+
describeVisualState() {
|
|
1139
|
+
const state = this.getState();
|
|
1140
|
+
const params = state.visual || {};
|
|
1141
|
+
const rotation = state.rotation_state || {};
|
|
1142
|
+
const geometry = state.geometry || {};
|
|
1143
|
+
|
|
1144
|
+
// Color description from hue
|
|
1145
|
+
const hue = params.hue || 0;
|
|
1146
|
+
const colorName = hue < 15 ? 'red' : hue < 45 ? 'orange' : hue < 75 ? 'yellow' :
|
|
1147
|
+
hue < 150 ? 'green' : hue < 195 ? 'cyan' : hue < 255 ? 'blue' :
|
|
1148
|
+
hue < 285 ? 'purple' : hue < 330 ? 'magenta' : 'red';
|
|
1149
|
+
const satDesc = (params.saturation || 0.8) > 0.7 ? 'vivid' :
|
|
1150
|
+
(params.saturation || 0.8) > 0.4 ? 'moderate' : 'desaturated';
|
|
1151
|
+
const intensityDesc = (params.intensity || 0.5) > 0.7 ? 'bright' :
|
|
1152
|
+
(params.intensity || 0.5) > 0.3 ? 'medium brightness' : 'dim';
|
|
1153
|
+
|
|
1154
|
+
// Motion description
|
|
1155
|
+
const speed = params.speed || 1.0;
|
|
1156
|
+
const speedDesc = speed > 2.0 ? 'rapidly' : speed > 1.0 ? 'moderately' :
|
|
1157
|
+
speed > 0.4 ? 'slowly' : 'very slowly';
|
|
1158
|
+
const chaos = params.chaos || 0;
|
|
1159
|
+
const chaosDesc = chaos > 0.7 ? 'highly turbulent' : chaos > 0.3 ? 'somewhat organic' :
|
|
1160
|
+
chaos > 0.05 ? 'subtly alive' : 'perfectly still';
|
|
1161
|
+
|
|
1162
|
+
// 4D rotation activity
|
|
1163
|
+
const has4D = Math.abs(rotation.XW || 0) > 0.1 ||
|
|
1164
|
+
Math.abs(rotation.YW || 0) > 0.1 ||
|
|
1165
|
+
Math.abs(rotation.ZW || 0) > 0.1;
|
|
1166
|
+
const rotDesc = has4D ? 'with visible 4D hyperspace rotation (inside-out morphing)' :
|
|
1167
|
+
'in standard 3D orientation';
|
|
1168
|
+
|
|
1169
|
+
// Density/complexity
|
|
1170
|
+
const density = params.gridDensity || 10;
|
|
1171
|
+
const densityDesc = density > 50 ? 'extremely intricate' : density > 25 ? 'detailed' :
|
|
1172
|
+
density > 12 ? 'moderate detail' : 'bold and sparse';
|
|
1173
|
+
|
|
1174
|
+
// Projection
|
|
1175
|
+
const dim = params.dimension || 3.8;
|
|
1176
|
+
const projDesc = dim < 3.3 ? 'dramatic fish-eye distortion' :
|
|
1177
|
+
dim < 3.8 ? 'moderate perspective depth' : 'subtle, flattened perspective';
|
|
1178
|
+
|
|
1179
|
+
const description = [
|
|
1180
|
+
`A ${satDesc} ${colorName} ${geometry.core_type || 'base'} ${geometry.base_type || 'tetrahedron'}`,
|
|
1181
|
+
`rendered in the ${state.system || 'quantum'} system.`,
|
|
1182
|
+
`The pattern is ${densityDesc} and ${chaosDesc},`,
|
|
1183
|
+
`animating ${speedDesc} ${rotDesc}.`,
|
|
1184
|
+
`Color is ${intensityDesc} with ${projDesc}.`,
|
|
1185
|
+
params.morphFactor > 0.5 ? `Shape is morphing between geometries (factor: ${params.morphFactor}).` : ''
|
|
1186
|
+
].filter(Boolean).join(' ');
|
|
1187
|
+
|
|
1188
|
+
return {
|
|
1189
|
+
description,
|
|
1190
|
+
mood: this._assessMood(params),
|
|
1191
|
+
complexity: density > 40 ? 'high' : density > 15 ? 'medium' : 'low',
|
|
1192
|
+
motion_level: speed > 1.5 ? 'high' : speed > 0.5 ? 'medium' : 'low',
|
|
1193
|
+
has_4d_rotation: has4D,
|
|
1194
|
+
color_family: colorName,
|
|
1195
|
+
suggested_next_actions: ['set_visual_parameters', 'set_rotation', 'batch_set_parameters']
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
/**
|
|
1200
|
+
* Assess the emotional mood of the current visual state
|
|
1201
|
+
*/
|
|
1202
|
+
_assessMood(params) {
|
|
1203
|
+
const hue = params.hue || 0;
|
|
1204
|
+
const speed = params.speed || 1.0;
|
|
1205
|
+
const chaos = params.chaos || 0;
|
|
1206
|
+
const intensity = params.intensity || 0.5;
|
|
1207
|
+
|
|
1208
|
+
if (speed < 0.3 && chaos < 0.1) return 'serene';
|
|
1209
|
+
if (speed > 2.0 && chaos > 0.6) return 'chaotic';
|
|
1210
|
+
if (hue > 180 && hue < 260 && intensity < 0.5) return 'mysterious';
|
|
1211
|
+
if (hue > 0 && hue < 60 && intensity > 0.6) return 'warm';
|
|
1212
|
+
if (hue > 150 && hue < 200 && speed < 0.8) return 'tranquil';
|
|
1213
|
+
if (chaos > 0.5 && speed > 1.5) return 'energetic';
|
|
1214
|
+
if (intensity > 0.8) return 'vibrant';
|
|
1215
|
+
return 'balanced';
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
/**
|
|
1219
|
+
* Atomically set multiple parameter categories in one call
|
|
1220
|
+
*/
|
|
1221
|
+
async batchSetParameters(args) {
|
|
1222
|
+
const { system, geometry, rotation, visual, preset } = args;
|
|
1223
|
+
|
|
1224
|
+
// Switch system first if requested
|
|
1225
|
+
if (system && this.engine) {
|
|
1226
|
+
await this.engine.switchSystem(system);
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
// Set geometry
|
|
1230
|
+
if (geometry !== undefined && this.engine) {
|
|
1231
|
+
this.engine.setParameter('geometry', geometry);
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
// Set rotation
|
|
1235
|
+
if (rotation) {
|
|
1236
|
+
const rotMap = { XY: 'rot4dXY', XZ: 'rot4dXZ', YZ: 'rot4dYZ',
|
|
1237
|
+
XW: 'rot4dXW', YW: 'rot4dYW', ZW: 'rot4dZW' };
|
|
1238
|
+
for (const [key, value] of Object.entries(rotation)) {
|
|
1239
|
+
if (value !== undefined && this.engine) {
|
|
1240
|
+
this.engine.setParameter(rotMap[key], value);
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
// Set visual parameters
|
|
1246
|
+
if (visual && this.engine) {
|
|
1247
|
+
for (const [key, value] of Object.entries(visual)) {
|
|
1248
|
+
this.engine.setParameter(key, value);
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
// Apply preset last (overrides relevant params)
|
|
1253
|
+
if (preset) {
|
|
1254
|
+
this.applyBehaviorPreset({ preset });
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
telemetry.recordEvent(EventType.PARAMETER_BATCH_CHANGE, {
|
|
1258
|
+
count: (rotation ? Object.keys(rotation).length : 0) +
|
|
1259
|
+
(visual ? Object.keys(visual).length : 0) +
|
|
1260
|
+
(system ? 1 : 0) + (geometry !== undefined ? 1 : 0)
|
|
1261
|
+
});
|
|
1262
|
+
|
|
1263
|
+
return {
|
|
1264
|
+
...this.getState(),
|
|
1265
|
+
batch_applied: true,
|
|
1266
|
+
suggested_next_actions: ['describe_visual_state', 'save_to_gallery', 'create_timeline']
|
|
1267
|
+
};
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
/**
|
|
1271
|
+
* Create a ParameterTimeline from agent specification
|
|
1272
|
+
*/
|
|
1273
|
+
createTimeline(args) {
|
|
1274
|
+
const { name, duration_ms, bpm, loop_mode = 'once', tracks } = args;
|
|
1275
|
+
|
|
1276
|
+
const timelineId = generateId('timeline');
|
|
1277
|
+
|
|
1278
|
+
// Validate tracks have properly sorted keyframes
|
|
1279
|
+
const validatedTracks = {};
|
|
1280
|
+
for (const [param, keyframes] of Object.entries(tracks)) {
|
|
1281
|
+
validatedTracks[param] = keyframes
|
|
1282
|
+
.map(kf => ({
|
|
1283
|
+
time: Math.max(0, Math.min(kf.time, duration_ms)),
|
|
1284
|
+
value: kf.value,
|
|
1285
|
+
easing: kf.easing || 'easeInOut'
|
|
1286
|
+
}))
|
|
1287
|
+
.sort((a, b) => a.time - b.time);
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
// Build timeline data for ParameterTimeline consumption
|
|
1291
|
+
const timelineData = {
|
|
1292
|
+
id: timelineId,
|
|
1293
|
+
name: name || `Timeline ${timelineId}`,
|
|
1294
|
+
duration: duration_ms,
|
|
1295
|
+
bpm: bpm || null,
|
|
1296
|
+
loopMode: loop_mode,
|
|
1297
|
+
tracks: validatedTracks
|
|
1298
|
+
};
|
|
1299
|
+
|
|
1300
|
+
// If engine is available, create and start the timeline
|
|
1301
|
+
if (this.engine) {
|
|
1302
|
+
// Store for later retrieval
|
|
1303
|
+
if (!this._timelines) this._timelines = new Map();
|
|
1304
|
+
this._timelines.set(timelineId, timelineData);
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
return {
|
|
1308
|
+
timeline_id: timelineId,
|
|
1309
|
+
name: timelineData.name,
|
|
1310
|
+
duration_ms,
|
|
1311
|
+
bpm: bpm || null,
|
|
1312
|
+
loop_mode,
|
|
1313
|
+
track_count: Object.keys(validatedTracks).length,
|
|
1314
|
+
tracks_summary: Object.entries(validatedTracks).map(([param, kfs]) => ({
|
|
1315
|
+
parameter: param,
|
|
1316
|
+
keyframe_count: kfs.length,
|
|
1317
|
+
value_range: [
|
|
1318
|
+
Math.min(...kfs.map(k => k.value)),
|
|
1319
|
+
Math.max(...kfs.map(k => k.value))
|
|
1320
|
+
]
|
|
1321
|
+
})),
|
|
1322
|
+
load_code: `const tl = new ParameterTimeline((n, v) => engine.setParameter(n, v));\ntl.importTimeline(${JSON.stringify(timelineData)});\ntl.play();`,
|
|
1323
|
+
suggested_next_actions: ['play_transition', 'describe_visual_state', 'save_to_gallery']
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
/**
|
|
1328
|
+
* Play a smooth transition sequence
|
|
1329
|
+
*/
|
|
1330
|
+
playTransition(args) {
|
|
1331
|
+
const { sequence } = args;
|
|
1332
|
+
|
|
1333
|
+
const transitionId = generateId('transition');
|
|
1334
|
+
|
|
1335
|
+
// Validate and normalize the sequence
|
|
1336
|
+
const normalizedSequence = sequence.map((step, i) => ({
|
|
1337
|
+
params: step.params,
|
|
1338
|
+
duration: step.duration || 1000,
|
|
1339
|
+
easing: step.easing || 'easeInOut',
|
|
1340
|
+
delay: step.delay || 0
|
|
1341
|
+
}));
|
|
1342
|
+
|
|
1343
|
+
const totalDuration = normalizedSequence.reduce(
|
|
1344
|
+
(sum, step) => sum + step.duration + step.delay, 0
|
|
1345
|
+
);
|
|
1346
|
+
|
|
1347
|
+
// Execute live if engine available
|
|
1348
|
+
let executing = false;
|
|
1349
|
+
const animator = this._getTransitionAnimator();
|
|
1350
|
+
if (animator) {
|
|
1351
|
+
const seqId = animator.sequence(normalizedSequence);
|
|
1352
|
+
executing = !!seqId;
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
return {
|
|
1356
|
+
transition_id: transitionId,
|
|
1357
|
+
executing,
|
|
1358
|
+
step_count: normalizedSequence.length,
|
|
1359
|
+
total_duration_ms: totalDuration,
|
|
1360
|
+
steps: normalizedSequence.map((step, i) => ({
|
|
1361
|
+
index: i,
|
|
1362
|
+
params: Object.keys(step.params),
|
|
1363
|
+
duration: step.duration,
|
|
1364
|
+
easing: step.easing,
|
|
1365
|
+
delay: step.delay
|
|
1366
|
+
})),
|
|
1367
|
+
load_code: executing ? null : `const animator = new TransitionAnimator(\n (n, v) => engine.setParameter(n, v),\n (n) => engine.getParameter(n)\n);\nanimator.sequence(${JSON.stringify(normalizedSequence)});`,
|
|
1368
|
+
suggested_next_actions: ['describe_visual_state', 'create_timeline', 'save_to_gallery']
|
|
1369
|
+
};
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
/**
|
|
1373
|
+
* Apply a named color preset
|
|
1374
|
+
*/
|
|
1375
|
+
applyColorPreset(args) {
|
|
1376
|
+
const { preset, transition = true, duration = 800 } = args;
|
|
1377
|
+
|
|
1378
|
+
const colorSystem = this._getColorPresets();
|
|
1379
|
+
|
|
1380
|
+
if (colorSystem) {
|
|
1381
|
+
// Use real ColorPresetsSystem — full preset library with transitions
|
|
1382
|
+
const config = colorSystem.getPreset(preset);
|
|
1383
|
+
if (!config) {
|
|
1384
|
+
const allPresets = colorSystem.getPresets().map(p => p.name);
|
|
1385
|
+
return {
|
|
1386
|
+
error: {
|
|
1387
|
+
type: 'ValidationError',
|
|
1388
|
+
code: 'INVALID_COLOR_PRESET',
|
|
1389
|
+
message: `Unknown color preset: ${preset}`,
|
|
1390
|
+
valid_options: allPresets
|
|
1391
|
+
}
|
|
1392
|
+
};
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
colorSystem.applyPreset(preset, transition, duration);
|
|
1396
|
+
|
|
1397
|
+
return {
|
|
1398
|
+
preset,
|
|
1399
|
+
applied: { hue: config.hue, saturation: config.saturation, intensity: config.intensity },
|
|
1400
|
+
transition: transition ? { enabled: true, duration } : { enabled: false },
|
|
1401
|
+
full_config: config,
|
|
1402
|
+
suggested_next_actions: ['set_post_processing', 'describe_visual_state', 'set_visual_parameters']
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
// Fallback: no engine, return preset metadata for artifact mode
|
|
1407
|
+
return {
|
|
1408
|
+
preset,
|
|
1409
|
+
applied: null,
|
|
1410
|
+
load_code: `const colors = new ColorPresetsSystem((n, v) => engine.setParameter(n, v));\ncolors.applyPreset('${preset}', ${transition}, ${duration});`,
|
|
1411
|
+
suggested_next_actions: ['set_post_processing', 'describe_visual_state', 'set_visual_parameters']
|
|
1412
|
+
};
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
/**
|
|
1416
|
+
* Configure post-processing effects pipeline
|
|
1417
|
+
*/
|
|
1418
|
+
setPostProcessing(args) {
|
|
1419
|
+
const { effects, chain_preset, clear_first = true } = args;
|
|
1420
|
+
|
|
1421
|
+
// Try to execute live in browser context
|
|
1422
|
+
let executing = false;
|
|
1423
|
+
if (typeof document !== 'undefined') {
|
|
1424
|
+
try {
|
|
1425
|
+
const target = document.getElementById('viz-container')
|
|
1426
|
+
|| document.querySelector('.vib3-container')
|
|
1427
|
+
|| document.querySelector('canvas')?.parentElement;
|
|
1428
|
+
|
|
1429
|
+
if (target) {
|
|
1430
|
+
// Lazy-init pipeline, importing dynamically to avoid Node.js issues
|
|
1431
|
+
if (!this._postPipeline) {
|
|
1432
|
+
// PostProcessingPipeline imported statically would fail in Node;
|
|
1433
|
+
// it's already a known browser-only module, so guard at runtime
|
|
1434
|
+
const { PostProcessingPipeline: PPP } = { PostProcessingPipeline: globalThis.PostProcessingPipeline };
|
|
1435
|
+
if (PPP) {
|
|
1436
|
+
this._postPipeline = new PPP(target);
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
if (this._postPipeline) {
|
|
1441
|
+
if (clear_first) this._postPipeline.clearChain?.();
|
|
1442
|
+
if (chain_preset) {
|
|
1443
|
+
this._postPipeline.loadPresetChain(chain_preset);
|
|
1444
|
+
} else if (effects) {
|
|
1445
|
+
for (const e of effects) {
|
|
1446
|
+
this._postPipeline.addEffect(e.name, { intensity: e.intensity || 0.5, ...e });
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
this._postPipeline.apply();
|
|
1450
|
+
executing = true;
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
} catch { /* fall through to code generation */ }
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
return {
|
|
1457
|
+
applied: true,
|
|
1458
|
+
executing,
|
|
1459
|
+
effects: effects || [],
|
|
1460
|
+
chain_preset: chain_preset || null,
|
|
1461
|
+
cleared_previous: clear_first,
|
|
1462
|
+
load_code: executing ? null : (effects ?
|
|
1463
|
+
`const pipeline = new PostProcessingPipeline(document.getElementById('viz-container'));\n${effects.map(e => `pipeline.addEffect('${e.name}', { intensity: ${e.intensity || 0.5} });`).join('\n')}\npipeline.apply();` :
|
|
1464
|
+
`const pipeline = new PostProcessingPipeline(document.getElementById('viz-container'));\npipeline.loadPresetChain('${chain_preset}');\npipeline.apply();`),
|
|
1465
|
+
suggested_next_actions: ['describe_visual_state', 'apply_color_preset', 'create_choreography']
|
|
1466
|
+
};
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
/**
|
|
1470
|
+
* Create a multi-scene choreography — the most powerful agent composition tool
|
|
1471
|
+
*/
|
|
1472
|
+
createChoreography(args) {
|
|
1473
|
+
const { name, duration_ms, bpm, scenes } = args;
|
|
1474
|
+
|
|
1475
|
+
const choreographyId = generateId('choreo');
|
|
1476
|
+
|
|
1477
|
+
// Validate scene time ranges don't exceed duration
|
|
1478
|
+
const validatedScenes = scenes.map((scene, i) => ({
|
|
1479
|
+
index: i,
|
|
1480
|
+
time_start: Math.max(0, scene.time_start),
|
|
1481
|
+
time_end: Math.min(scene.time_end, duration_ms),
|
|
1482
|
+
system: scene.system,
|
|
1483
|
+
geometry: scene.geometry ?? 0,
|
|
1484
|
+
transition_in: scene.transition_in || { type: 'cut', duration: 0 },
|
|
1485
|
+
tracks: scene.tracks || {},
|
|
1486
|
+
color_preset: scene.color_preset || null,
|
|
1487
|
+
post_processing: scene.post_processing || [],
|
|
1488
|
+
audio: scene.audio || null
|
|
1489
|
+
}));
|
|
1490
|
+
|
|
1491
|
+
const choreography = {
|
|
1492
|
+
id: choreographyId,
|
|
1493
|
+
name: name || `Choreography ${choreographyId}`,
|
|
1494
|
+
duration_ms,
|
|
1495
|
+
bpm: bpm || null,
|
|
1496
|
+
scene_count: validatedScenes.length,
|
|
1497
|
+
scenes: validatedScenes
|
|
1498
|
+
};
|
|
1499
|
+
|
|
1500
|
+
// Store for later retrieval
|
|
1501
|
+
if (!this._choreographies) this._choreographies = new Map();
|
|
1502
|
+
this._choreographies.set(choreographyId, choreography);
|
|
1503
|
+
|
|
1504
|
+
return {
|
|
1505
|
+
choreography_id: choreographyId,
|
|
1506
|
+
name: choreography.name,
|
|
1507
|
+
duration_ms,
|
|
1508
|
+
bpm: bpm || null,
|
|
1509
|
+
scene_count: validatedScenes.length,
|
|
1510
|
+
scenes_summary: validatedScenes.map(s => ({
|
|
1511
|
+
index: s.index,
|
|
1512
|
+
time: `${s.time_start}ms → ${s.time_end}ms`,
|
|
1513
|
+
system: s.system,
|
|
1514
|
+
geometry: s.geometry,
|
|
1515
|
+
transition: s.transition_in.type,
|
|
1516
|
+
track_count: Object.keys(s.tracks).length,
|
|
1517
|
+
color_preset: s.color_preset,
|
|
1518
|
+
effects: s.post_processing
|
|
1519
|
+
})),
|
|
1520
|
+
choreography_json: JSON.stringify(choreography, null, 2),
|
|
1521
|
+
suggested_next_actions: ['describe_visual_state', 'export_package']
|
|
1522
|
+
};
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
// ===== VISUAL FEEDBACK TOOLS (Phase 7.1 — Agent Harness) =====
|
|
1526
|
+
|
|
1527
|
+
/**
|
|
1528
|
+
* Capture the current visualization as a base64 PNG by compositing all canvas layers.
|
|
1529
|
+
* Only works in browser context where canvases exist.
|
|
1530
|
+
*/
|
|
1531
|
+
async captureScreenshot(args) {
|
|
1532
|
+
const { width = 512, height = 512, format = 'png', quality = 0.92 } = args;
|
|
1533
|
+
|
|
1534
|
+
const isBrowser = typeof document !== 'undefined';
|
|
1535
|
+
if (!isBrowser) {
|
|
1536
|
+
return {
|
|
1537
|
+
error: {
|
|
1538
|
+
type: 'EnvironmentError',
|
|
1539
|
+
code: 'NO_BROWSER_CONTEXT',
|
|
1540
|
+
message: 'capture_screenshot requires a browser context with canvas elements',
|
|
1541
|
+
suggestion: 'Use the headless renderer tool (tools/headless-renderer.js) for Node.js environments'
|
|
1542
|
+
}
|
|
1543
|
+
};
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
try {
|
|
1547
|
+
// Create composite canvas
|
|
1548
|
+
const composite = document.createElement('canvas');
|
|
1549
|
+
composite.width = width;
|
|
1550
|
+
composite.height = height;
|
|
1551
|
+
const ctx = composite.getContext('2d');
|
|
1552
|
+
|
|
1553
|
+
if (!ctx) {
|
|
1554
|
+
return {
|
|
1555
|
+
error: { type: 'SystemError', code: 'CANVAS_CONTEXT_FAILED', message: 'Could not get 2D context for compositing' }
|
|
1556
|
+
};
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
// Fill with black background
|
|
1560
|
+
ctx.fillStyle = '#000000';
|
|
1561
|
+
ctx.fillRect(0, 0, width, height);
|
|
1562
|
+
|
|
1563
|
+
// Find all visualization canvases and composite them in z-order
|
|
1564
|
+
const canvases = document.querySelectorAll('canvas.visualization-canvas');
|
|
1565
|
+
const sortedCanvases = Array.from(canvases).sort((a, b) => {
|
|
1566
|
+
const zA = parseInt(a.style.zIndex || '0', 10);
|
|
1567
|
+
const zB = parseInt(b.style.zIndex || '0', 10);
|
|
1568
|
+
return zA - zB;
|
|
1569
|
+
});
|
|
1570
|
+
|
|
1571
|
+
for (const canvas of sortedCanvases) {
|
|
1572
|
+
if (canvas.width > 0 && canvas.height > 0) {
|
|
1573
|
+
ctx.drawImage(canvas, 0, 0, width, height);
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
// If no canvases found, try to find any canvas at all
|
|
1578
|
+
if (sortedCanvases.length === 0) {
|
|
1579
|
+
const anyCanvas = document.querySelector('canvas');
|
|
1580
|
+
if (anyCanvas && anyCanvas.width > 0) {
|
|
1581
|
+
ctx.drawImage(anyCanvas, 0, 0, width, height);
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
// Convert to data URL
|
|
1586
|
+
const mimeType = format === 'jpeg' ? 'image/jpeg' : format === 'webp' ? 'image/webp' : 'image/png';
|
|
1587
|
+
const dataUrl = composite.toDataURL(mimeType, quality);
|
|
1588
|
+
const base64 = dataUrl.split(',')[1];
|
|
1589
|
+
|
|
1590
|
+
// Clean up
|
|
1591
|
+
composite.remove();
|
|
1592
|
+
|
|
1593
|
+
return {
|
|
1594
|
+
format,
|
|
1595
|
+
width,
|
|
1596
|
+
height,
|
|
1597
|
+
mime_type: mimeType,
|
|
1598
|
+
data_url: dataUrl,
|
|
1599
|
+
base64_length: base64.length,
|
|
1600
|
+
canvas_count: sortedCanvases.length,
|
|
1601
|
+
suggested_next_actions: ['describe_visual_state', 'set_visual_parameters', 'batch_set_parameters']
|
|
1602
|
+
};
|
|
1603
|
+
} catch (err) {
|
|
1604
|
+
return {
|
|
1605
|
+
error: {
|
|
1606
|
+
type: 'SystemError',
|
|
1607
|
+
code: 'SCREENSHOT_FAILED',
|
|
1608
|
+
message: err.message,
|
|
1609
|
+
suggestion: 'Check that canvas elements exist and are rendered'
|
|
1610
|
+
}
|
|
1611
|
+
};
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
/**
|
|
1616
|
+
* Map a natural-language description to VIB3+ parameters using AestheticMapper.
|
|
1617
|
+
*/
|
|
1618
|
+
async designFromDescription(args) {
|
|
1619
|
+
const { description, apply = false } = args;
|
|
1620
|
+
|
|
1621
|
+
if (!this._aestheticMapper) {
|
|
1622
|
+
this._aestheticMapper = new AestheticMapper();
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
const mapped = this._aestheticMapper.mapDescription(description);
|
|
1626
|
+
const resolved = this._aestheticMapper.resolveToValues(description);
|
|
1627
|
+
|
|
1628
|
+
// Apply to engine if requested
|
|
1629
|
+
if (apply && this.engine) {
|
|
1630
|
+
if (resolved.system) {
|
|
1631
|
+
await this.engine.switchSystem(resolved.system);
|
|
1632
|
+
}
|
|
1633
|
+
if (resolved.geometry !== undefined) {
|
|
1634
|
+
this.engine.setParameter('geometry', resolved.geometry);
|
|
1635
|
+
}
|
|
1636
|
+
for (const [param, value] of Object.entries(resolved.params)) {
|
|
1637
|
+
this.engine.setParameter(param, value);
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
return {
|
|
1642
|
+
description,
|
|
1643
|
+
applied: apply,
|
|
1644
|
+
matched_words: mapped.matched_words,
|
|
1645
|
+
total_words: mapped.total_words,
|
|
1646
|
+
resolved: {
|
|
1647
|
+
system: resolved.system,
|
|
1648
|
+
geometry: resolved.geometry,
|
|
1649
|
+
params: resolved.params,
|
|
1650
|
+
color_preset: resolved.color_preset,
|
|
1651
|
+
post_processing: resolved.post_processing
|
|
1652
|
+
},
|
|
1653
|
+
parameter_ranges: mapped.params,
|
|
1654
|
+
suggested_next_actions: apply
|
|
1655
|
+
? ['describe_visual_state', 'capture_screenshot', 'create_timeline']
|
|
1656
|
+
: ['design_from_description', 'batch_set_parameters']
|
|
1657
|
+
};
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
/**
|
|
1661
|
+
* Return the full aesthetic vocabulary by category.
|
|
1662
|
+
*/
|
|
1663
|
+
getAestheticVocabulary() {
|
|
1664
|
+
if (!this._aestheticMapper) {
|
|
1665
|
+
this._aestheticMapper = new AestheticMapper();
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
return {
|
|
1669
|
+
vocabulary: this._aestheticMapper.getVocabularyByCategory(),
|
|
1670
|
+
all_words: this._aestheticMapper.getVocabulary(),
|
|
1671
|
+
word_count: this._aestheticMapper.getVocabulary().length,
|
|
1672
|
+
usage: 'Pass space-separated words to design_from_description. Example: "serene ocean deep minimal"',
|
|
1673
|
+
suggested_next_actions: ['design_from_description']
|
|
1674
|
+
};
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
/**
|
|
1678
|
+
* Load and play a choreography.
|
|
1679
|
+
*/
|
|
1680
|
+
async playChoreographyTool(args) {
|
|
1681
|
+
const { choreography, choreography_id, action = 'play', seek_percent, loop = false } = args;
|
|
1682
|
+
|
|
1683
|
+
// Resolve choreography spec
|
|
1684
|
+
let spec = choreography;
|
|
1685
|
+
if (!spec && choreography_id && this._choreographies) {
|
|
1686
|
+
spec = this._choreographies.get(choreography_id);
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
if (!spec && action === 'play') {
|
|
1690
|
+
return {
|
|
1691
|
+
error: {
|
|
1692
|
+
type: 'ValidationError',
|
|
1693
|
+
code: 'NO_CHOREOGRAPHY',
|
|
1694
|
+
message: 'Provide a choreography spec or a valid choreography_id',
|
|
1695
|
+
suggestion: 'Use create_choreography first, then pass the result here'
|
|
1696
|
+
}
|
|
1697
|
+
};
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
// Create or reuse player
|
|
1701
|
+
if (!this._choreographyPlayer && this.engine) {
|
|
1702
|
+
this._choreographyPlayer = new ChoreographyPlayer(this.engine, {
|
|
1703
|
+
onSceneChange: (index, scene) => {
|
|
1704
|
+
telemetry.recordEvent(EventType.PARAMETER_CHANGE, {
|
|
1705
|
+
type: 'choreography_scene',
|
|
1706
|
+
scene_index: index,
|
|
1707
|
+
system: scene.system
|
|
1708
|
+
});
|
|
1709
|
+
}
|
|
1710
|
+
});
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
if (!this._choreographyPlayer) {
|
|
1714
|
+
return {
|
|
1715
|
+
error: {
|
|
1716
|
+
type: 'SystemError',
|
|
1717
|
+
code: 'NO_ENGINE',
|
|
1718
|
+
message: 'Engine not initialized — cannot play choreography',
|
|
1719
|
+
suggestion: 'Initialize the VIB3Engine first'
|
|
1720
|
+
}
|
|
1721
|
+
};
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
const player = this._choreographyPlayer;
|
|
1725
|
+
|
|
1726
|
+
switch (action) {
|
|
1727
|
+
case 'play':
|
|
1728
|
+
if (spec) {
|
|
1729
|
+
player.load(spec);
|
|
1730
|
+
player.loopMode = loop ? 'loop' : 'once';
|
|
1731
|
+
}
|
|
1732
|
+
player.play();
|
|
1733
|
+
break;
|
|
1734
|
+
case 'pause':
|
|
1735
|
+
player.pause();
|
|
1736
|
+
break;
|
|
1737
|
+
case 'stop':
|
|
1738
|
+
player.stop();
|
|
1739
|
+
break;
|
|
1740
|
+
case 'seek':
|
|
1741
|
+
if (seek_percent !== undefined) {
|
|
1742
|
+
player.seekToPercent(seek_percent);
|
|
1743
|
+
}
|
|
1744
|
+
break;
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
return {
|
|
1748
|
+
action,
|
|
1749
|
+
state: player.getState(),
|
|
1750
|
+
suggested_next_actions: action === 'play'
|
|
1751
|
+
? ['play_choreography', 'capture_screenshot', 'describe_visual_state']
|
|
1752
|
+
: ['play_choreography']
|
|
1753
|
+
};
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
/**
|
|
1757
|
+
* Control a previously created timeline.
|
|
1758
|
+
*/
|
|
1759
|
+
controlTimeline(args) {
|
|
1760
|
+
const { timeline_id, action, seek_percent, speed } = args;
|
|
1761
|
+
|
|
1762
|
+
if (!this._liveTimelines) this._liveTimelines = new Map();
|
|
1763
|
+
|
|
1764
|
+
let tl = this._liveTimelines.get(timeline_id);
|
|
1765
|
+
|
|
1766
|
+
// If timeline not live yet, try to create it from stored data
|
|
1767
|
+
if (!tl && this._timelines && this._timelines.has(timeline_id) && this.engine) {
|
|
1768
|
+
const data = this._timelines.get(timeline_id);
|
|
1769
|
+
|
|
1770
|
+
tl = new ParameterTimeline(
|
|
1771
|
+
(name, value) => this.engine.setParameter(name, value)
|
|
1772
|
+
);
|
|
1773
|
+
|
|
1774
|
+
// Build import-compatible format
|
|
1775
|
+
const importData = {
|
|
1776
|
+
type: 'vib3-parameter-timeline',
|
|
1777
|
+
version: '1.0.0',
|
|
1778
|
+
duration: data.duration,
|
|
1779
|
+
loopMode: data.loopMode || 'once',
|
|
1780
|
+
bpm: data.bpm || 120,
|
|
1781
|
+
tracks: {}
|
|
1782
|
+
};
|
|
1783
|
+
|
|
1784
|
+
for (const [param, keyframes] of Object.entries(data.tracks)) {
|
|
1785
|
+
importData.tracks[param] = {
|
|
1786
|
+
enabled: true,
|
|
1787
|
+
keyframes: keyframes.map(kf => ({
|
|
1788
|
+
time: kf.time,
|
|
1789
|
+
value: kf.value,
|
|
1790
|
+
easing: kf.easing || 'easeInOut'
|
|
1791
|
+
}))
|
|
1792
|
+
};
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
tl.importTimeline(importData);
|
|
1796
|
+
this._liveTimelines.set(timeline_id, tl);
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
if (!tl) {
|
|
1800
|
+
return {
|
|
1801
|
+
error: {
|
|
1802
|
+
type: 'ValidationError',
|
|
1803
|
+
code: 'TIMELINE_NOT_FOUND',
|
|
1804
|
+
message: `Timeline '${timeline_id}' not found`,
|
|
1805
|
+
suggestion: 'Create a timeline first with create_timeline'
|
|
1806
|
+
}
|
|
1807
|
+
};
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
switch (action) {
|
|
1811
|
+
case 'play':
|
|
1812
|
+
tl.play();
|
|
1813
|
+
break;
|
|
1814
|
+
case 'pause':
|
|
1815
|
+
tl.pause();
|
|
1816
|
+
break;
|
|
1817
|
+
case 'stop':
|
|
1818
|
+
tl.stop();
|
|
1819
|
+
break;
|
|
1820
|
+
case 'seek':
|
|
1821
|
+
if (seek_percent !== undefined) {
|
|
1822
|
+
tl.seekToPercent(seek_percent);
|
|
1823
|
+
}
|
|
1824
|
+
break;
|
|
1825
|
+
case 'set_speed':
|
|
1826
|
+
if (speed !== undefined) {
|
|
1827
|
+
tl.playbackSpeed = Math.max(0.1, Math.min(10, speed));
|
|
1828
|
+
}
|
|
1829
|
+
break;
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
return {
|
|
1833
|
+
timeline_id,
|
|
1834
|
+
action,
|
|
1835
|
+
playing: tl.playing,
|
|
1836
|
+
currentTime: tl.currentTime,
|
|
1837
|
+
duration: tl.duration,
|
|
1838
|
+
progress: tl.duration > 0 ? tl.currentTime / tl.duration : 0,
|
|
1839
|
+
playbackSpeed: tl.playbackSpeed,
|
|
1840
|
+
suggested_next_actions: ['control_timeline', 'describe_visual_state', 'capture_screenshot']
|
|
1841
|
+
};
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
// ====================================================================
|
|
1845
|
+
// Layer Relationship Tools (Phase 8)
|
|
1846
|
+
// ====================================================================
|
|
1847
|
+
|
|
1848
|
+
/**
|
|
1849
|
+
* Get the holographic system's layer graph (if available).
|
|
1850
|
+
* @private
|
|
1851
|
+
* @returns {import('../../render/LayerRelationshipGraph.js').LayerRelationshipGraph|null}
|
|
1852
|
+
*/
|
|
1853
|
+
_getLayerGraph() {
|
|
1854
|
+
if (!this.engine) return null;
|
|
1855
|
+
// Try to access the current system's layer graph
|
|
1856
|
+
const system = this.engine.currentSystem || this.engine._activeSystem;
|
|
1857
|
+
if (system && system.layerGraph) {
|
|
1858
|
+
return system.layerGraph;
|
|
1859
|
+
}
|
|
1860
|
+
if (system && system._layerGraph) {
|
|
1861
|
+
return system._layerGraph;
|
|
1862
|
+
}
|
|
1863
|
+
return null;
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
/**
|
|
1867
|
+
* Load a named layer relationship profile.
|
|
1868
|
+
*/
|
|
1869
|
+
setLayerProfile(args) {
|
|
1870
|
+
const { profile } = args;
|
|
1871
|
+
const graph = this._getLayerGraph();
|
|
1872
|
+
|
|
1873
|
+
if (!graph) {
|
|
1874
|
+
return {
|
|
1875
|
+
error: 'Layer relationship graph not available. Switch to holographic system first.',
|
|
1876
|
+
suggested_next_actions: ['switch_system']
|
|
1877
|
+
};
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
graph.loadProfile(profile);
|
|
1881
|
+
telemetry.recordEvent(EventType.PARAMETER_CHANGE, { type: 'layer_profile', profile });
|
|
1882
|
+
|
|
1883
|
+
return {
|
|
1884
|
+
profile,
|
|
1885
|
+
keystone: graph.keystone,
|
|
1886
|
+
active_profile: graph.activeProfile,
|
|
1887
|
+
available_profiles: ['holographic', 'symmetry', 'chord', 'storm', 'legacy'],
|
|
1888
|
+
suggested_next_actions: ['get_layer_config', 'set_layer_relationship', 'tune_layer_relationship']
|
|
1889
|
+
};
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
/**
|
|
1893
|
+
* Set relationship for a specific layer.
|
|
1894
|
+
*/
|
|
1895
|
+
setLayerRelationship(args) {
|
|
1896
|
+
const { layer, relationship, config } = args;
|
|
1897
|
+
const graph = this._getLayerGraph();
|
|
1898
|
+
|
|
1899
|
+
if (!graph) {
|
|
1900
|
+
return {
|
|
1901
|
+
error: 'Layer relationship graph not available. Switch to holographic system first.',
|
|
1902
|
+
suggested_next_actions: ['switch_system']
|
|
1903
|
+
};
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
if (config) {
|
|
1907
|
+
graph.setRelationship(layer, { preset: relationship, config });
|
|
1908
|
+
} else {
|
|
1909
|
+
graph.setRelationship(layer, relationship);
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
telemetry.recordEvent(EventType.PARAMETER_CHANGE, {
|
|
1913
|
+
type: 'layer_relationship', layer, relationship
|
|
1914
|
+
});
|
|
1915
|
+
|
|
1916
|
+
return {
|
|
1917
|
+
layer,
|
|
1918
|
+
relationship,
|
|
1919
|
+
config: config || {},
|
|
1920
|
+
keystone: graph.keystone,
|
|
1921
|
+
suggested_next_actions: ['get_layer_config', 'tune_layer_relationship', 'describe_visual_state']
|
|
1922
|
+
};
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
/**
|
|
1926
|
+
* Change the keystone (driver) layer.
|
|
1927
|
+
*/
|
|
1928
|
+
setLayerKeystone(args) {
|
|
1929
|
+
const { layer } = args;
|
|
1930
|
+
const graph = this._getLayerGraph();
|
|
1931
|
+
|
|
1932
|
+
if (!graph) {
|
|
1933
|
+
return {
|
|
1934
|
+
error: 'Layer relationship graph not available. Switch to holographic system first.',
|
|
1935
|
+
suggested_next_actions: ['switch_system']
|
|
1936
|
+
};
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
graph.setKeystone(layer);
|
|
1940
|
+
telemetry.recordEvent(EventType.PARAMETER_CHANGE, { type: 'layer_keystone', layer });
|
|
1941
|
+
|
|
1942
|
+
return {
|
|
1943
|
+
keystone: layer,
|
|
1944
|
+
note: 'Other layers\' relationships are preserved. Set new relationships for the old keystone if needed.',
|
|
1945
|
+
suggested_next_actions: ['set_layer_relationship', 'get_layer_config']
|
|
1946
|
+
};
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
/**
|
|
1950
|
+
* Get current layer configuration.
|
|
1951
|
+
*/
|
|
1952
|
+
getLayerConfig() {
|
|
1953
|
+
const graph = this._getLayerGraph();
|
|
1954
|
+
|
|
1955
|
+
if (!graph) {
|
|
1956
|
+
return {
|
|
1957
|
+
error: 'Layer relationship graph not available. Switch to holographic system first.',
|
|
1958
|
+
suggested_next_actions: ['switch_system']
|
|
1959
|
+
};
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
const config = graph.exportConfig();
|
|
1963
|
+
|
|
1964
|
+
return {
|
|
1965
|
+
keystone: config.keystone,
|
|
1966
|
+
active_profile: config.profile,
|
|
1967
|
+
relationships: config.relationships,
|
|
1968
|
+
shaders: config.shaders,
|
|
1969
|
+
available_profiles: ['holographic', 'symmetry', 'chord', 'storm', 'legacy'],
|
|
1970
|
+
available_presets: ['echo', 'mirror', 'complement', 'harmonic', 'reactive', 'chase'],
|
|
1971
|
+
suggested_next_actions: ['set_layer_profile', 'set_layer_relationship', 'tune_layer_relationship']
|
|
1972
|
+
};
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
/**
|
|
1976
|
+
* Tune a layer's relationship config.
|
|
1977
|
+
*/
|
|
1978
|
+
tuneLayerRelationship(args) {
|
|
1979
|
+
const { layer, config: configOverrides } = args;
|
|
1980
|
+
const graph = this._getLayerGraph();
|
|
1981
|
+
|
|
1982
|
+
if (!graph) {
|
|
1983
|
+
return {
|
|
1984
|
+
error: 'Layer relationship graph not available. Switch to holographic system first.',
|
|
1985
|
+
suggested_next_actions: ['switch_system']
|
|
1986
|
+
};
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
const graphConfig = graph.exportConfig();
|
|
1990
|
+
const currentRel = graphConfig.relationships[layer];
|
|
1991
|
+
|
|
1992
|
+
if (!currentRel || !currentRel.preset) {
|
|
1993
|
+
return {
|
|
1994
|
+
error: `Layer "${layer}" has no tunable preset relationship. Set one first with set_layer_relationship.`,
|
|
1995
|
+
suggested_next_actions: ['set_layer_relationship']
|
|
1996
|
+
};
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
const factory = PRESET_REGISTRY[currentRel.preset];
|
|
2000
|
+
if (!factory) {
|
|
2001
|
+
return {
|
|
2002
|
+
error: `Unknown preset "${currentRel.preset}" on layer "${layer}".`,
|
|
2003
|
+
suggested_next_actions: ['set_layer_relationship']
|
|
2004
|
+
};
|
|
2005
|
+
}
|
|
2006
|
+
|
|
2007
|
+
const newConfig = { ...(currentRel.config || {}), ...configOverrides };
|
|
2008
|
+
graph.setRelationship(layer, { preset: currentRel.preset, config: newConfig });
|
|
2009
|
+
|
|
2010
|
+
telemetry.recordEvent(EventType.PARAMETER_CHANGE, {
|
|
2011
|
+
type: 'layer_tune', layer, tuned_keys: Object.keys(configOverrides)
|
|
2012
|
+
});
|
|
2013
|
+
|
|
2014
|
+
return {
|
|
2015
|
+
layer,
|
|
2016
|
+
preset: currentRel.preset,
|
|
2017
|
+
previous_config: currentRel.config,
|
|
2018
|
+
new_config: newConfig,
|
|
2019
|
+
suggested_next_actions: ['get_layer_config', 'describe_visual_state', 'capture_screenshot']
|
|
2020
|
+
};
|
|
2021
|
+
}
|
|
946
2022
|
}
|
|
947
2023
|
|
|
948
2024
|
// Singleton instance
|