@vib3code/sdk 2.0.1 → 2.0.3-canary.75a3290
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 +918 -0
- 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/holograms/HolographicVisualizer.js +29 -12
- package/src/holograms/RealHolographicSystem.js +194 -43
- 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,10 @@
|
|
|
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 { PRESET_REGISTRY } from '../../render/LayerRelationshipGraph.js';
|
|
9
13
|
|
|
10
14
|
/**
|
|
11
15
|
* Generate unique IDs
|
|
@@ -163,6 +167,60 @@ export class MCPServer {
|
|
|
163
167
|
case 'list_behavior_presets':
|
|
164
168
|
result = this.listBehaviorPresets();
|
|
165
169
|
break;
|
|
170
|
+
// Agent-power tools (Phase 7)
|
|
171
|
+
case 'describe_visual_state':
|
|
172
|
+
result = this.describeVisualState();
|
|
173
|
+
break;
|
|
174
|
+
case 'batch_set_parameters':
|
|
175
|
+
result = await this.batchSetParameters(args);
|
|
176
|
+
break;
|
|
177
|
+
case 'create_timeline':
|
|
178
|
+
result = this.createTimeline(args);
|
|
179
|
+
break;
|
|
180
|
+
case 'play_transition':
|
|
181
|
+
result = this.playTransition(args);
|
|
182
|
+
break;
|
|
183
|
+
case 'apply_color_preset':
|
|
184
|
+
result = this.applyColorPreset(args);
|
|
185
|
+
break;
|
|
186
|
+
case 'set_post_processing':
|
|
187
|
+
result = this.setPostProcessing(args);
|
|
188
|
+
break;
|
|
189
|
+
case 'create_choreography':
|
|
190
|
+
result = this.createChoreography(args);
|
|
191
|
+
break;
|
|
192
|
+
// Visual feedback tools (Phase 7.1)
|
|
193
|
+
case 'capture_screenshot':
|
|
194
|
+
result = await this.captureScreenshot(args);
|
|
195
|
+
break;
|
|
196
|
+
case 'design_from_description':
|
|
197
|
+
result = await this.designFromDescription(args);
|
|
198
|
+
break;
|
|
199
|
+
case 'get_aesthetic_vocabulary':
|
|
200
|
+
result = this.getAestheticVocabulary();
|
|
201
|
+
break;
|
|
202
|
+
case 'play_choreography':
|
|
203
|
+
result = await this.playChoreographyTool(args);
|
|
204
|
+
break;
|
|
205
|
+
case 'control_timeline':
|
|
206
|
+
result = this.controlTimeline(args);
|
|
207
|
+
break;
|
|
208
|
+
// Layer relationship tools (Phase 8)
|
|
209
|
+
case 'set_layer_profile':
|
|
210
|
+
result = this.setLayerProfile(args);
|
|
211
|
+
break;
|
|
212
|
+
case 'set_layer_relationship':
|
|
213
|
+
result = this.setLayerRelationship(args);
|
|
214
|
+
break;
|
|
215
|
+
case 'set_layer_keystone':
|
|
216
|
+
result = this.setLayerKeystone(args);
|
|
217
|
+
break;
|
|
218
|
+
case 'get_layer_config':
|
|
219
|
+
result = this.getLayerConfig();
|
|
220
|
+
break;
|
|
221
|
+
case 'tune_layer_relationship':
|
|
222
|
+
result = this.tuneLayerRelationship(args);
|
|
223
|
+
break;
|
|
166
224
|
default:
|
|
167
225
|
throw new Error(`Unknown tool: ${toolName}`);
|
|
168
226
|
}
|
|
@@ -943,6 +1001,866 @@ export class MCPServer {
|
|
|
943
1001
|
suggested_next_actions: ['apply_behavior_preset']
|
|
944
1002
|
};
|
|
945
1003
|
}
|
|
1004
|
+
|
|
1005
|
+
// ===== AGENT-POWER TOOLS (Phase 7 — Agent Harness) =====
|
|
1006
|
+
|
|
1007
|
+
/**
|
|
1008
|
+
* Generate a natural-language description of the current visual state.
|
|
1009
|
+
* Enables text-only agents to "see" what the visualization looks like.
|
|
1010
|
+
*/
|
|
1011
|
+
describeVisualState() {
|
|
1012
|
+
const state = this.getState();
|
|
1013
|
+
const params = state.visual || {};
|
|
1014
|
+
const rotation = state.rotation_state || {};
|
|
1015
|
+
const geometry = state.geometry || {};
|
|
1016
|
+
|
|
1017
|
+
// Color description from hue
|
|
1018
|
+
const hue = params.hue || 0;
|
|
1019
|
+
const colorName = hue < 15 ? 'red' : hue < 45 ? 'orange' : hue < 75 ? 'yellow' :
|
|
1020
|
+
hue < 150 ? 'green' : hue < 195 ? 'cyan' : hue < 255 ? 'blue' :
|
|
1021
|
+
hue < 285 ? 'purple' : hue < 330 ? 'magenta' : 'red';
|
|
1022
|
+
const satDesc = (params.saturation || 0.8) > 0.7 ? 'vivid' :
|
|
1023
|
+
(params.saturation || 0.8) > 0.4 ? 'moderate' : 'desaturated';
|
|
1024
|
+
const intensityDesc = (params.intensity || 0.5) > 0.7 ? 'bright' :
|
|
1025
|
+
(params.intensity || 0.5) > 0.3 ? 'medium brightness' : 'dim';
|
|
1026
|
+
|
|
1027
|
+
// Motion description
|
|
1028
|
+
const speed = params.speed || 1.0;
|
|
1029
|
+
const speedDesc = speed > 2.0 ? 'rapidly' : speed > 1.0 ? 'moderately' :
|
|
1030
|
+
speed > 0.4 ? 'slowly' : 'very slowly';
|
|
1031
|
+
const chaos = params.chaos || 0;
|
|
1032
|
+
const chaosDesc = chaos > 0.7 ? 'highly turbulent' : chaos > 0.3 ? 'somewhat organic' :
|
|
1033
|
+
chaos > 0.05 ? 'subtly alive' : 'perfectly still';
|
|
1034
|
+
|
|
1035
|
+
// 4D rotation activity
|
|
1036
|
+
const has4D = Math.abs(rotation.XW || 0) > 0.1 ||
|
|
1037
|
+
Math.abs(rotation.YW || 0) > 0.1 ||
|
|
1038
|
+
Math.abs(rotation.ZW || 0) > 0.1;
|
|
1039
|
+
const rotDesc = has4D ? 'with visible 4D hyperspace rotation (inside-out morphing)' :
|
|
1040
|
+
'in standard 3D orientation';
|
|
1041
|
+
|
|
1042
|
+
// Density/complexity
|
|
1043
|
+
const density = params.gridDensity || 10;
|
|
1044
|
+
const densityDesc = density > 50 ? 'extremely intricate' : density > 25 ? 'detailed' :
|
|
1045
|
+
density > 12 ? 'moderate detail' : 'bold and sparse';
|
|
1046
|
+
|
|
1047
|
+
// Projection
|
|
1048
|
+
const dim = params.dimension || 3.8;
|
|
1049
|
+
const projDesc = dim < 3.3 ? 'dramatic fish-eye distortion' :
|
|
1050
|
+
dim < 3.8 ? 'moderate perspective depth' : 'subtle, flattened perspective';
|
|
1051
|
+
|
|
1052
|
+
const description = [
|
|
1053
|
+
`A ${satDesc} ${colorName} ${geometry.core_type || 'base'} ${geometry.base_type || 'tetrahedron'}`,
|
|
1054
|
+
`rendered in the ${state.system || 'quantum'} system.`,
|
|
1055
|
+
`The pattern is ${densityDesc} and ${chaosDesc},`,
|
|
1056
|
+
`animating ${speedDesc} ${rotDesc}.`,
|
|
1057
|
+
`Color is ${intensityDesc} with ${projDesc}.`,
|
|
1058
|
+
params.morphFactor > 0.5 ? `Shape is morphing between geometries (factor: ${params.morphFactor}).` : ''
|
|
1059
|
+
].filter(Boolean).join(' ');
|
|
1060
|
+
|
|
1061
|
+
return {
|
|
1062
|
+
description,
|
|
1063
|
+
mood: this._assessMood(params),
|
|
1064
|
+
complexity: density > 40 ? 'high' : density > 15 ? 'medium' : 'low',
|
|
1065
|
+
motion_level: speed > 1.5 ? 'high' : speed > 0.5 ? 'medium' : 'low',
|
|
1066
|
+
has_4d_rotation: has4D,
|
|
1067
|
+
color_family: colorName,
|
|
1068
|
+
suggested_next_actions: ['set_visual_parameters', 'set_rotation', 'batch_set_parameters']
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
/**
|
|
1073
|
+
* Assess the emotional mood of the current visual state
|
|
1074
|
+
*/
|
|
1075
|
+
_assessMood(params) {
|
|
1076
|
+
const hue = params.hue || 0;
|
|
1077
|
+
const speed = params.speed || 1.0;
|
|
1078
|
+
const chaos = params.chaos || 0;
|
|
1079
|
+
const intensity = params.intensity || 0.5;
|
|
1080
|
+
|
|
1081
|
+
if (speed < 0.3 && chaos < 0.1) return 'serene';
|
|
1082
|
+
if (speed > 2.0 && chaos > 0.6) return 'chaotic';
|
|
1083
|
+
if (hue > 180 && hue < 260 && intensity < 0.5) return 'mysterious';
|
|
1084
|
+
if (hue > 0 && hue < 60 && intensity > 0.6) return 'warm';
|
|
1085
|
+
if (hue > 150 && hue < 200 && speed < 0.8) return 'tranquil';
|
|
1086
|
+
if (chaos > 0.5 && speed > 1.5) return 'energetic';
|
|
1087
|
+
if (intensity > 0.8) return 'vibrant';
|
|
1088
|
+
return 'balanced';
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
/**
|
|
1092
|
+
* Atomically set multiple parameter categories in one call
|
|
1093
|
+
*/
|
|
1094
|
+
async batchSetParameters(args) {
|
|
1095
|
+
const { system, geometry, rotation, visual, preset } = args;
|
|
1096
|
+
|
|
1097
|
+
// Switch system first if requested
|
|
1098
|
+
if (system && this.engine) {
|
|
1099
|
+
await this.engine.switchSystem(system);
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
// Set geometry
|
|
1103
|
+
if (geometry !== undefined && this.engine) {
|
|
1104
|
+
this.engine.setParameter('geometry', geometry);
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
// Set rotation
|
|
1108
|
+
if (rotation) {
|
|
1109
|
+
const rotMap = { XY: 'rot4dXY', XZ: 'rot4dXZ', YZ: 'rot4dYZ',
|
|
1110
|
+
XW: 'rot4dXW', YW: 'rot4dYW', ZW: 'rot4dZW' };
|
|
1111
|
+
for (const [key, value] of Object.entries(rotation)) {
|
|
1112
|
+
if (value !== undefined && this.engine) {
|
|
1113
|
+
this.engine.setParameter(rotMap[key], value);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
// Set visual parameters
|
|
1119
|
+
if (visual && this.engine) {
|
|
1120
|
+
for (const [key, value] of Object.entries(visual)) {
|
|
1121
|
+
this.engine.setParameter(key, value);
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
// Apply preset last (overrides relevant params)
|
|
1126
|
+
if (preset) {
|
|
1127
|
+
this.applyBehaviorPreset({ preset });
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
telemetry.recordEvent(EventType.PARAMETER_BATCH_CHANGE, {
|
|
1131
|
+
count: (rotation ? Object.keys(rotation).length : 0) +
|
|
1132
|
+
(visual ? Object.keys(visual).length : 0) +
|
|
1133
|
+
(system ? 1 : 0) + (geometry !== undefined ? 1 : 0)
|
|
1134
|
+
});
|
|
1135
|
+
|
|
1136
|
+
return {
|
|
1137
|
+
...this.getState(),
|
|
1138
|
+
batch_applied: true,
|
|
1139
|
+
suggested_next_actions: ['describe_visual_state', 'save_to_gallery', 'create_timeline']
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
/**
|
|
1144
|
+
* Create a ParameterTimeline from agent specification
|
|
1145
|
+
*/
|
|
1146
|
+
createTimeline(args) {
|
|
1147
|
+
const { name, duration_ms, bpm, loop_mode = 'once', tracks } = args;
|
|
1148
|
+
|
|
1149
|
+
const timelineId = generateId('timeline');
|
|
1150
|
+
|
|
1151
|
+
// Validate tracks have properly sorted keyframes
|
|
1152
|
+
const validatedTracks = {};
|
|
1153
|
+
for (const [param, keyframes] of Object.entries(tracks)) {
|
|
1154
|
+
validatedTracks[param] = keyframes
|
|
1155
|
+
.map(kf => ({
|
|
1156
|
+
time: Math.max(0, Math.min(kf.time, duration_ms)),
|
|
1157
|
+
value: kf.value,
|
|
1158
|
+
easing: kf.easing || 'easeInOut'
|
|
1159
|
+
}))
|
|
1160
|
+
.sort((a, b) => a.time - b.time);
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
// Build timeline data for ParameterTimeline consumption
|
|
1164
|
+
const timelineData = {
|
|
1165
|
+
id: timelineId,
|
|
1166
|
+
name: name || `Timeline ${timelineId}`,
|
|
1167
|
+
duration: duration_ms,
|
|
1168
|
+
bpm: bpm || null,
|
|
1169
|
+
loopMode: loop_mode,
|
|
1170
|
+
tracks: validatedTracks
|
|
1171
|
+
};
|
|
1172
|
+
|
|
1173
|
+
// If engine is available, create and start the timeline
|
|
1174
|
+
if (this.engine) {
|
|
1175
|
+
// Store for later retrieval
|
|
1176
|
+
if (!this._timelines) this._timelines = new Map();
|
|
1177
|
+
this._timelines.set(timelineId, timelineData);
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
return {
|
|
1181
|
+
timeline_id: timelineId,
|
|
1182
|
+
name: timelineData.name,
|
|
1183
|
+
duration_ms,
|
|
1184
|
+
bpm: bpm || null,
|
|
1185
|
+
loop_mode,
|
|
1186
|
+
track_count: Object.keys(validatedTracks).length,
|
|
1187
|
+
tracks_summary: Object.entries(validatedTracks).map(([param, kfs]) => ({
|
|
1188
|
+
parameter: param,
|
|
1189
|
+
keyframe_count: kfs.length,
|
|
1190
|
+
value_range: [
|
|
1191
|
+
Math.min(...kfs.map(k => k.value)),
|
|
1192
|
+
Math.max(...kfs.map(k => k.value))
|
|
1193
|
+
]
|
|
1194
|
+
})),
|
|
1195
|
+
load_code: `const tl = new ParameterTimeline((n, v) => engine.setParameter(n, v));\ntl.importTimeline(${JSON.stringify(timelineData)});\ntl.play();`,
|
|
1196
|
+
suggested_next_actions: ['play_transition', 'describe_visual_state', 'save_to_gallery']
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
/**
|
|
1201
|
+
* Play a smooth transition sequence
|
|
1202
|
+
*/
|
|
1203
|
+
playTransition(args) {
|
|
1204
|
+
const { sequence } = args;
|
|
1205
|
+
|
|
1206
|
+
const transitionId = generateId('transition');
|
|
1207
|
+
|
|
1208
|
+
// Validate and normalize the sequence
|
|
1209
|
+
const normalizedSequence = sequence.map((step, i) => ({
|
|
1210
|
+
params: step.params,
|
|
1211
|
+
duration: step.duration || 1000,
|
|
1212
|
+
easing: step.easing || 'easeInOut',
|
|
1213
|
+
delay: step.delay || 0
|
|
1214
|
+
}));
|
|
1215
|
+
|
|
1216
|
+
const totalDuration = normalizedSequence.reduce(
|
|
1217
|
+
(sum, step) => sum + step.duration + step.delay, 0
|
|
1218
|
+
);
|
|
1219
|
+
|
|
1220
|
+
return {
|
|
1221
|
+
transition_id: transitionId,
|
|
1222
|
+
step_count: normalizedSequence.length,
|
|
1223
|
+
total_duration_ms: totalDuration,
|
|
1224
|
+
steps: normalizedSequence.map((step, i) => ({
|
|
1225
|
+
index: i,
|
|
1226
|
+
params: Object.keys(step.params),
|
|
1227
|
+
duration: step.duration,
|
|
1228
|
+
easing: step.easing,
|
|
1229
|
+
delay: step.delay
|
|
1230
|
+
})),
|
|
1231
|
+
load_code: `const animator = new TransitionAnimator(\n (n, v) => engine.setParameter(n, v),\n (n) => engine.getParameter(n)\n);\nanimator.sequence(${JSON.stringify(normalizedSequence)});`,
|
|
1232
|
+
suggested_next_actions: ['describe_visual_state', 'create_timeline', 'save_to_gallery']
|
|
1233
|
+
};
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
/**
|
|
1237
|
+
* Apply a named color preset
|
|
1238
|
+
*/
|
|
1239
|
+
applyColorPreset(args) {
|
|
1240
|
+
const { preset } = args;
|
|
1241
|
+
|
|
1242
|
+
// Color preset hue/saturation mappings (subset — full list in ColorPresetsSystem)
|
|
1243
|
+
const COLOR_PRESETS = {
|
|
1244
|
+
Ocean: { hue: 200, saturation: 0.8, intensity: 0.6 },
|
|
1245
|
+
Lava: { hue: 15, saturation: 0.9, intensity: 0.8 },
|
|
1246
|
+
Neon: { hue: 300, saturation: 1.0, intensity: 0.9 },
|
|
1247
|
+
Monochrome: { hue: 0, saturation: 0.0, intensity: 0.6 },
|
|
1248
|
+
Sunset: { hue: 30, saturation: 0.85, intensity: 0.7 },
|
|
1249
|
+
Aurora: { hue: 140, saturation: 0.7, intensity: 0.6 },
|
|
1250
|
+
Cyberpunk: { hue: 280, saturation: 0.9, intensity: 0.8 },
|
|
1251
|
+
Forest: { hue: 120, saturation: 0.6, intensity: 0.5 },
|
|
1252
|
+
Desert: { hue: 40, saturation: 0.5, intensity: 0.7 },
|
|
1253
|
+
Galaxy: { hue: 260, saturation: 0.8, intensity: 0.4 },
|
|
1254
|
+
Ice: { hue: 190, saturation: 0.5, intensity: 0.8 },
|
|
1255
|
+
Fire: { hue: 10, saturation: 1.0, intensity: 0.9 },
|
|
1256
|
+
Toxic: { hue: 100, saturation: 0.9, intensity: 0.7 },
|
|
1257
|
+
Royal: { hue: 270, saturation: 0.7, intensity: 0.5 },
|
|
1258
|
+
Pastel: { hue: 330, saturation: 0.3, intensity: 0.8 },
|
|
1259
|
+
Retro: { hue: 50, saturation: 0.7, intensity: 0.6 },
|
|
1260
|
+
Midnight: { hue: 240, saturation: 0.6, intensity: 0.3 },
|
|
1261
|
+
Tropical: { hue: 160, saturation: 0.8, intensity: 0.7 },
|
|
1262
|
+
Ethereal: { hue: 220, saturation: 0.4, intensity: 0.7 },
|
|
1263
|
+
Volcanic: { hue: 5, saturation: 0.95, intensity: 0.6 },
|
|
1264
|
+
Holographic: { hue: 180, saturation: 0.6, intensity: 0.8 },
|
|
1265
|
+
Vaporwave: { hue: 310, saturation: 0.7, intensity: 0.7 }
|
|
1266
|
+
};
|
|
1267
|
+
|
|
1268
|
+
const presetData = COLOR_PRESETS[preset];
|
|
1269
|
+
if (!presetData) {
|
|
1270
|
+
return {
|
|
1271
|
+
error: {
|
|
1272
|
+
type: 'ValidationError',
|
|
1273
|
+
code: 'INVALID_COLOR_PRESET',
|
|
1274
|
+
message: `Unknown color preset: ${preset}`,
|
|
1275
|
+
valid_options: Object.keys(COLOR_PRESETS)
|
|
1276
|
+
}
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
if (this.engine) {
|
|
1281
|
+
this.engine.setParameter('hue', presetData.hue);
|
|
1282
|
+
this.engine.setParameter('saturation', presetData.saturation);
|
|
1283
|
+
this.engine.setParameter('intensity', presetData.intensity);
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
return {
|
|
1287
|
+
preset,
|
|
1288
|
+
applied: presetData,
|
|
1289
|
+
suggested_next_actions: ['set_post_processing', 'describe_visual_state', 'set_visual_parameters']
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
/**
|
|
1294
|
+
* Configure post-processing effects pipeline
|
|
1295
|
+
*/
|
|
1296
|
+
setPostProcessing(args) {
|
|
1297
|
+
const { effects, chain_preset, clear_first = true } = args;
|
|
1298
|
+
|
|
1299
|
+
return {
|
|
1300
|
+
applied: true,
|
|
1301
|
+
effects: effects || [],
|
|
1302
|
+
chain_preset: chain_preset || null,
|
|
1303
|
+
cleared_previous: clear_first,
|
|
1304
|
+
load_code: effects ?
|
|
1305
|
+
`const pipeline = new PostProcessingPipeline(gl, canvas);\n${effects.map(e => `pipeline.addEffect('${e.name}', { intensity: ${e.intensity || 0.5} });`).join('\n')}` :
|
|
1306
|
+
`pipeline.applyChain('${chain_preset}');`,
|
|
1307
|
+
suggested_next_actions: ['describe_visual_state', 'apply_color_preset', 'create_choreography']
|
|
1308
|
+
};
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
/**
|
|
1312
|
+
* Create a multi-scene choreography — the most powerful agent composition tool
|
|
1313
|
+
*/
|
|
1314
|
+
createChoreography(args) {
|
|
1315
|
+
const { name, duration_ms, bpm, scenes } = args;
|
|
1316
|
+
|
|
1317
|
+
const choreographyId = generateId('choreo');
|
|
1318
|
+
|
|
1319
|
+
// Validate scene time ranges don't exceed duration
|
|
1320
|
+
const validatedScenes = scenes.map((scene, i) => ({
|
|
1321
|
+
index: i,
|
|
1322
|
+
time_start: Math.max(0, scene.time_start),
|
|
1323
|
+
time_end: Math.min(scene.time_end, duration_ms),
|
|
1324
|
+
system: scene.system,
|
|
1325
|
+
geometry: scene.geometry ?? 0,
|
|
1326
|
+
transition_in: scene.transition_in || { type: 'cut', duration: 0 },
|
|
1327
|
+
tracks: scene.tracks || {},
|
|
1328
|
+
color_preset: scene.color_preset || null,
|
|
1329
|
+
post_processing: scene.post_processing || [],
|
|
1330
|
+
audio: scene.audio || null
|
|
1331
|
+
}));
|
|
1332
|
+
|
|
1333
|
+
const choreography = {
|
|
1334
|
+
id: choreographyId,
|
|
1335
|
+
name: name || `Choreography ${choreographyId}`,
|
|
1336
|
+
duration_ms,
|
|
1337
|
+
bpm: bpm || null,
|
|
1338
|
+
scene_count: validatedScenes.length,
|
|
1339
|
+
scenes: validatedScenes
|
|
1340
|
+
};
|
|
1341
|
+
|
|
1342
|
+
// Store for later retrieval
|
|
1343
|
+
if (!this._choreographies) this._choreographies = new Map();
|
|
1344
|
+
this._choreographies.set(choreographyId, choreography);
|
|
1345
|
+
|
|
1346
|
+
return {
|
|
1347
|
+
choreography_id: choreographyId,
|
|
1348
|
+
name: choreography.name,
|
|
1349
|
+
duration_ms,
|
|
1350
|
+
bpm: bpm || null,
|
|
1351
|
+
scene_count: validatedScenes.length,
|
|
1352
|
+
scenes_summary: validatedScenes.map(s => ({
|
|
1353
|
+
index: s.index,
|
|
1354
|
+
time: `${s.time_start}ms → ${s.time_end}ms`,
|
|
1355
|
+
system: s.system,
|
|
1356
|
+
geometry: s.geometry,
|
|
1357
|
+
transition: s.transition_in.type,
|
|
1358
|
+
track_count: Object.keys(s.tracks).length,
|
|
1359
|
+
color_preset: s.color_preset,
|
|
1360
|
+
effects: s.post_processing
|
|
1361
|
+
})),
|
|
1362
|
+
choreography_json: JSON.stringify(choreography, null, 2),
|
|
1363
|
+
suggested_next_actions: ['describe_visual_state', 'export_package']
|
|
1364
|
+
};
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
// ===== VISUAL FEEDBACK TOOLS (Phase 7.1 — Agent Harness) =====
|
|
1368
|
+
|
|
1369
|
+
/**
|
|
1370
|
+
* Capture the current visualization as a base64 PNG by compositing all canvas layers.
|
|
1371
|
+
* Only works in browser context where canvases exist.
|
|
1372
|
+
*/
|
|
1373
|
+
async captureScreenshot(args) {
|
|
1374
|
+
const { width = 512, height = 512, format = 'png', quality = 0.92 } = args;
|
|
1375
|
+
|
|
1376
|
+
const isBrowser = typeof document !== 'undefined';
|
|
1377
|
+
if (!isBrowser) {
|
|
1378
|
+
return {
|
|
1379
|
+
error: {
|
|
1380
|
+
type: 'EnvironmentError',
|
|
1381
|
+
code: 'NO_BROWSER_CONTEXT',
|
|
1382
|
+
message: 'capture_screenshot requires a browser context with canvas elements',
|
|
1383
|
+
suggestion: 'Use the headless renderer tool (tools/headless-renderer.js) for Node.js environments'
|
|
1384
|
+
}
|
|
1385
|
+
};
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
try {
|
|
1389
|
+
// Create composite canvas
|
|
1390
|
+
const composite = document.createElement('canvas');
|
|
1391
|
+
composite.width = width;
|
|
1392
|
+
composite.height = height;
|
|
1393
|
+
const ctx = composite.getContext('2d');
|
|
1394
|
+
|
|
1395
|
+
if (!ctx) {
|
|
1396
|
+
return {
|
|
1397
|
+
error: { type: 'SystemError', code: 'CANVAS_CONTEXT_FAILED', message: 'Could not get 2D context for compositing' }
|
|
1398
|
+
};
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
// Fill with black background
|
|
1402
|
+
ctx.fillStyle = '#000000';
|
|
1403
|
+
ctx.fillRect(0, 0, width, height);
|
|
1404
|
+
|
|
1405
|
+
// Find all visualization canvases and composite them in z-order
|
|
1406
|
+
const canvases = document.querySelectorAll('canvas.visualization-canvas');
|
|
1407
|
+
const sortedCanvases = Array.from(canvases).sort((a, b) => {
|
|
1408
|
+
const zA = parseInt(a.style.zIndex || '0', 10);
|
|
1409
|
+
const zB = parseInt(b.style.zIndex || '0', 10);
|
|
1410
|
+
return zA - zB;
|
|
1411
|
+
});
|
|
1412
|
+
|
|
1413
|
+
for (const canvas of sortedCanvases) {
|
|
1414
|
+
if (canvas.width > 0 && canvas.height > 0) {
|
|
1415
|
+
ctx.drawImage(canvas, 0, 0, width, height);
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
// If no canvases found, try to find any canvas at all
|
|
1420
|
+
if (sortedCanvases.length === 0) {
|
|
1421
|
+
const anyCanvas = document.querySelector('canvas');
|
|
1422
|
+
if (anyCanvas && anyCanvas.width > 0) {
|
|
1423
|
+
ctx.drawImage(anyCanvas, 0, 0, width, height);
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
// Convert to data URL
|
|
1428
|
+
const mimeType = format === 'jpeg' ? 'image/jpeg' : format === 'webp' ? 'image/webp' : 'image/png';
|
|
1429
|
+
const dataUrl = composite.toDataURL(mimeType, quality);
|
|
1430
|
+
const base64 = dataUrl.split(',')[1];
|
|
1431
|
+
|
|
1432
|
+
// Clean up
|
|
1433
|
+
composite.remove();
|
|
1434
|
+
|
|
1435
|
+
return {
|
|
1436
|
+
format,
|
|
1437
|
+
width,
|
|
1438
|
+
height,
|
|
1439
|
+
mime_type: mimeType,
|
|
1440
|
+
data_url: dataUrl,
|
|
1441
|
+
base64_length: base64.length,
|
|
1442
|
+
canvas_count: sortedCanvases.length,
|
|
1443
|
+
suggested_next_actions: ['describe_visual_state', 'set_visual_parameters', 'batch_set_parameters']
|
|
1444
|
+
};
|
|
1445
|
+
} catch (err) {
|
|
1446
|
+
return {
|
|
1447
|
+
error: {
|
|
1448
|
+
type: 'SystemError',
|
|
1449
|
+
code: 'SCREENSHOT_FAILED',
|
|
1450
|
+
message: err.message,
|
|
1451
|
+
suggestion: 'Check that canvas elements exist and are rendered'
|
|
1452
|
+
}
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
/**
|
|
1458
|
+
* Map a natural-language description to VIB3+ parameters using AestheticMapper.
|
|
1459
|
+
*/
|
|
1460
|
+
async designFromDescription(args) {
|
|
1461
|
+
const { description, apply = false } = args;
|
|
1462
|
+
|
|
1463
|
+
if (!this._aestheticMapper) {
|
|
1464
|
+
this._aestheticMapper = new AestheticMapper();
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
const mapped = this._aestheticMapper.mapDescription(description);
|
|
1468
|
+
const resolved = this._aestheticMapper.resolveToValues(description);
|
|
1469
|
+
|
|
1470
|
+
// Apply to engine if requested
|
|
1471
|
+
if (apply && this.engine) {
|
|
1472
|
+
if (resolved.system) {
|
|
1473
|
+
await this.engine.switchSystem(resolved.system);
|
|
1474
|
+
}
|
|
1475
|
+
if (resolved.geometry !== undefined) {
|
|
1476
|
+
this.engine.setParameter('geometry', resolved.geometry);
|
|
1477
|
+
}
|
|
1478
|
+
for (const [param, value] of Object.entries(resolved.params)) {
|
|
1479
|
+
this.engine.setParameter(param, value);
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
return {
|
|
1484
|
+
description,
|
|
1485
|
+
applied: apply,
|
|
1486
|
+
matched_words: mapped.matched_words,
|
|
1487
|
+
total_words: mapped.total_words,
|
|
1488
|
+
resolved: {
|
|
1489
|
+
system: resolved.system,
|
|
1490
|
+
geometry: resolved.geometry,
|
|
1491
|
+
params: resolved.params,
|
|
1492
|
+
color_preset: resolved.color_preset,
|
|
1493
|
+
post_processing: resolved.post_processing
|
|
1494
|
+
},
|
|
1495
|
+
parameter_ranges: mapped.params,
|
|
1496
|
+
suggested_next_actions: apply
|
|
1497
|
+
? ['describe_visual_state', 'capture_screenshot', 'create_timeline']
|
|
1498
|
+
: ['design_from_description', 'batch_set_parameters']
|
|
1499
|
+
};
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
/**
|
|
1503
|
+
* Return the full aesthetic vocabulary by category.
|
|
1504
|
+
*/
|
|
1505
|
+
getAestheticVocabulary() {
|
|
1506
|
+
if (!this._aestheticMapper) {
|
|
1507
|
+
this._aestheticMapper = new AestheticMapper();
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
return {
|
|
1511
|
+
vocabulary: this._aestheticMapper.getVocabularyByCategory(),
|
|
1512
|
+
all_words: this._aestheticMapper.getVocabulary(),
|
|
1513
|
+
word_count: this._aestheticMapper.getVocabulary().length,
|
|
1514
|
+
usage: 'Pass space-separated words to design_from_description. Example: "serene ocean deep minimal"',
|
|
1515
|
+
suggested_next_actions: ['design_from_description']
|
|
1516
|
+
};
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
/**
|
|
1520
|
+
* Load and play a choreography.
|
|
1521
|
+
*/
|
|
1522
|
+
async playChoreographyTool(args) {
|
|
1523
|
+
const { choreography, choreography_id, action = 'play', seek_percent, loop = false } = args;
|
|
1524
|
+
|
|
1525
|
+
// Resolve choreography spec
|
|
1526
|
+
let spec = choreography;
|
|
1527
|
+
if (!spec && choreography_id && this._choreographies) {
|
|
1528
|
+
spec = this._choreographies.get(choreography_id);
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
if (!spec && action === 'play') {
|
|
1532
|
+
return {
|
|
1533
|
+
error: {
|
|
1534
|
+
type: 'ValidationError',
|
|
1535
|
+
code: 'NO_CHOREOGRAPHY',
|
|
1536
|
+
message: 'Provide a choreography spec or a valid choreography_id',
|
|
1537
|
+
suggestion: 'Use create_choreography first, then pass the result here'
|
|
1538
|
+
}
|
|
1539
|
+
};
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
// Create or reuse player
|
|
1543
|
+
if (!this._choreographyPlayer && this.engine) {
|
|
1544
|
+
this._choreographyPlayer = new ChoreographyPlayer(this.engine, {
|
|
1545
|
+
onSceneChange: (index, scene) => {
|
|
1546
|
+
telemetry.recordEvent(EventType.PARAMETER_CHANGE, {
|
|
1547
|
+
type: 'choreography_scene',
|
|
1548
|
+
scene_index: index,
|
|
1549
|
+
system: scene.system
|
|
1550
|
+
});
|
|
1551
|
+
}
|
|
1552
|
+
});
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
if (!this._choreographyPlayer) {
|
|
1556
|
+
return {
|
|
1557
|
+
error: {
|
|
1558
|
+
type: 'SystemError',
|
|
1559
|
+
code: 'NO_ENGINE',
|
|
1560
|
+
message: 'Engine not initialized — cannot play choreography',
|
|
1561
|
+
suggestion: 'Initialize the VIB3Engine first'
|
|
1562
|
+
}
|
|
1563
|
+
};
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
const player = this._choreographyPlayer;
|
|
1567
|
+
|
|
1568
|
+
switch (action) {
|
|
1569
|
+
case 'play':
|
|
1570
|
+
if (spec) {
|
|
1571
|
+
player.load(spec);
|
|
1572
|
+
player.loopMode = loop ? 'loop' : 'once';
|
|
1573
|
+
}
|
|
1574
|
+
player.play();
|
|
1575
|
+
break;
|
|
1576
|
+
case 'pause':
|
|
1577
|
+
player.pause();
|
|
1578
|
+
break;
|
|
1579
|
+
case 'stop':
|
|
1580
|
+
player.stop();
|
|
1581
|
+
break;
|
|
1582
|
+
case 'seek':
|
|
1583
|
+
if (seek_percent !== undefined) {
|
|
1584
|
+
player.seekToPercent(seek_percent);
|
|
1585
|
+
}
|
|
1586
|
+
break;
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
return {
|
|
1590
|
+
action,
|
|
1591
|
+
state: player.getState(),
|
|
1592
|
+
suggested_next_actions: action === 'play'
|
|
1593
|
+
? ['play_choreography', 'capture_screenshot', 'describe_visual_state']
|
|
1594
|
+
: ['play_choreography']
|
|
1595
|
+
};
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
/**
|
|
1599
|
+
* Control a previously created timeline.
|
|
1600
|
+
*/
|
|
1601
|
+
controlTimeline(args) {
|
|
1602
|
+
const { timeline_id, action, seek_percent, speed } = args;
|
|
1603
|
+
|
|
1604
|
+
if (!this._liveTimelines) this._liveTimelines = new Map();
|
|
1605
|
+
|
|
1606
|
+
let tl = this._liveTimelines.get(timeline_id);
|
|
1607
|
+
|
|
1608
|
+
// If timeline not live yet, try to create it from stored data
|
|
1609
|
+
if (!tl && this._timelines && this._timelines.has(timeline_id) && this.engine) {
|
|
1610
|
+
const data = this._timelines.get(timeline_id);
|
|
1611
|
+
|
|
1612
|
+
tl = new ParameterTimeline(
|
|
1613
|
+
(name, value) => this.engine.setParameter(name, value)
|
|
1614
|
+
);
|
|
1615
|
+
|
|
1616
|
+
// Build import-compatible format
|
|
1617
|
+
const importData = {
|
|
1618
|
+
type: 'vib3-parameter-timeline',
|
|
1619
|
+
version: '1.0.0',
|
|
1620
|
+
duration: data.duration,
|
|
1621
|
+
loopMode: data.loopMode || 'once',
|
|
1622
|
+
bpm: data.bpm || 120,
|
|
1623
|
+
tracks: {}
|
|
1624
|
+
};
|
|
1625
|
+
|
|
1626
|
+
for (const [param, keyframes] of Object.entries(data.tracks)) {
|
|
1627
|
+
importData.tracks[param] = {
|
|
1628
|
+
enabled: true,
|
|
1629
|
+
keyframes: keyframes.map(kf => ({
|
|
1630
|
+
time: kf.time,
|
|
1631
|
+
value: kf.value,
|
|
1632
|
+
easing: kf.easing || 'easeInOut'
|
|
1633
|
+
}))
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
tl.importTimeline(importData);
|
|
1638
|
+
this._liveTimelines.set(timeline_id, tl);
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
if (!tl) {
|
|
1642
|
+
return {
|
|
1643
|
+
error: {
|
|
1644
|
+
type: 'ValidationError',
|
|
1645
|
+
code: 'TIMELINE_NOT_FOUND',
|
|
1646
|
+
message: `Timeline '${timeline_id}' not found`,
|
|
1647
|
+
suggestion: 'Create a timeline first with create_timeline'
|
|
1648
|
+
}
|
|
1649
|
+
};
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
switch (action) {
|
|
1653
|
+
case 'play':
|
|
1654
|
+
tl.play();
|
|
1655
|
+
break;
|
|
1656
|
+
case 'pause':
|
|
1657
|
+
tl.pause();
|
|
1658
|
+
break;
|
|
1659
|
+
case 'stop':
|
|
1660
|
+
tl.stop();
|
|
1661
|
+
break;
|
|
1662
|
+
case 'seek':
|
|
1663
|
+
if (seek_percent !== undefined) {
|
|
1664
|
+
tl.seekToPercent(seek_percent);
|
|
1665
|
+
}
|
|
1666
|
+
break;
|
|
1667
|
+
case 'set_speed':
|
|
1668
|
+
if (speed !== undefined) {
|
|
1669
|
+
tl.playbackSpeed = Math.max(0.1, Math.min(10, speed));
|
|
1670
|
+
}
|
|
1671
|
+
break;
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
return {
|
|
1675
|
+
timeline_id,
|
|
1676
|
+
action,
|
|
1677
|
+
playing: tl.playing,
|
|
1678
|
+
currentTime: tl.currentTime,
|
|
1679
|
+
duration: tl.duration,
|
|
1680
|
+
progress: tl.duration > 0 ? tl.currentTime / tl.duration : 0,
|
|
1681
|
+
playbackSpeed: tl.playbackSpeed,
|
|
1682
|
+
suggested_next_actions: ['control_timeline', 'describe_visual_state', 'capture_screenshot']
|
|
1683
|
+
};
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
// ====================================================================
|
|
1687
|
+
// Layer Relationship Tools (Phase 8)
|
|
1688
|
+
// ====================================================================
|
|
1689
|
+
|
|
1690
|
+
/**
|
|
1691
|
+
* Get the holographic system's layer graph (if available).
|
|
1692
|
+
* @private
|
|
1693
|
+
* @returns {import('../../render/LayerRelationshipGraph.js').LayerRelationshipGraph|null}
|
|
1694
|
+
*/
|
|
1695
|
+
_getLayerGraph() {
|
|
1696
|
+
if (!this.engine) return null;
|
|
1697
|
+
// Try to access the current system's layer graph
|
|
1698
|
+
const system = this.engine.currentSystem || this.engine._activeSystem;
|
|
1699
|
+
if (system && system.layerGraph) {
|
|
1700
|
+
return system.layerGraph;
|
|
1701
|
+
}
|
|
1702
|
+
if (system && system._layerGraph) {
|
|
1703
|
+
return system._layerGraph;
|
|
1704
|
+
}
|
|
1705
|
+
return null;
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
/**
|
|
1709
|
+
* Load a named layer relationship profile.
|
|
1710
|
+
*/
|
|
1711
|
+
setLayerProfile(args) {
|
|
1712
|
+
const { profile } = args;
|
|
1713
|
+
const graph = this._getLayerGraph();
|
|
1714
|
+
|
|
1715
|
+
if (!graph) {
|
|
1716
|
+
return {
|
|
1717
|
+
error: 'Layer relationship graph not available. Switch to holographic system first.',
|
|
1718
|
+
suggested_next_actions: ['switch_system']
|
|
1719
|
+
};
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
graph.loadProfile(profile);
|
|
1723
|
+
telemetry.recordEvent(EventType.PARAMETER_CHANGE, { type: 'layer_profile', profile });
|
|
1724
|
+
|
|
1725
|
+
return {
|
|
1726
|
+
profile,
|
|
1727
|
+
keystone: graph.keystone,
|
|
1728
|
+
active_profile: graph.activeProfile,
|
|
1729
|
+
available_profiles: ['holographic', 'symmetry', 'chord', 'storm', 'legacy'],
|
|
1730
|
+
suggested_next_actions: ['get_layer_config', 'set_layer_relationship', 'tune_layer_relationship']
|
|
1731
|
+
};
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
/**
|
|
1735
|
+
* Set relationship for a specific layer.
|
|
1736
|
+
*/
|
|
1737
|
+
setLayerRelationship(args) {
|
|
1738
|
+
const { layer, relationship, config } = args;
|
|
1739
|
+
const graph = this._getLayerGraph();
|
|
1740
|
+
|
|
1741
|
+
if (!graph) {
|
|
1742
|
+
return {
|
|
1743
|
+
error: 'Layer relationship graph not available. Switch to holographic system first.',
|
|
1744
|
+
suggested_next_actions: ['switch_system']
|
|
1745
|
+
};
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
if (config) {
|
|
1749
|
+
graph.setRelationship(layer, { preset: relationship, config });
|
|
1750
|
+
} else {
|
|
1751
|
+
graph.setRelationship(layer, relationship);
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
telemetry.recordEvent(EventType.PARAMETER_CHANGE, {
|
|
1755
|
+
type: 'layer_relationship', layer, relationship
|
|
1756
|
+
});
|
|
1757
|
+
|
|
1758
|
+
return {
|
|
1759
|
+
layer,
|
|
1760
|
+
relationship,
|
|
1761
|
+
config: config || {},
|
|
1762
|
+
keystone: graph.keystone,
|
|
1763
|
+
suggested_next_actions: ['get_layer_config', 'tune_layer_relationship', 'describe_visual_state']
|
|
1764
|
+
};
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
/**
|
|
1768
|
+
* Change the keystone (driver) layer.
|
|
1769
|
+
*/
|
|
1770
|
+
setLayerKeystone(args) {
|
|
1771
|
+
const { layer } = args;
|
|
1772
|
+
const graph = this._getLayerGraph();
|
|
1773
|
+
|
|
1774
|
+
if (!graph) {
|
|
1775
|
+
return {
|
|
1776
|
+
error: 'Layer relationship graph not available. Switch to holographic system first.',
|
|
1777
|
+
suggested_next_actions: ['switch_system']
|
|
1778
|
+
};
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
graph.setKeystone(layer);
|
|
1782
|
+
telemetry.recordEvent(EventType.PARAMETER_CHANGE, { type: 'layer_keystone', layer });
|
|
1783
|
+
|
|
1784
|
+
return {
|
|
1785
|
+
keystone: layer,
|
|
1786
|
+
note: 'Other layers\' relationships are preserved. Set new relationships for the old keystone if needed.',
|
|
1787
|
+
suggested_next_actions: ['set_layer_relationship', 'get_layer_config']
|
|
1788
|
+
};
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
/**
|
|
1792
|
+
* Get current layer configuration.
|
|
1793
|
+
*/
|
|
1794
|
+
getLayerConfig() {
|
|
1795
|
+
const graph = this._getLayerGraph();
|
|
1796
|
+
|
|
1797
|
+
if (!graph) {
|
|
1798
|
+
return {
|
|
1799
|
+
error: 'Layer relationship graph not available. Switch to holographic system first.',
|
|
1800
|
+
suggested_next_actions: ['switch_system']
|
|
1801
|
+
};
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
const config = graph.exportConfig();
|
|
1805
|
+
|
|
1806
|
+
return {
|
|
1807
|
+
keystone: config.keystone,
|
|
1808
|
+
active_profile: config.profile,
|
|
1809
|
+
relationships: config.relationships,
|
|
1810
|
+
shaders: config.shaders,
|
|
1811
|
+
available_profiles: ['holographic', 'symmetry', 'chord', 'storm', 'legacy'],
|
|
1812
|
+
available_presets: ['echo', 'mirror', 'complement', 'harmonic', 'reactive', 'chase'],
|
|
1813
|
+
suggested_next_actions: ['set_layer_profile', 'set_layer_relationship', 'tune_layer_relationship']
|
|
1814
|
+
};
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
/**
|
|
1818
|
+
* Tune a layer's relationship config.
|
|
1819
|
+
*/
|
|
1820
|
+
tuneLayerRelationship(args) {
|
|
1821
|
+
const { layer, config: configOverrides } = args;
|
|
1822
|
+
const graph = this._getLayerGraph();
|
|
1823
|
+
|
|
1824
|
+
if (!graph) {
|
|
1825
|
+
return {
|
|
1826
|
+
error: 'Layer relationship graph not available. Switch to holographic system first.',
|
|
1827
|
+
suggested_next_actions: ['switch_system']
|
|
1828
|
+
};
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
const graphConfig = graph.exportConfig();
|
|
1832
|
+
const currentRel = graphConfig.relationships[layer];
|
|
1833
|
+
|
|
1834
|
+
if (!currentRel || !currentRel.preset) {
|
|
1835
|
+
return {
|
|
1836
|
+
error: `Layer "${layer}" has no tunable preset relationship. Set one first with set_layer_relationship.`,
|
|
1837
|
+
suggested_next_actions: ['set_layer_relationship']
|
|
1838
|
+
};
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
const factory = PRESET_REGISTRY[currentRel.preset];
|
|
1842
|
+
if (!factory) {
|
|
1843
|
+
return {
|
|
1844
|
+
error: `Unknown preset "${currentRel.preset}" on layer "${layer}".`,
|
|
1845
|
+
suggested_next_actions: ['set_layer_relationship']
|
|
1846
|
+
};
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
const newConfig = { ...(currentRel.config || {}), ...configOverrides };
|
|
1850
|
+
graph.setRelationship(layer, { preset: currentRel.preset, config: newConfig });
|
|
1851
|
+
|
|
1852
|
+
telemetry.recordEvent(EventType.PARAMETER_CHANGE, {
|
|
1853
|
+
type: 'layer_tune', layer, tuned_keys: Object.keys(configOverrides)
|
|
1854
|
+
});
|
|
1855
|
+
|
|
1856
|
+
return {
|
|
1857
|
+
layer,
|
|
1858
|
+
preset: currentRel.preset,
|
|
1859
|
+
previous_config: currentRel.config,
|
|
1860
|
+
new_config: newConfig,
|
|
1861
|
+
suggested_next_actions: ['get_layer_config', 'describe_visual_state', 'capture_screenshot']
|
|
1862
|
+
};
|
|
1863
|
+
}
|
|
946
1864
|
}
|
|
947
1865
|
|
|
948
1866
|
// Singleton instance
|