@vib3code/sdk 2.0.1 → 2.0.3-canary.0c55e5a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/DOCS/AGENT_HARNESS_ARCHITECTURE.md +243 -0
- package/DOCS/CLI_ONBOARDING.md +1 -1
- package/DOCS/CROSS_SITE_DESIGN_PATTERNS.md +117 -0
- package/DOCS/EPIC_SCROLL_EVENTS.md +773 -0
- package/DOCS/HANDOFF_LANDING_PAGE.md +154 -0
- package/DOCS/HANDOFF_SDK_DEVELOPMENT.md +493 -0
- package/DOCS/MULTIVIZ_CHOREOGRAPHY_PATTERNS.md +937 -0
- package/DOCS/PRODUCT_STRATEGY.md +63 -0
- package/DOCS/README.md +103 -0
- package/DOCS/REFERENCE_SCROLL_ANALYSIS.md +97 -0
- package/DOCS/ROADMAP.md +111 -0
- package/DOCS/SCROLL_TIMELINE_v3.md +269 -0
- package/DOCS/SITE_REFACTOR_PLAN.md +100 -0
- package/DOCS/STATUS.md +24 -0
- package/DOCS/SYSTEM_INVENTORY.md +33 -30
- package/DOCS/VISUAL_ANALYSIS_CLICKERSS.md +85 -0
- package/DOCS/VISUAL_ANALYSIS_FACETAD.md +133 -0
- package/DOCS/VISUAL_ANALYSIS_SIMONE.md +95 -0
- package/DOCS/VISUAL_ANALYSIS_TABLESIDE.md +86 -0
- package/DOCS/{BLUEPRINT_EXECUTION_PLAN_2026-01-07.md → archive/BLUEPRINT_EXECUTION_PLAN_2026-01-07.md} +1 -1
- package/DOCS/{DEV_TRACK_ANALYSIS.md → archive/DEV_TRACK_ANALYSIS.md} +3 -0
- package/DOCS/{SYSTEM_AUDIT_2026-01-30.md → archive/SYSTEM_AUDIT_2026-01-30.md} +3 -0
- package/DOCS/{DEV_TRACK_SESSION_2026-01-31.md → dev-tracks/DEV_TRACK_SESSION_2026-01-31.md} +1 -1
- package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-06.md +231 -0
- package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-13.md +127 -0
- package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-15.md +142 -0
- package/DOCS/dev-tracks/README.md +10 -0
- package/README.md +26 -13
- package/cpp/CMakeLists.txt +236 -0
- package/cpp/bindings/embind.cpp +269 -0
- package/cpp/build.sh +129 -0
- package/cpp/geometry/Crystal.cpp +103 -0
- package/cpp/geometry/Fractal.cpp +136 -0
- package/cpp/geometry/GeometryGenerator.cpp +262 -0
- package/cpp/geometry/KleinBottle.cpp +71 -0
- package/cpp/geometry/Sphere.cpp +134 -0
- package/cpp/geometry/Tesseract.cpp +94 -0
- package/cpp/geometry/Tetrahedron.cpp +83 -0
- package/cpp/geometry/Torus.cpp +65 -0
- package/cpp/geometry/WarpFunctions.cpp +238 -0
- package/cpp/geometry/Wave.cpp +85 -0
- package/cpp/include/vib3_ffi.h +238 -0
- package/cpp/math/Mat4x4.cpp +409 -0
- package/cpp/math/Mat4x4.hpp +209 -0
- package/cpp/math/Projection.cpp +142 -0
- package/cpp/math/Projection.hpp +148 -0
- package/cpp/math/Rotor4D.cpp +322 -0
- package/cpp/math/Rotor4D.hpp +204 -0
- package/cpp/math/Vec4.cpp +303 -0
- package/cpp/math/Vec4.hpp +225 -0
- package/cpp/src/vib3_ffi.cpp +607 -0
- package/cpp/tests/Geometry_test.cpp +213 -0
- package/cpp/tests/Mat4x4_test.cpp +494 -0
- package/cpp/tests/Projection_test.cpp +298 -0
- package/cpp/tests/Rotor4D_test.cpp +423 -0
- package/cpp/tests/Vec4_test.cpp +489 -0
- package/package.json +40 -27
- package/src/agent/index.js +1 -3
- package/src/agent/mcp/MCPServer.js +1024 -7
- package/src/agent/mcp/index.js +1 -1
- package/src/agent/mcp/stdio-server.js +264 -0
- package/src/agent/mcp/tools.js +454 -0
- package/src/cli/index.js +374 -44
- package/src/core/CanvasManager.js +97 -204
- package/src/core/ErrorReporter.js +1 -1
- package/src/core/Parameters.js +1 -1
- package/src/core/VIB3Engine.js +93 -4
- package/src/core/VitalitySystem.js +53 -0
- package/src/core/index.js +18 -0
- package/src/core/renderers/FacetedRendererAdapter.js +10 -9
- package/src/core/renderers/HolographicRendererAdapter.js +13 -9
- package/src/core/renderers/QuantumRendererAdapter.js +11 -7
- package/src/creative/AestheticMapper.js +628 -0
- package/src/creative/ChoreographyPlayer.js +481 -0
- package/src/creative/index.js +11 -0
- package/src/export/TradingCardManager.js +3 -4
- package/src/export/index.js +11 -1
- package/src/faceted/FacetedSystem.js +241 -388
- package/src/games/glyph-war/GlyphWarVisualizer.js +641 -0
- package/src/holograms/HolographicVisualizer.js +29 -12
- package/src/holograms/RealHolographicSystem.js +194 -43
- package/src/math/Mat4x4.js +70 -13
- package/src/math/Rotor4D.js +100 -39
- package/src/math/index.js +7 -7
- package/src/polychora/PolychoraSystem.js +77 -0
- package/src/quantum/QuantumEngine.js +103 -66
- package/src/quantum/QuantumVisualizer.js +7 -2
- package/src/reactivity/index.js +3 -5
- package/src/render/LayerPresetManager.js +372 -0
- package/src/render/LayerReactivityBridge.js +344 -0
- package/src/render/LayerRelationshipGraph.js +610 -0
- package/src/render/MultiCanvasBridge.js +148 -25
- package/src/render/UnifiedRenderBridge.js +3 -0
- package/src/render/index.js +27 -2
- package/src/scene/index.js +4 -4
- package/src/shaders/faceted/faceted.frag.glsl +220 -80
- package/src/shaders/faceted/faceted.frag.wgsl +138 -97
- package/src/shaders/holographic/holographic.frag.glsl +28 -9
- package/src/shaders/holographic/holographic.frag.wgsl +107 -38
- package/src/shaders/quantum/quantum.frag.glsl +1 -0
- package/src/shaders/quantum/quantum.frag.wgsl +1 -1
- package/src/testing/ParallelTestFramework.js +2 -2
- package/src/viewer/GalleryUI.js +17 -0
- package/src/viewer/ViewerPortal.js +2 -2
- package/src/viewer/index.js +1 -1
- package/tools/headless-renderer.js +258 -0
- package/tools/shader-sync-verify.js +8 -4
- package/tools/site-analysis/all-reports.json +32 -0
- package/tools/site-analysis/combined-analysis.md +50 -0
- package/tools/site-analyzer.mjs +779 -0
- package/tools/visual-catalog/capture.js +276 -0
- package/tools/visual-catalog/composite.js +138 -0
- package/types/adaptive-sdk.d.ts +204 -5
- package/types/agent/cli.d.ts +78 -0
- package/types/agent/index.d.ts +18 -0
- package/types/agent/mcp.d.ts +87 -0
- package/types/agent/telemetry.d.ts +190 -0
- package/types/core/VIB3Engine.d.ts +26 -0
- package/types/core/index.d.ts +261 -0
- package/types/creative/AestheticMapper.d.ts +72 -0
- package/types/creative/ChoreographyPlayer.d.ts +96 -0
- package/types/creative/index.d.ts +17 -0
- package/types/export/index.d.ts +243 -0
- package/types/geometry/index.d.ts +164 -0
- package/types/math/index.d.ts +214 -0
- package/types/render/LayerPresetManager.d.ts +78 -0
- package/types/render/LayerReactivityBridge.d.ts +85 -0
- package/types/render/LayerRelationshipGraph.d.ts +174 -0
- package/types/render/index.d.ts +3 -0
- package/types/scene/index.d.ts +204 -0
- package/types/systems/index.d.ts +244 -0
- package/types/variations/index.d.ts +62 -0
- package/types/viewer/index.d.ts +225 -0
- /package/DOCS/{DEV_TRACK_PLAN_2026-01-07.md → archive/DEV_TRACK_PLAN_2026-01-07.md} +0 -0
- /package/DOCS/{SESSION_014_PLAN.md → archive/SESSION_014_PLAN.md} +0 -0
- /package/DOCS/{SESSION_LOG_2026-01-07.md → archive/SESSION_LOG_2026-01-07.md} +0 -0
- /package/DOCS/{STRATEGIC_BLUEPRINT_2026-01-07.md → archive/STRATEGIC_BLUEPRINT_2026-01-07.md} +0 -0
- /package/src/viewer/{ReactivityManager.js → ViewerInputHandler.js} +0 -0
|
@@ -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 = {}) {
|
|
@@ -163,6 +199,60 @@ export class MCPServer {
|
|
|
163
199
|
case 'list_behavior_presets':
|
|
164
200
|
result = this.listBehaviorPresets();
|
|
165
201
|
break;
|
|
202
|
+
// Agent-power tools (Phase 7)
|
|
203
|
+
case 'describe_visual_state':
|
|
204
|
+
result = this.describeVisualState();
|
|
205
|
+
break;
|
|
206
|
+
case 'batch_set_parameters':
|
|
207
|
+
result = await this.batchSetParameters(args);
|
|
208
|
+
break;
|
|
209
|
+
case 'create_timeline':
|
|
210
|
+
result = this.createTimeline(args);
|
|
211
|
+
break;
|
|
212
|
+
case 'play_transition':
|
|
213
|
+
result = this.playTransition(args);
|
|
214
|
+
break;
|
|
215
|
+
case 'apply_color_preset':
|
|
216
|
+
result = this.applyColorPreset(args);
|
|
217
|
+
break;
|
|
218
|
+
case 'set_post_processing':
|
|
219
|
+
result = this.setPostProcessing(args);
|
|
220
|
+
break;
|
|
221
|
+
case 'create_choreography':
|
|
222
|
+
result = this.createChoreography(args);
|
|
223
|
+
break;
|
|
224
|
+
// Visual feedback tools (Phase 7.1)
|
|
225
|
+
case 'capture_screenshot':
|
|
226
|
+
result = await this.captureScreenshot(args);
|
|
227
|
+
break;
|
|
228
|
+
case 'design_from_description':
|
|
229
|
+
result = await this.designFromDescription(args);
|
|
230
|
+
break;
|
|
231
|
+
case 'get_aesthetic_vocabulary':
|
|
232
|
+
result = this.getAestheticVocabulary();
|
|
233
|
+
break;
|
|
234
|
+
case 'play_choreography':
|
|
235
|
+
result = await this.playChoreographyTool(args);
|
|
236
|
+
break;
|
|
237
|
+
case 'control_timeline':
|
|
238
|
+
result = this.controlTimeline(args);
|
|
239
|
+
break;
|
|
240
|
+
// Layer relationship tools (Phase 8)
|
|
241
|
+
case 'set_layer_profile':
|
|
242
|
+
result = this.setLayerProfile(args);
|
|
243
|
+
break;
|
|
244
|
+
case 'set_layer_relationship':
|
|
245
|
+
result = this.setLayerRelationship(args);
|
|
246
|
+
break;
|
|
247
|
+
case 'set_layer_keystone':
|
|
248
|
+
result = this.setLayerKeystone(args);
|
|
249
|
+
break;
|
|
250
|
+
case 'get_layer_config':
|
|
251
|
+
result = this.getLayerConfig();
|
|
252
|
+
break;
|
|
253
|
+
case 'tune_layer_relationship':
|
|
254
|
+
result = this.tuneLayerRelationship(args);
|
|
255
|
+
break;
|
|
166
256
|
default:
|
|
167
257
|
throw new Error(`Unknown tool: ${toolName}`);
|
|
168
258
|
}
|
|
@@ -426,32 +516,68 @@ export class MCPServer {
|
|
|
426
516
|
|
|
427
517
|
telemetry.recordEvent(EventType.GALLERY_SAVE, { slot });
|
|
428
518
|
|
|
519
|
+
// Persist actual engine state if available
|
|
520
|
+
if (this.engine) {
|
|
521
|
+
const state = this.engine.exportState();
|
|
522
|
+
this._gallerySlots.set(slot, {
|
|
523
|
+
name: name || `Variation ${slot}`,
|
|
524
|
+
saved_at: new Date().toISOString(),
|
|
525
|
+
state
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
|
|
429
529
|
return {
|
|
430
530
|
slot,
|
|
431
531
|
name: name || `Variation ${slot}`,
|
|
432
532
|
saved_at: new Date().toISOString(),
|
|
533
|
+
persisted: !!this.engine,
|
|
534
|
+
gallery_size: this._gallerySlots.size,
|
|
433
535
|
suggested_next_actions: ['load_from_gallery', 'randomize_parameters']
|
|
434
536
|
};
|
|
435
537
|
}
|
|
436
538
|
|
|
437
539
|
/**
|
|
438
|
-
* Load from gallery
|
|
540
|
+
* Load from gallery — restores previously saved state
|
|
439
541
|
*/
|
|
440
542
|
loadFromGallery(args) {
|
|
441
543
|
const { slot } = args;
|
|
442
544
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
545
|
+
telemetry.recordEvent(EventType.GALLERY_LOAD, { slot });
|
|
546
|
+
|
|
547
|
+
const saved = this._gallerySlots.get(slot);
|
|
548
|
+
if (saved && this.engine) {
|
|
549
|
+
// Restore saved state
|
|
550
|
+
this.engine.importState(saved.state);
|
|
551
|
+
return {
|
|
552
|
+
slot,
|
|
553
|
+
name: saved.name,
|
|
554
|
+
saved_at: saved.saved_at,
|
|
555
|
+
loaded_at: new Date().toISOString(),
|
|
556
|
+
restored: true,
|
|
557
|
+
...this.getState()
|
|
558
|
+
};
|
|
447
559
|
}
|
|
448
560
|
|
|
449
|
-
|
|
561
|
+
if (!saved) {
|
|
562
|
+
// No saved state — fall back to random variation
|
|
563
|
+
if (this.engine) {
|
|
564
|
+
const params = this.engine.parameters?.generateVariationParameters?.(slot) || {};
|
|
565
|
+
this.engine.setParameters(params);
|
|
566
|
+
}
|
|
567
|
+
return {
|
|
568
|
+
slot,
|
|
569
|
+
loaded_at: new Date().toISOString(),
|
|
570
|
+
restored: false,
|
|
571
|
+
note: 'No saved state in this slot — generated random variation',
|
|
572
|
+
...this.getState()
|
|
573
|
+
};
|
|
574
|
+
}
|
|
450
575
|
|
|
451
576
|
return {
|
|
452
577
|
slot,
|
|
453
578
|
loaded_at: new Date().toISOString(),
|
|
454
|
-
|
|
579
|
+
restored: false,
|
|
580
|
+
note: 'Engine not initialized — cannot apply state'
|
|
455
581
|
};
|
|
456
582
|
}
|
|
457
583
|
|
|
@@ -943,6 +1069,897 @@ export class MCPServer {
|
|
|
943
1069
|
suggested_next_actions: ['apply_behavior_preset']
|
|
944
1070
|
};
|
|
945
1071
|
}
|
|
1072
|
+
|
|
1073
|
+
// ===== AGENT-POWER TOOLS (Phase 7 — Agent Harness) =====
|
|
1074
|
+
|
|
1075
|
+
/**
|
|
1076
|
+
* Generate a natural-language description of the current visual state.
|
|
1077
|
+
* Enables text-only agents to "see" what the visualization looks like.
|
|
1078
|
+
*/
|
|
1079
|
+
describeVisualState() {
|
|
1080
|
+
const state = this.getState();
|
|
1081
|
+
const params = state.visual || {};
|
|
1082
|
+
const rotation = state.rotation_state || {};
|
|
1083
|
+
const geometry = state.geometry || {};
|
|
1084
|
+
|
|
1085
|
+
// Color description from hue
|
|
1086
|
+
const hue = params.hue || 0;
|
|
1087
|
+
const colorName = hue < 15 ? 'red' : hue < 45 ? 'orange' : hue < 75 ? 'yellow' :
|
|
1088
|
+
hue < 150 ? 'green' : hue < 195 ? 'cyan' : hue < 255 ? 'blue' :
|
|
1089
|
+
hue < 285 ? 'purple' : hue < 330 ? 'magenta' : 'red';
|
|
1090
|
+
const satDesc = (params.saturation || 0.8) > 0.7 ? 'vivid' :
|
|
1091
|
+
(params.saturation || 0.8) > 0.4 ? 'moderate' : 'desaturated';
|
|
1092
|
+
const intensityDesc = (params.intensity || 0.5) > 0.7 ? 'bright' :
|
|
1093
|
+
(params.intensity || 0.5) > 0.3 ? 'medium brightness' : 'dim';
|
|
1094
|
+
|
|
1095
|
+
// Motion description
|
|
1096
|
+
const speed = params.speed || 1.0;
|
|
1097
|
+
const speedDesc = speed > 2.0 ? 'rapidly' : speed > 1.0 ? 'moderately' :
|
|
1098
|
+
speed > 0.4 ? 'slowly' : 'very slowly';
|
|
1099
|
+
const chaos = params.chaos || 0;
|
|
1100
|
+
const chaosDesc = chaos > 0.7 ? 'highly turbulent' : chaos > 0.3 ? 'somewhat organic' :
|
|
1101
|
+
chaos > 0.05 ? 'subtly alive' : 'perfectly still';
|
|
1102
|
+
|
|
1103
|
+
// 4D rotation activity
|
|
1104
|
+
const has4D = Math.abs(rotation.XW || 0) > 0.1 ||
|
|
1105
|
+
Math.abs(rotation.YW || 0) > 0.1 ||
|
|
1106
|
+
Math.abs(rotation.ZW || 0) > 0.1;
|
|
1107
|
+
const rotDesc = has4D ? 'with visible 4D hyperspace rotation (inside-out morphing)' :
|
|
1108
|
+
'in standard 3D orientation';
|
|
1109
|
+
|
|
1110
|
+
// Density/complexity
|
|
1111
|
+
const density = params.gridDensity || 10;
|
|
1112
|
+
const densityDesc = density > 50 ? 'extremely intricate' : density > 25 ? 'detailed' :
|
|
1113
|
+
density > 12 ? 'moderate detail' : 'bold and sparse';
|
|
1114
|
+
|
|
1115
|
+
// Projection
|
|
1116
|
+
const dim = params.dimension || 3.8;
|
|
1117
|
+
const projDesc = dim < 3.3 ? 'dramatic fish-eye distortion' :
|
|
1118
|
+
dim < 3.8 ? 'moderate perspective depth' : 'subtle, flattened perspective';
|
|
1119
|
+
|
|
1120
|
+
const description = [
|
|
1121
|
+
`A ${satDesc} ${colorName} ${geometry.core_type || 'base'} ${geometry.base_type || 'tetrahedron'}`,
|
|
1122
|
+
`rendered in the ${state.system || 'quantum'} system.`,
|
|
1123
|
+
`The pattern is ${densityDesc} and ${chaosDesc},`,
|
|
1124
|
+
`animating ${speedDesc} ${rotDesc}.`,
|
|
1125
|
+
`Color is ${intensityDesc} with ${projDesc}.`,
|
|
1126
|
+
params.morphFactor > 0.5 ? `Shape is morphing between geometries (factor: ${params.morphFactor}).` : ''
|
|
1127
|
+
].filter(Boolean).join(' ');
|
|
1128
|
+
|
|
1129
|
+
return {
|
|
1130
|
+
description,
|
|
1131
|
+
mood: this._assessMood(params),
|
|
1132
|
+
complexity: density > 40 ? 'high' : density > 15 ? 'medium' : 'low',
|
|
1133
|
+
motion_level: speed > 1.5 ? 'high' : speed > 0.5 ? 'medium' : 'low',
|
|
1134
|
+
has_4d_rotation: has4D,
|
|
1135
|
+
color_family: colorName,
|
|
1136
|
+
suggested_next_actions: ['set_visual_parameters', 'set_rotation', 'batch_set_parameters']
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
/**
|
|
1141
|
+
* Assess the emotional mood of the current visual state
|
|
1142
|
+
*/
|
|
1143
|
+
_assessMood(params) {
|
|
1144
|
+
const hue = params.hue || 0;
|
|
1145
|
+
const speed = params.speed || 1.0;
|
|
1146
|
+
const chaos = params.chaos || 0;
|
|
1147
|
+
const intensity = params.intensity || 0.5;
|
|
1148
|
+
|
|
1149
|
+
if (speed < 0.3 && chaos < 0.1) return 'serene';
|
|
1150
|
+
if (speed > 2.0 && chaos > 0.6) return 'chaotic';
|
|
1151
|
+
if (hue > 180 && hue < 260 && intensity < 0.5) return 'mysterious';
|
|
1152
|
+
if (hue > 0 && hue < 60 && intensity > 0.6) return 'warm';
|
|
1153
|
+
if (hue > 150 && hue < 200 && speed < 0.8) return 'tranquil';
|
|
1154
|
+
if (chaos > 0.5 && speed > 1.5) return 'energetic';
|
|
1155
|
+
if (intensity > 0.8) return 'vibrant';
|
|
1156
|
+
return 'balanced';
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
/**
|
|
1160
|
+
* Atomically set multiple parameter categories in one call
|
|
1161
|
+
*/
|
|
1162
|
+
async batchSetParameters(args) {
|
|
1163
|
+
const { system, geometry, rotation, visual, preset } = args;
|
|
1164
|
+
|
|
1165
|
+
// Switch system first if requested
|
|
1166
|
+
if (system && this.engine) {
|
|
1167
|
+
await this.engine.switchSystem(system);
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
// Set geometry
|
|
1171
|
+
if (geometry !== undefined && this.engine) {
|
|
1172
|
+
this.engine.setParameter('geometry', geometry);
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
// Set rotation
|
|
1176
|
+
if (rotation) {
|
|
1177
|
+
const rotMap = { XY: 'rot4dXY', XZ: 'rot4dXZ', YZ: 'rot4dYZ',
|
|
1178
|
+
XW: 'rot4dXW', YW: 'rot4dYW', ZW: 'rot4dZW' };
|
|
1179
|
+
for (const [key, value] of Object.entries(rotation)) {
|
|
1180
|
+
if (value !== undefined && this.engine) {
|
|
1181
|
+
this.engine.setParameter(rotMap[key], value);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
// Set visual parameters
|
|
1187
|
+
if (visual && this.engine) {
|
|
1188
|
+
for (const [key, value] of Object.entries(visual)) {
|
|
1189
|
+
this.engine.setParameter(key, value);
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
// Apply preset last (overrides relevant params)
|
|
1194
|
+
if (preset) {
|
|
1195
|
+
this.applyBehaviorPreset({ preset });
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
telemetry.recordEvent(EventType.PARAMETER_BATCH_CHANGE, {
|
|
1199
|
+
count: (rotation ? Object.keys(rotation).length : 0) +
|
|
1200
|
+
(visual ? Object.keys(visual).length : 0) +
|
|
1201
|
+
(system ? 1 : 0) + (geometry !== undefined ? 1 : 0)
|
|
1202
|
+
});
|
|
1203
|
+
|
|
1204
|
+
return {
|
|
1205
|
+
...this.getState(),
|
|
1206
|
+
batch_applied: true,
|
|
1207
|
+
suggested_next_actions: ['describe_visual_state', 'save_to_gallery', 'create_timeline']
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
/**
|
|
1212
|
+
* Create a ParameterTimeline from agent specification
|
|
1213
|
+
*/
|
|
1214
|
+
createTimeline(args) {
|
|
1215
|
+
const { name, duration_ms, bpm, loop_mode = 'once', tracks } = args;
|
|
1216
|
+
|
|
1217
|
+
const timelineId = generateId('timeline');
|
|
1218
|
+
|
|
1219
|
+
// Validate tracks have properly sorted keyframes
|
|
1220
|
+
const validatedTracks = {};
|
|
1221
|
+
for (const [param, keyframes] of Object.entries(tracks)) {
|
|
1222
|
+
validatedTracks[param] = keyframes
|
|
1223
|
+
.map(kf => ({
|
|
1224
|
+
time: Math.max(0, Math.min(kf.time, duration_ms)),
|
|
1225
|
+
value: kf.value,
|
|
1226
|
+
easing: kf.easing || 'easeInOut'
|
|
1227
|
+
}))
|
|
1228
|
+
.sort((a, b) => a.time - b.time);
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
// Build timeline data for ParameterTimeline consumption
|
|
1232
|
+
const timelineData = {
|
|
1233
|
+
id: timelineId,
|
|
1234
|
+
name: name || `Timeline ${timelineId}`,
|
|
1235
|
+
duration: duration_ms,
|
|
1236
|
+
bpm: bpm || null,
|
|
1237
|
+
loopMode: loop_mode,
|
|
1238
|
+
tracks: validatedTracks
|
|
1239
|
+
};
|
|
1240
|
+
|
|
1241
|
+
// If engine is available, create and start the timeline
|
|
1242
|
+
if (this.engine) {
|
|
1243
|
+
// Store for later retrieval
|
|
1244
|
+
if (!this._timelines) this._timelines = new Map();
|
|
1245
|
+
this._timelines.set(timelineId, timelineData);
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
return {
|
|
1249
|
+
timeline_id: timelineId,
|
|
1250
|
+
name: timelineData.name,
|
|
1251
|
+
duration_ms,
|
|
1252
|
+
bpm: bpm || null,
|
|
1253
|
+
loop_mode,
|
|
1254
|
+
track_count: Object.keys(validatedTracks).length,
|
|
1255
|
+
tracks_summary: Object.entries(validatedTracks).map(([param, kfs]) => ({
|
|
1256
|
+
parameter: param,
|
|
1257
|
+
keyframe_count: kfs.length,
|
|
1258
|
+
value_range: [
|
|
1259
|
+
Math.min(...kfs.map(k => k.value)),
|
|
1260
|
+
Math.max(...kfs.map(k => k.value))
|
|
1261
|
+
]
|
|
1262
|
+
})),
|
|
1263
|
+
load_code: `const tl = new ParameterTimeline((n, v) => engine.setParameter(n, v));\ntl.importTimeline(${JSON.stringify(timelineData)});\ntl.play();`,
|
|
1264
|
+
suggested_next_actions: ['play_transition', 'describe_visual_state', 'save_to_gallery']
|
|
1265
|
+
};
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
/**
|
|
1269
|
+
* Play a smooth transition sequence
|
|
1270
|
+
*/
|
|
1271
|
+
playTransition(args) {
|
|
1272
|
+
const { sequence } = args;
|
|
1273
|
+
|
|
1274
|
+
const transitionId = generateId('transition');
|
|
1275
|
+
|
|
1276
|
+
// Validate and normalize the sequence
|
|
1277
|
+
const normalizedSequence = sequence.map((step, i) => ({
|
|
1278
|
+
params: step.params,
|
|
1279
|
+
duration: step.duration || 1000,
|
|
1280
|
+
easing: step.easing || 'easeInOut',
|
|
1281
|
+
delay: step.delay || 0
|
|
1282
|
+
}));
|
|
1283
|
+
|
|
1284
|
+
const totalDuration = normalizedSequence.reduce(
|
|
1285
|
+
(sum, step) => sum + step.duration + step.delay, 0
|
|
1286
|
+
);
|
|
1287
|
+
|
|
1288
|
+
// Execute live if engine available
|
|
1289
|
+
let executing = false;
|
|
1290
|
+
const animator = this._getTransitionAnimator();
|
|
1291
|
+
if (animator) {
|
|
1292
|
+
const seqId = animator.sequence(normalizedSequence);
|
|
1293
|
+
executing = !!seqId;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
return {
|
|
1297
|
+
transition_id: transitionId,
|
|
1298
|
+
executing,
|
|
1299
|
+
step_count: normalizedSequence.length,
|
|
1300
|
+
total_duration_ms: totalDuration,
|
|
1301
|
+
steps: normalizedSequence.map((step, i) => ({
|
|
1302
|
+
index: i,
|
|
1303
|
+
params: Object.keys(step.params),
|
|
1304
|
+
duration: step.duration,
|
|
1305
|
+
easing: step.easing,
|
|
1306
|
+
delay: step.delay
|
|
1307
|
+
})),
|
|
1308
|
+
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)});`,
|
|
1309
|
+
suggested_next_actions: ['describe_visual_state', 'create_timeline', 'save_to_gallery']
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
/**
|
|
1314
|
+
* Apply a named color preset
|
|
1315
|
+
*/
|
|
1316
|
+
applyColorPreset(args) {
|
|
1317
|
+
const { preset, transition = true, duration = 800 } = args;
|
|
1318
|
+
|
|
1319
|
+
const colorSystem = this._getColorPresets();
|
|
1320
|
+
|
|
1321
|
+
if (colorSystem) {
|
|
1322
|
+
// Use real ColorPresetsSystem — full preset library with transitions
|
|
1323
|
+
const config = colorSystem.getPreset(preset);
|
|
1324
|
+
if (!config) {
|
|
1325
|
+
const allPresets = colorSystem.getPresets().map(p => p.name);
|
|
1326
|
+
return {
|
|
1327
|
+
error: {
|
|
1328
|
+
type: 'ValidationError',
|
|
1329
|
+
code: 'INVALID_COLOR_PRESET',
|
|
1330
|
+
message: `Unknown color preset: ${preset}`,
|
|
1331
|
+
valid_options: allPresets
|
|
1332
|
+
}
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
colorSystem.applyPreset(preset, transition, duration);
|
|
1337
|
+
|
|
1338
|
+
return {
|
|
1339
|
+
preset,
|
|
1340
|
+
applied: { hue: config.hue, saturation: config.saturation, intensity: config.intensity },
|
|
1341
|
+
transition: transition ? { enabled: true, duration } : { enabled: false },
|
|
1342
|
+
full_config: config,
|
|
1343
|
+
suggested_next_actions: ['set_post_processing', 'describe_visual_state', 'set_visual_parameters']
|
|
1344
|
+
};
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
// Fallback: no engine, return preset metadata for artifact mode
|
|
1348
|
+
return {
|
|
1349
|
+
preset,
|
|
1350
|
+
applied: null,
|
|
1351
|
+
load_code: `const colors = new ColorPresetsSystem((n, v) => engine.setParameter(n, v));\ncolors.applyPreset('${preset}', ${transition}, ${duration});`,
|
|
1352
|
+
suggested_next_actions: ['set_post_processing', 'describe_visual_state', 'set_visual_parameters']
|
|
1353
|
+
};
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
/**
|
|
1357
|
+
* Configure post-processing effects pipeline
|
|
1358
|
+
*/
|
|
1359
|
+
setPostProcessing(args) {
|
|
1360
|
+
const { effects, chain_preset, clear_first = true } = args;
|
|
1361
|
+
|
|
1362
|
+
// Try to execute live in browser context
|
|
1363
|
+
let executing = false;
|
|
1364
|
+
if (typeof document !== 'undefined') {
|
|
1365
|
+
try {
|
|
1366
|
+
const target = document.getElementById('viz-container')
|
|
1367
|
+
|| document.querySelector('.vib3-container')
|
|
1368
|
+
|| document.querySelector('canvas')?.parentElement;
|
|
1369
|
+
|
|
1370
|
+
if (target) {
|
|
1371
|
+
// Lazy-init pipeline, importing dynamically to avoid Node.js issues
|
|
1372
|
+
if (!this._postPipeline) {
|
|
1373
|
+
// PostProcessingPipeline imported statically would fail in Node;
|
|
1374
|
+
// it's already a known browser-only module, so guard at runtime
|
|
1375
|
+
const { PostProcessingPipeline: PPP } = { PostProcessingPipeline: globalThis.PostProcessingPipeline };
|
|
1376
|
+
if (PPP) {
|
|
1377
|
+
this._postPipeline = new PPP(target);
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
if (this._postPipeline) {
|
|
1382
|
+
if (clear_first) this._postPipeline.clearChain?.();
|
|
1383
|
+
if (chain_preset) {
|
|
1384
|
+
this._postPipeline.loadPresetChain(chain_preset);
|
|
1385
|
+
} else if (effects) {
|
|
1386
|
+
for (const e of effects) {
|
|
1387
|
+
this._postPipeline.addEffect(e.name, { intensity: e.intensity || 0.5, ...e });
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
this._postPipeline.apply();
|
|
1391
|
+
executing = true;
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
} catch { /* fall through to code generation */ }
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
return {
|
|
1398
|
+
applied: true,
|
|
1399
|
+
executing,
|
|
1400
|
+
effects: effects || [],
|
|
1401
|
+
chain_preset: chain_preset || null,
|
|
1402
|
+
cleared_previous: clear_first,
|
|
1403
|
+
load_code: executing ? null : (effects ?
|
|
1404
|
+
`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();` :
|
|
1405
|
+
`const pipeline = new PostProcessingPipeline(document.getElementById('viz-container'));\npipeline.loadPresetChain('${chain_preset}');\npipeline.apply();`),
|
|
1406
|
+
suggested_next_actions: ['describe_visual_state', 'apply_color_preset', 'create_choreography']
|
|
1407
|
+
};
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
/**
|
|
1411
|
+
* Create a multi-scene choreography — the most powerful agent composition tool
|
|
1412
|
+
*/
|
|
1413
|
+
createChoreography(args) {
|
|
1414
|
+
const { name, duration_ms, bpm, scenes } = args;
|
|
1415
|
+
|
|
1416
|
+
const choreographyId = generateId('choreo');
|
|
1417
|
+
|
|
1418
|
+
// Validate scene time ranges don't exceed duration
|
|
1419
|
+
const validatedScenes = scenes.map((scene, i) => ({
|
|
1420
|
+
index: i,
|
|
1421
|
+
time_start: Math.max(0, scene.time_start),
|
|
1422
|
+
time_end: Math.min(scene.time_end, duration_ms),
|
|
1423
|
+
system: scene.system,
|
|
1424
|
+
geometry: scene.geometry ?? 0,
|
|
1425
|
+
transition_in: scene.transition_in || { type: 'cut', duration: 0 },
|
|
1426
|
+
tracks: scene.tracks || {},
|
|
1427
|
+
color_preset: scene.color_preset || null,
|
|
1428
|
+
post_processing: scene.post_processing || [],
|
|
1429
|
+
audio: scene.audio || null
|
|
1430
|
+
}));
|
|
1431
|
+
|
|
1432
|
+
const choreography = {
|
|
1433
|
+
id: choreographyId,
|
|
1434
|
+
name: name || `Choreography ${choreographyId}`,
|
|
1435
|
+
duration_ms,
|
|
1436
|
+
bpm: bpm || null,
|
|
1437
|
+
scene_count: validatedScenes.length,
|
|
1438
|
+
scenes: validatedScenes
|
|
1439
|
+
};
|
|
1440
|
+
|
|
1441
|
+
// Store for later retrieval
|
|
1442
|
+
if (!this._choreographies) this._choreographies = new Map();
|
|
1443
|
+
this._choreographies.set(choreographyId, choreography);
|
|
1444
|
+
|
|
1445
|
+
return {
|
|
1446
|
+
choreography_id: choreographyId,
|
|
1447
|
+
name: choreography.name,
|
|
1448
|
+
duration_ms,
|
|
1449
|
+
bpm: bpm || null,
|
|
1450
|
+
scene_count: validatedScenes.length,
|
|
1451
|
+
scenes_summary: validatedScenes.map(s => ({
|
|
1452
|
+
index: s.index,
|
|
1453
|
+
time: `${s.time_start}ms → ${s.time_end}ms`,
|
|
1454
|
+
system: s.system,
|
|
1455
|
+
geometry: s.geometry,
|
|
1456
|
+
transition: s.transition_in.type,
|
|
1457
|
+
track_count: Object.keys(s.tracks).length,
|
|
1458
|
+
color_preset: s.color_preset,
|
|
1459
|
+
effects: s.post_processing
|
|
1460
|
+
})),
|
|
1461
|
+
choreography_json: JSON.stringify(choreography, null, 2),
|
|
1462
|
+
suggested_next_actions: ['describe_visual_state', 'export_package']
|
|
1463
|
+
};
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
// ===== VISUAL FEEDBACK TOOLS (Phase 7.1 — Agent Harness) =====
|
|
1467
|
+
|
|
1468
|
+
/**
|
|
1469
|
+
* Capture the current visualization as a base64 PNG by compositing all canvas layers.
|
|
1470
|
+
* Only works in browser context where canvases exist.
|
|
1471
|
+
*/
|
|
1472
|
+
async captureScreenshot(args) {
|
|
1473
|
+
const { width = 512, height = 512, format = 'png', quality = 0.92 } = args;
|
|
1474
|
+
|
|
1475
|
+
const isBrowser = typeof document !== 'undefined';
|
|
1476
|
+
if (!isBrowser) {
|
|
1477
|
+
return {
|
|
1478
|
+
error: {
|
|
1479
|
+
type: 'EnvironmentError',
|
|
1480
|
+
code: 'NO_BROWSER_CONTEXT',
|
|
1481
|
+
message: 'capture_screenshot requires a browser context with canvas elements',
|
|
1482
|
+
suggestion: 'Use the headless renderer tool (tools/headless-renderer.js) for Node.js environments'
|
|
1483
|
+
}
|
|
1484
|
+
};
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
try {
|
|
1488
|
+
// Create composite canvas
|
|
1489
|
+
const composite = document.createElement('canvas');
|
|
1490
|
+
composite.width = width;
|
|
1491
|
+
composite.height = height;
|
|
1492
|
+
const ctx = composite.getContext('2d');
|
|
1493
|
+
|
|
1494
|
+
if (!ctx) {
|
|
1495
|
+
return {
|
|
1496
|
+
error: { type: 'SystemError', code: 'CANVAS_CONTEXT_FAILED', message: 'Could not get 2D context for compositing' }
|
|
1497
|
+
};
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
// Fill with black background
|
|
1501
|
+
ctx.fillStyle = '#000000';
|
|
1502
|
+
ctx.fillRect(0, 0, width, height);
|
|
1503
|
+
|
|
1504
|
+
// Find all visualization canvases and composite them in z-order
|
|
1505
|
+
const canvases = document.querySelectorAll('canvas.visualization-canvas');
|
|
1506
|
+
const sortedCanvases = Array.from(canvases).sort((a, b) => {
|
|
1507
|
+
const zA = parseInt(a.style.zIndex || '0', 10);
|
|
1508
|
+
const zB = parseInt(b.style.zIndex || '0', 10);
|
|
1509
|
+
return zA - zB;
|
|
1510
|
+
});
|
|
1511
|
+
|
|
1512
|
+
for (const canvas of sortedCanvases) {
|
|
1513
|
+
if (canvas.width > 0 && canvas.height > 0) {
|
|
1514
|
+
ctx.drawImage(canvas, 0, 0, width, height);
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
// If no canvases found, try to find any canvas at all
|
|
1519
|
+
if (sortedCanvases.length === 0) {
|
|
1520
|
+
const anyCanvas = document.querySelector('canvas');
|
|
1521
|
+
if (anyCanvas && anyCanvas.width > 0) {
|
|
1522
|
+
ctx.drawImage(anyCanvas, 0, 0, width, height);
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
// Convert to data URL
|
|
1527
|
+
const mimeType = format === 'jpeg' ? 'image/jpeg' : format === 'webp' ? 'image/webp' : 'image/png';
|
|
1528
|
+
const dataUrl = composite.toDataURL(mimeType, quality);
|
|
1529
|
+
const base64 = dataUrl.split(',')[1];
|
|
1530
|
+
|
|
1531
|
+
// Clean up
|
|
1532
|
+
composite.remove();
|
|
1533
|
+
|
|
1534
|
+
return {
|
|
1535
|
+
format,
|
|
1536
|
+
width,
|
|
1537
|
+
height,
|
|
1538
|
+
mime_type: mimeType,
|
|
1539
|
+
data_url: dataUrl,
|
|
1540
|
+
base64_length: base64.length,
|
|
1541
|
+
canvas_count: sortedCanvases.length,
|
|
1542
|
+
suggested_next_actions: ['describe_visual_state', 'set_visual_parameters', 'batch_set_parameters']
|
|
1543
|
+
};
|
|
1544
|
+
} catch (err) {
|
|
1545
|
+
return {
|
|
1546
|
+
error: {
|
|
1547
|
+
type: 'SystemError',
|
|
1548
|
+
code: 'SCREENSHOT_FAILED',
|
|
1549
|
+
message: err.message,
|
|
1550
|
+
suggestion: 'Check that canvas elements exist and are rendered'
|
|
1551
|
+
}
|
|
1552
|
+
};
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
/**
|
|
1557
|
+
* Map a natural-language description to VIB3+ parameters using AestheticMapper.
|
|
1558
|
+
*/
|
|
1559
|
+
async designFromDescription(args) {
|
|
1560
|
+
const { description, apply = false } = args;
|
|
1561
|
+
|
|
1562
|
+
if (!this._aestheticMapper) {
|
|
1563
|
+
this._aestheticMapper = new AestheticMapper();
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
const mapped = this._aestheticMapper.mapDescription(description);
|
|
1567
|
+
const resolved = this._aestheticMapper.resolveToValues(description);
|
|
1568
|
+
|
|
1569
|
+
// Apply to engine if requested
|
|
1570
|
+
if (apply && this.engine) {
|
|
1571
|
+
if (resolved.system) {
|
|
1572
|
+
await this.engine.switchSystem(resolved.system);
|
|
1573
|
+
}
|
|
1574
|
+
if (resolved.geometry !== undefined) {
|
|
1575
|
+
this.engine.setParameter('geometry', resolved.geometry);
|
|
1576
|
+
}
|
|
1577
|
+
for (const [param, value] of Object.entries(resolved.params)) {
|
|
1578
|
+
this.engine.setParameter(param, value);
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
return {
|
|
1583
|
+
description,
|
|
1584
|
+
applied: apply,
|
|
1585
|
+
matched_words: mapped.matched_words,
|
|
1586
|
+
total_words: mapped.total_words,
|
|
1587
|
+
resolved: {
|
|
1588
|
+
system: resolved.system,
|
|
1589
|
+
geometry: resolved.geometry,
|
|
1590
|
+
params: resolved.params,
|
|
1591
|
+
color_preset: resolved.color_preset,
|
|
1592
|
+
post_processing: resolved.post_processing
|
|
1593
|
+
},
|
|
1594
|
+
parameter_ranges: mapped.params,
|
|
1595
|
+
suggested_next_actions: apply
|
|
1596
|
+
? ['describe_visual_state', 'capture_screenshot', 'create_timeline']
|
|
1597
|
+
: ['design_from_description', 'batch_set_parameters']
|
|
1598
|
+
};
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
/**
|
|
1602
|
+
* Return the full aesthetic vocabulary by category.
|
|
1603
|
+
*/
|
|
1604
|
+
getAestheticVocabulary() {
|
|
1605
|
+
if (!this._aestheticMapper) {
|
|
1606
|
+
this._aestheticMapper = new AestheticMapper();
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
return {
|
|
1610
|
+
vocabulary: this._aestheticMapper.getVocabularyByCategory(),
|
|
1611
|
+
all_words: this._aestheticMapper.getVocabulary(),
|
|
1612
|
+
word_count: this._aestheticMapper.getVocabulary().length,
|
|
1613
|
+
usage: 'Pass space-separated words to design_from_description. Example: "serene ocean deep minimal"',
|
|
1614
|
+
suggested_next_actions: ['design_from_description']
|
|
1615
|
+
};
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
/**
|
|
1619
|
+
* Load and play a choreography.
|
|
1620
|
+
*/
|
|
1621
|
+
async playChoreographyTool(args) {
|
|
1622
|
+
const { choreography, choreography_id, action = 'play', seek_percent, loop = false } = args;
|
|
1623
|
+
|
|
1624
|
+
// Resolve choreography spec
|
|
1625
|
+
let spec = choreography;
|
|
1626
|
+
if (!spec && choreography_id && this._choreographies) {
|
|
1627
|
+
spec = this._choreographies.get(choreography_id);
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
if (!spec && action === 'play') {
|
|
1631
|
+
return {
|
|
1632
|
+
error: {
|
|
1633
|
+
type: 'ValidationError',
|
|
1634
|
+
code: 'NO_CHOREOGRAPHY',
|
|
1635
|
+
message: 'Provide a choreography spec or a valid choreography_id',
|
|
1636
|
+
suggestion: 'Use create_choreography first, then pass the result here'
|
|
1637
|
+
}
|
|
1638
|
+
};
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
// Create or reuse player
|
|
1642
|
+
if (!this._choreographyPlayer && this.engine) {
|
|
1643
|
+
this._choreographyPlayer = new ChoreographyPlayer(this.engine, {
|
|
1644
|
+
onSceneChange: (index, scene) => {
|
|
1645
|
+
telemetry.recordEvent(EventType.PARAMETER_CHANGE, {
|
|
1646
|
+
type: 'choreography_scene',
|
|
1647
|
+
scene_index: index,
|
|
1648
|
+
system: scene.system
|
|
1649
|
+
});
|
|
1650
|
+
}
|
|
1651
|
+
});
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
if (!this._choreographyPlayer) {
|
|
1655
|
+
return {
|
|
1656
|
+
error: {
|
|
1657
|
+
type: 'SystemError',
|
|
1658
|
+
code: 'NO_ENGINE',
|
|
1659
|
+
message: 'Engine not initialized — cannot play choreography',
|
|
1660
|
+
suggestion: 'Initialize the VIB3Engine first'
|
|
1661
|
+
}
|
|
1662
|
+
};
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
const player = this._choreographyPlayer;
|
|
1666
|
+
|
|
1667
|
+
switch (action) {
|
|
1668
|
+
case 'play':
|
|
1669
|
+
if (spec) {
|
|
1670
|
+
player.load(spec);
|
|
1671
|
+
player.loopMode = loop ? 'loop' : 'once';
|
|
1672
|
+
}
|
|
1673
|
+
player.play();
|
|
1674
|
+
break;
|
|
1675
|
+
case 'pause':
|
|
1676
|
+
player.pause();
|
|
1677
|
+
break;
|
|
1678
|
+
case 'stop':
|
|
1679
|
+
player.stop();
|
|
1680
|
+
break;
|
|
1681
|
+
case 'seek':
|
|
1682
|
+
if (seek_percent !== undefined) {
|
|
1683
|
+
player.seekToPercent(seek_percent);
|
|
1684
|
+
}
|
|
1685
|
+
break;
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
return {
|
|
1689
|
+
action,
|
|
1690
|
+
state: player.getState(),
|
|
1691
|
+
suggested_next_actions: action === 'play'
|
|
1692
|
+
? ['play_choreography', 'capture_screenshot', 'describe_visual_state']
|
|
1693
|
+
: ['play_choreography']
|
|
1694
|
+
};
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
/**
|
|
1698
|
+
* Control a previously created timeline.
|
|
1699
|
+
*/
|
|
1700
|
+
controlTimeline(args) {
|
|
1701
|
+
const { timeline_id, action, seek_percent, speed } = args;
|
|
1702
|
+
|
|
1703
|
+
if (!this._liveTimelines) this._liveTimelines = new Map();
|
|
1704
|
+
|
|
1705
|
+
let tl = this._liveTimelines.get(timeline_id);
|
|
1706
|
+
|
|
1707
|
+
// If timeline not live yet, try to create it from stored data
|
|
1708
|
+
if (!tl && this._timelines && this._timelines.has(timeline_id) && this.engine) {
|
|
1709
|
+
const data = this._timelines.get(timeline_id);
|
|
1710
|
+
|
|
1711
|
+
tl = new ParameterTimeline(
|
|
1712
|
+
(name, value) => this.engine.setParameter(name, value)
|
|
1713
|
+
);
|
|
1714
|
+
|
|
1715
|
+
// Build import-compatible format
|
|
1716
|
+
const importData = {
|
|
1717
|
+
type: 'vib3-parameter-timeline',
|
|
1718
|
+
version: '1.0.0',
|
|
1719
|
+
duration: data.duration,
|
|
1720
|
+
loopMode: data.loopMode || 'once',
|
|
1721
|
+
bpm: data.bpm || 120,
|
|
1722
|
+
tracks: {}
|
|
1723
|
+
};
|
|
1724
|
+
|
|
1725
|
+
for (const [param, keyframes] of Object.entries(data.tracks)) {
|
|
1726
|
+
importData.tracks[param] = {
|
|
1727
|
+
enabled: true,
|
|
1728
|
+
keyframes: keyframes.map(kf => ({
|
|
1729
|
+
time: kf.time,
|
|
1730
|
+
value: kf.value,
|
|
1731
|
+
easing: kf.easing || 'easeInOut'
|
|
1732
|
+
}))
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
tl.importTimeline(importData);
|
|
1737
|
+
this._liveTimelines.set(timeline_id, tl);
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
if (!tl) {
|
|
1741
|
+
return {
|
|
1742
|
+
error: {
|
|
1743
|
+
type: 'ValidationError',
|
|
1744
|
+
code: 'TIMELINE_NOT_FOUND',
|
|
1745
|
+
message: `Timeline '${timeline_id}' not found`,
|
|
1746
|
+
suggestion: 'Create a timeline first with create_timeline'
|
|
1747
|
+
}
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
switch (action) {
|
|
1752
|
+
case 'play':
|
|
1753
|
+
tl.play();
|
|
1754
|
+
break;
|
|
1755
|
+
case 'pause':
|
|
1756
|
+
tl.pause();
|
|
1757
|
+
break;
|
|
1758
|
+
case 'stop':
|
|
1759
|
+
tl.stop();
|
|
1760
|
+
break;
|
|
1761
|
+
case 'seek':
|
|
1762
|
+
if (seek_percent !== undefined) {
|
|
1763
|
+
tl.seekToPercent(seek_percent);
|
|
1764
|
+
}
|
|
1765
|
+
break;
|
|
1766
|
+
case 'set_speed':
|
|
1767
|
+
if (speed !== undefined) {
|
|
1768
|
+
tl.playbackSpeed = Math.max(0.1, Math.min(10, speed));
|
|
1769
|
+
}
|
|
1770
|
+
break;
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
return {
|
|
1774
|
+
timeline_id,
|
|
1775
|
+
action,
|
|
1776
|
+
playing: tl.playing,
|
|
1777
|
+
currentTime: tl.currentTime,
|
|
1778
|
+
duration: tl.duration,
|
|
1779
|
+
progress: tl.duration > 0 ? tl.currentTime / tl.duration : 0,
|
|
1780
|
+
playbackSpeed: tl.playbackSpeed,
|
|
1781
|
+
suggested_next_actions: ['control_timeline', 'describe_visual_state', 'capture_screenshot']
|
|
1782
|
+
};
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
// ====================================================================
|
|
1786
|
+
// Layer Relationship Tools (Phase 8)
|
|
1787
|
+
// ====================================================================
|
|
1788
|
+
|
|
1789
|
+
/**
|
|
1790
|
+
* Get the holographic system's layer graph (if available).
|
|
1791
|
+
* @private
|
|
1792
|
+
* @returns {import('../../render/LayerRelationshipGraph.js').LayerRelationshipGraph|null}
|
|
1793
|
+
*/
|
|
1794
|
+
_getLayerGraph() {
|
|
1795
|
+
if (!this.engine) return null;
|
|
1796
|
+
// Try to access the current system's layer graph
|
|
1797
|
+
const system = this.engine.currentSystem || this.engine._activeSystem;
|
|
1798
|
+
if (system && system.layerGraph) {
|
|
1799
|
+
return system.layerGraph;
|
|
1800
|
+
}
|
|
1801
|
+
if (system && system._layerGraph) {
|
|
1802
|
+
return system._layerGraph;
|
|
1803
|
+
}
|
|
1804
|
+
return null;
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
/**
|
|
1808
|
+
* Load a named layer relationship profile.
|
|
1809
|
+
*/
|
|
1810
|
+
setLayerProfile(args) {
|
|
1811
|
+
const { profile } = args;
|
|
1812
|
+
const graph = this._getLayerGraph();
|
|
1813
|
+
|
|
1814
|
+
if (!graph) {
|
|
1815
|
+
return {
|
|
1816
|
+
error: 'Layer relationship graph not available. Switch to holographic system first.',
|
|
1817
|
+
suggested_next_actions: ['switch_system']
|
|
1818
|
+
};
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
graph.loadProfile(profile);
|
|
1822
|
+
telemetry.recordEvent(EventType.PARAMETER_CHANGE, { type: 'layer_profile', profile });
|
|
1823
|
+
|
|
1824
|
+
return {
|
|
1825
|
+
profile,
|
|
1826
|
+
keystone: graph.keystone,
|
|
1827
|
+
active_profile: graph.activeProfile,
|
|
1828
|
+
available_profiles: ['holographic', 'symmetry', 'chord', 'storm', 'legacy'],
|
|
1829
|
+
suggested_next_actions: ['get_layer_config', 'set_layer_relationship', 'tune_layer_relationship']
|
|
1830
|
+
};
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
/**
|
|
1834
|
+
* Set relationship for a specific layer.
|
|
1835
|
+
*/
|
|
1836
|
+
setLayerRelationship(args) {
|
|
1837
|
+
const { layer, relationship, config } = args;
|
|
1838
|
+
const graph = this._getLayerGraph();
|
|
1839
|
+
|
|
1840
|
+
if (!graph) {
|
|
1841
|
+
return {
|
|
1842
|
+
error: 'Layer relationship graph not available. Switch to holographic system first.',
|
|
1843
|
+
suggested_next_actions: ['switch_system']
|
|
1844
|
+
};
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
if (config) {
|
|
1848
|
+
graph.setRelationship(layer, { preset: relationship, config });
|
|
1849
|
+
} else {
|
|
1850
|
+
graph.setRelationship(layer, relationship);
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
telemetry.recordEvent(EventType.PARAMETER_CHANGE, {
|
|
1854
|
+
type: 'layer_relationship', layer, relationship
|
|
1855
|
+
});
|
|
1856
|
+
|
|
1857
|
+
return {
|
|
1858
|
+
layer,
|
|
1859
|
+
relationship,
|
|
1860
|
+
config: config || {},
|
|
1861
|
+
keystone: graph.keystone,
|
|
1862
|
+
suggested_next_actions: ['get_layer_config', 'tune_layer_relationship', 'describe_visual_state']
|
|
1863
|
+
};
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
/**
|
|
1867
|
+
* Change the keystone (driver) layer.
|
|
1868
|
+
*/
|
|
1869
|
+
setLayerKeystone(args) {
|
|
1870
|
+
const { layer } = 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.setKeystone(layer);
|
|
1881
|
+
telemetry.recordEvent(EventType.PARAMETER_CHANGE, { type: 'layer_keystone', layer });
|
|
1882
|
+
|
|
1883
|
+
return {
|
|
1884
|
+
keystone: layer,
|
|
1885
|
+
note: 'Other layers\' relationships are preserved. Set new relationships for the old keystone if needed.',
|
|
1886
|
+
suggested_next_actions: ['set_layer_relationship', 'get_layer_config']
|
|
1887
|
+
};
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
/**
|
|
1891
|
+
* Get current layer configuration.
|
|
1892
|
+
*/
|
|
1893
|
+
getLayerConfig() {
|
|
1894
|
+
const graph = this._getLayerGraph();
|
|
1895
|
+
|
|
1896
|
+
if (!graph) {
|
|
1897
|
+
return {
|
|
1898
|
+
error: 'Layer relationship graph not available. Switch to holographic system first.',
|
|
1899
|
+
suggested_next_actions: ['switch_system']
|
|
1900
|
+
};
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
const config = graph.exportConfig();
|
|
1904
|
+
|
|
1905
|
+
return {
|
|
1906
|
+
keystone: config.keystone,
|
|
1907
|
+
active_profile: config.profile,
|
|
1908
|
+
relationships: config.relationships,
|
|
1909
|
+
shaders: config.shaders,
|
|
1910
|
+
available_profiles: ['holographic', 'symmetry', 'chord', 'storm', 'legacy'],
|
|
1911
|
+
available_presets: ['echo', 'mirror', 'complement', 'harmonic', 'reactive', 'chase'],
|
|
1912
|
+
suggested_next_actions: ['set_layer_profile', 'set_layer_relationship', 'tune_layer_relationship']
|
|
1913
|
+
};
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
/**
|
|
1917
|
+
* Tune a layer's relationship config.
|
|
1918
|
+
*/
|
|
1919
|
+
tuneLayerRelationship(args) {
|
|
1920
|
+
const { layer, config: configOverrides } = args;
|
|
1921
|
+
const graph = this._getLayerGraph();
|
|
1922
|
+
|
|
1923
|
+
if (!graph) {
|
|
1924
|
+
return {
|
|
1925
|
+
error: 'Layer relationship graph not available. Switch to holographic system first.',
|
|
1926
|
+
suggested_next_actions: ['switch_system']
|
|
1927
|
+
};
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
const graphConfig = graph.exportConfig();
|
|
1931
|
+
const currentRel = graphConfig.relationships[layer];
|
|
1932
|
+
|
|
1933
|
+
if (!currentRel || !currentRel.preset) {
|
|
1934
|
+
return {
|
|
1935
|
+
error: `Layer "${layer}" has no tunable preset relationship. Set one first with set_layer_relationship.`,
|
|
1936
|
+
suggested_next_actions: ['set_layer_relationship']
|
|
1937
|
+
};
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1940
|
+
const factory = PRESET_REGISTRY[currentRel.preset];
|
|
1941
|
+
if (!factory) {
|
|
1942
|
+
return {
|
|
1943
|
+
error: `Unknown preset "${currentRel.preset}" on layer "${layer}".`,
|
|
1944
|
+
suggested_next_actions: ['set_layer_relationship']
|
|
1945
|
+
};
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
const newConfig = { ...(currentRel.config || {}), ...configOverrides };
|
|
1949
|
+
graph.setRelationship(layer, { preset: currentRel.preset, config: newConfig });
|
|
1950
|
+
|
|
1951
|
+
telemetry.recordEvent(EventType.PARAMETER_CHANGE, {
|
|
1952
|
+
type: 'layer_tune', layer, tuned_keys: Object.keys(configOverrides)
|
|
1953
|
+
});
|
|
1954
|
+
|
|
1955
|
+
return {
|
|
1956
|
+
layer,
|
|
1957
|
+
preset: currentRel.preset,
|
|
1958
|
+
previous_config: currentRel.config,
|
|
1959
|
+
new_config: newConfig,
|
|
1960
|
+
suggested_next_actions: ['get_layer_config', 'describe_visual_state', 'capture_screenshot']
|
|
1961
|
+
};
|
|
1962
|
+
}
|
|
946
1963
|
}
|
|
947
1964
|
|
|
948
1965
|
// Singleton instance
|