@vib3code/sdk 2.0.1 → 2.0.3-canary.91a95f3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/DOCS/AGENT_HARNESS_ARCHITECTURE.md +243 -0
  3. package/DOCS/CLI_ONBOARDING.md +1 -1
  4. package/DOCS/CROSS_SITE_DESIGN_PATTERNS.md +117 -0
  5. package/DOCS/EPIC_SCROLL_EVENTS.md +773 -0
  6. package/DOCS/HANDOFF_LANDING_PAGE.md +154 -0
  7. package/DOCS/HANDOFF_SDK_DEVELOPMENT.md +493 -0
  8. package/DOCS/MULTIVIZ_CHOREOGRAPHY_PATTERNS.md +937 -0
  9. package/DOCS/PRODUCT_STRATEGY.md +63 -0
  10. package/DOCS/README.md +103 -0
  11. package/DOCS/REFERENCE_SCROLL_ANALYSIS.md +97 -0
  12. package/DOCS/ROADMAP.md +111 -0
  13. package/DOCS/SCROLL_TIMELINE_v3.md +269 -0
  14. package/DOCS/SITE_REFACTOR_PLAN.md +100 -0
  15. package/DOCS/STATUS.md +24 -0
  16. package/DOCS/SYSTEM_INVENTORY.md +33 -30
  17. package/DOCS/VISUAL_ANALYSIS_CLICKERSS.md +85 -0
  18. package/DOCS/VISUAL_ANALYSIS_FACETAD.md +133 -0
  19. package/DOCS/VISUAL_ANALYSIS_SIMONE.md +95 -0
  20. package/DOCS/VISUAL_ANALYSIS_TABLESIDE.md +86 -0
  21. package/DOCS/{BLUEPRINT_EXECUTION_PLAN_2026-01-07.md → archive/BLUEPRINT_EXECUTION_PLAN_2026-01-07.md} +1 -1
  22. package/DOCS/{DEV_TRACK_ANALYSIS.md → archive/DEV_TRACK_ANALYSIS.md} +3 -0
  23. package/DOCS/{SYSTEM_AUDIT_2026-01-30.md → archive/SYSTEM_AUDIT_2026-01-30.md} +3 -0
  24. package/DOCS/{DEV_TRACK_SESSION_2026-01-31.md → dev-tracks/DEV_TRACK_SESSION_2026-01-31.md} +1 -1
  25. package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-06.md +231 -0
  26. package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-13.md +114 -0
  27. package/DOCS/dev-tracks/README.md +10 -0
  28. package/README.md +26 -13
  29. package/cpp/CMakeLists.txt +236 -0
  30. package/cpp/bindings/embind.cpp +269 -0
  31. package/cpp/build.sh +129 -0
  32. package/cpp/geometry/Crystal.cpp +103 -0
  33. package/cpp/geometry/Fractal.cpp +136 -0
  34. package/cpp/geometry/GeometryGenerator.cpp +262 -0
  35. package/cpp/geometry/KleinBottle.cpp +71 -0
  36. package/cpp/geometry/Sphere.cpp +134 -0
  37. package/cpp/geometry/Tesseract.cpp +94 -0
  38. package/cpp/geometry/Tetrahedron.cpp +83 -0
  39. package/cpp/geometry/Torus.cpp +65 -0
  40. package/cpp/geometry/WarpFunctions.cpp +238 -0
  41. package/cpp/geometry/Wave.cpp +85 -0
  42. package/cpp/include/vib3_ffi.h +238 -0
  43. package/cpp/math/Mat4x4.cpp +409 -0
  44. package/cpp/math/Mat4x4.hpp +209 -0
  45. package/cpp/math/Projection.cpp +142 -0
  46. package/cpp/math/Projection.hpp +148 -0
  47. package/cpp/math/Rotor4D.cpp +322 -0
  48. package/cpp/math/Rotor4D.hpp +204 -0
  49. package/cpp/math/Vec4.cpp +303 -0
  50. package/cpp/math/Vec4.hpp +225 -0
  51. package/cpp/src/vib3_ffi.cpp +607 -0
  52. package/cpp/tests/Geometry_test.cpp +213 -0
  53. package/cpp/tests/Mat4x4_test.cpp +494 -0
  54. package/cpp/tests/Projection_test.cpp +298 -0
  55. package/cpp/tests/Rotor4D_test.cpp +423 -0
  56. package/cpp/tests/Vec4_test.cpp +489 -0
  57. package/package.json +31 -27
  58. package/src/agent/mcp/MCPServer.js +722 -0
  59. package/src/agent/mcp/stdio-server.js +264 -0
  60. package/src/agent/mcp/tools.js +367 -0
  61. package/src/cli/index.js +0 -0
  62. package/src/core/CanvasManager.js +97 -204
  63. package/src/core/ErrorReporter.js +1 -1
  64. package/src/core/Parameters.js +1 -1
  65. package/src/core/VIB3Engine.js +38 -1
  66. package/src/core/VitalitySystem.js +53 -0
  67. package/src/core/renderers/HolographicRendererAdapter.js +2 -2
  68. package/src/creative/AestheticMapper.js +628 -0
  69. package/src/creative/ChoreographyPlayer.js +481 -0
  70. package/src/export/TradingCardManager.js +3 -4
  71. package/src/faceted/FacetedSystem.js +237 -388
  72. package/src/holograms/HolographicVisualizer.js +29 -12
  73. package/src/holograms/RealHolographicSystem.js +68 -12
  74. package/src/polychora/PolychoraSystem.js +77 -0
  75. package/src/quantum/QuantumEngine.js +103 -66
  76. package/src/quantum/QuantumVisualizer.js +7 -2
  77. package/src/render/UnifiedRenderBridge.js +3 -0
  78. package/src/shaders/faceted/faceted.frag.glsl +220 -80
  79. package/src/shaders/faceted/faceted.frag.wgsl +138 -97
  80. package/src/shaders/holographic/holographic.frag.glsl +28 -9
  81. package/src/shaders/holographic/holographic.frag.wgsl +107 -38
  82. package/src/shaders/quantum/quantum.frag.glsl +1 -0
  83. package/src/shaders/quantum/quantum.frag.wgsl +1 -1
  84. package/src/viewer/index.js +1 -1
  85. package/tools/headless-renderer.js +258 -0
  86. package/tools/shader-sync-verify.js +8 -4
  87. package/tools/site-analysis/all-reports.json +32 -0
  88. package/tools/site-analysis/combined-analysis.md +50 -0
  89. package/tools/site-analyzer.mjs +779 -0
  90. package/tools/visual-catalog/capture.js +276 -0
  91. package/tools/visual-catalog/composite.js +138 -0
  92. /package/DOCS/{DEV_TRACK_PLAN_2026-01-07.md → archive/DEV_TRACK_PLAN_2026-01-07.md} +0 -0
  93. /package/DOCS/{SESSION_014_PLAN.md → archive/SESSION_014_PLAN.md} +0 -0
  94. /package/DOCS/{SESSION_LOG_2026-01-07.md → archive/SESSION_LOG_2026-01-07.md} +0 -0
  95. /package/DOCS/{STRATEGIC_BLUEPRINT_2026-01-07.md → archive/STRATEGIC_BLUEPRINT_2026-01-07.md} +0 -0
  96. /package/src/viewer/{ReactivityManager.js → ViewerInputHandler.js} +0 -0
@@ -6,6 +6,9 @@
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';
9
12
 
10
13
  /**
11
14
  * Generate unique IDs
@@ -163,6 +166,44 @@ export class MCPServer {
163
166
  case 'list_behavior_presets':
164
167
  result = this.listBehaviorPresets();
165
168
  break;
169
+ // Agent-power tools (Phase 7)
170
+ case 'describe_visual_state':
171
+ result = this.describeVisualState();
172
+ break;
173
+ case 'batch_set_parameters':
174
+ result = await this.batchSetParameters(args);
175
+ break;
176
+ case 'create_timeline':
177
+ result = this.createTimeline(args);
178
+ break;
179
+ case 'play_transition':
180
+ result = this.playTransition(args);
181
+ break;
182
+ case 'apply_color_preset':
183
+ result = this.applyColorPreset(args);
184
+ break;
185
+ case 'set_post_processing':
186
+ result = this.setPostProcessing(args);
187
+ break;
188
+ case 'create_choreography':
189
+ result = this.createChoreography(args);
190
+ break;
191
+ // Visual feedback tools (Phase 7.1)
192
+ case 'capture_screenshot':
193
+ result = await this.captureScreenshot(args);
194
+ break;
195
+ case 'design_from_description':
196
+ result = await this.designFromDescription(args);
197
+ break;
198
+ case 'get_aesthetic_vocabulary':
199
+ result = this.getAestheticVocabulary();
200
+ break;
201
+ case 'play_choreography':
202
+ result = await this.playChoreographyTool(args);
203
+ break;
204
+ case 'control_timeline':
205
+ result = this.controlTimeline(args);
206
+ break;
166
207
  default:
167
208
  throw new Error(`Unknown tool: ${toolName}`);
168
209
  }
@@ -943,6 +984,687 @@ export class MCPServer {
943
984
  suggested_next_actions: ['apply_behavior_preset']
944
985
  };
945
986
  }
987
+
988
+ // ===== AGENT-POWER TOOLS (Phase 7 — Agent Harness) =====
989
+
990
+ /**
991
+ * Generate a natural-language description of the current visual state.
992
+ * Enables text-only agents to "see" what the visualization looks like.
993
+ */
994
+ describeVisualState() {
995
+ const state = this.getState();
996
+ const params = state.visual || {};
997
+ const rotation = state.rotation_state || {};
998
+ const geometry = state.geometry || {};
999
+
1000
+ // Color description from hue
1001
+ const hue = params.hue || 0;
1002
+ const colorName = hue < 15 ? 'red' : hue < 45 ? 'orange' : hue < 75 ? 'yellow' :
1003
+ hue < 150 ? 'green' : hue < 195 ? 'cyan' : hue < 255 ? 'blue' :
1004
+ hue < 285 ? 'purple' : hue < 330 ? 'magenta' : 'red';
1005
+ const satDesc = (params.saturation || 0.8) > 0.7 ? 'vivid' :
1006
+ (params.saturation || 0.8) > 0.4 ? 'moderate' : 'desaturated';
1007
+ const intensityDesc = (params.intensity || 0.5) > 0.7 ? 'bright' :
1008
+ (params.intensity || 0.5) > 0.3 ? 'medium brightness' : 'dim';
1009
+
1010
+ // Motion description
1011
+ const speed = params.speed || 1.0;
1012
+ const speedDesc = speed > 2.0 ? 'rapidly' : speed > 1.0 ? 'moderately' :
1013
+ speed > 0.4 ? 'slowly' : 'very slowly';
1014
+ const chaos = params.chaos || 0;
1015
+ const chaosDesc = chaos > 0.7 ? 'highly turbulent' : chaos > 0.3 ? 'somewhat organic' :
1016
+ chaos > 0.05 ? 'subtly alive' : 'perfectly still';
1017
+
1018
+ // 4D rotation activity
1019
+ const has4D = Math.abs(rotation.XW || 0) > 0.1 ||
1020
+ Math.abs(rotation.YW || 0) > 0.1 ||
1021
+ Math.abs(rotation.ZW || 0) > 0.1;
1022
+ const rotDesc = has4D ? 'with visible 4D hyperspace rotation (inside-out morphing)' :
1023
+ 'in standard 3D orientation';
1024
+
1025
+ // Density/complexity
1026
+ const density = params.gridDensity || 10;
1027
+ const densityDesc = density > 50 ? 'extremely intricate' : density > 25 ? 'detailed' :
1028
+ density > 12 ? 'moderate detail' : 'bold and sparse';
1029
+
1030
+ // Projection
1031
+ const dim = params.dimension || 3.8;
1032
+ const projDesc = dim < 3.3 ? 'dramatic fish-eye distortion' :
1033
+ dim < 3.8 ? 'moderate perspective depth' : 'subtle, flattened perspective';
1034
+
1035
+ const description = [
1036
+ `A ${satDesc} ${colorName} ${geometry.core_type || 'base'} ${geometry.base_type || 'tetrahedron'}`,
1037
+ `rendered in the ${state.system || 'quantum'} system.`,
1038
+ `The pattern is ${densityDesc} and ${chaosDesc},`,
1039
+ `animating ${speedDesc} ${rotDesc}.`,
1040
+ `Color is ${intensityDesc} with ${projDesc}.`,
1041
+ params.morphFactor > 0.5 ? `Shape is morphing between geometries (factor: ${params.morphFactor}).` : ''
1042
+ ].filter(Boolean).join(' ');
1043
+
1044
+ return {
1045
+ description,
1046
+ mood: this._assessMood(params),
1047
+ complexity: density > 40 ? 'high' : density > 15 ? 'medium' : 'low',
1048
+ motion_level: speed > 1.5 ? 'high' : speed > 0.5 ? 'medium' : 'low',
1049
+ has_4d_rotation: has4D,
1050
+ color_family: colorName,
1051
+ suggested_next_actions: ['set_visual_parameters', 'set_rotation', 'batch_set_parameters']
1052
+ };
1053
+ }
1054
+
1055
+ /**
1056
+ * Assess the emotional mood of the current visual state
1057
+ */
1058
+ _assessMood(params) {
1059
+ const hue = params.hue || 0;
1060
+ const speed = params.speed || 1.0;
1061
+ const chaos = params.chaos || 0;
1062
+ const intensity = params.intensity || 0.5;
1063
+
1064
+ if (speed < 0.3 && chaos < 0.1) return 'serene';
1065
+ if (speed > 2.0 && chaos > 0.6) return 'chaotic';
1066
+ if (hue > 180 && hue < 260 && intensity < 0.5) return 'mysterious';
1067
+ if (hue > 0 && hue < 60 && intensity > 0.6) return 'warm';
1068
+ if (hue > 150 && hue < 200 && speed < 0.8) return 'tranquil';
1069
+ if (chaos > 0.5 && speed > 1.5) return 'energetic';
1070
+ if (intensity > 0.8) return 'vibrant';
1071
+ return 'balanced';
1072
+ }
1073
+
1074
+ /**
1075
+ * Atomically set multiple parameter categories in one call
1076
+ */
1077
+ async batchSetParameters(args) {
1078
+ const { system, geometry, rotation, visual, preset } = args;
1079
+
1080
+ // Switch system first if requested
1081
+ if (system && this.engine) {
1082
+ await this.engine.switchSystem(system);
1083
+ }
1084
+
1085
+ // Set geometry
1086
+ if (geometry !== undefined && this.engine) {
1087
+ this.engine.setParameter('geometry', geometry);
1088
+ }
1089
+
1090
+ // Set rotation
1091
+ if (rotation) {
1092
+ const rotMap = { XY: 'rot4dXY', XZ: 'rot4dXZ', YZ: 'rot4dYZ',
1093
+ XW: 'rot4dXW', YW: 'rot4dYW', ZW: 'rot4dZW' };
1094
+ for (const [key, value] of Object.entries(rotation)) {
1095
+ if (value !== undefined && this.engine) {
1096
+ this.engine.setParameter(rotMap[key], value);
1097
+ }
1098
+ }
1099
+ }
1100
+
1101
+ // Set visual parameters
1102
+ if (visual && this.engine) {
1103
+ for (const [key, value] of Object.entries(visual)) {
1104
+ this.engine.setParameter(key, value);
1105
+ }
1106
+ }
1107
+
1108
+ // Apply preset last (overrides relevant params)
1109
+ if (preset) {
1110
+ this.applyBehaviorPreset({ preset });
1111
+ }
1112
+
1113
+ telemetry.recordEvent(EventType.PARAMETER_BATCH_CHANGE, {
1114
+ count: (rotation ? Object.keys(rotation).length : 0) +
1115
+ (visual ? Object.keys(visual).length : 0) +
1116
+ (system ? 1 : 0) + (geometry !== undefined ? 1 : 0)
1117
+ });
1118
+
1119
+ return {
1120
+ ...this.getState(),
1121
+ batch_applied: true,
1122
+ suggested_next_actions: ['describe_visual_state', 'save_to_gallery', 'create_timeline']
1123
+ };
1124
+ }
1125
+
1126
+ /**
1127
+ * Create a ParameterTimeline from agent specification
1128
+ */
1129
+ createTimeline(args) {
1130
+ const { name, duration_ms, bpm, loop_mode = 'once', tracks } = args;
1131
+
1132
+ const timelineId = generateId('timeline');
1133
+
1134
+ // Validate tracks have properly sorted keyframes
1135
+ const validatedTracks = {};
1136
+ for (const [param, keyframes] of Object.entries(tracks)) {
1137
+ validatedTracks[param] = keyframes
1138
+ .map(kf => ({
1139
+ time: Math.max(0, Math.min(kf.time, duration_ms)),
1140
+ value: kf.value,
1141
+ easing: kf.easing || 'easeInOut'
1142
+ }))
1143
+ .sort((a, b) => a.time - b.time);
1144
+ }
1145
+
1146
+ // Build timeline data for ParameterTimeline consumption
1147
+ const timelineData = {
1148
+ id: timelineId,
1149
+ name: name || `Timeline ${timelineId}`,
1150
+ duration: duration_ms,
1151
+ bpm: bpm || null,
1152
+ loopMode: loop_mode,
1153
+ tracks: validatedTracks
1154
+ };
1155
+
1156
+ // If engine is available, create and start the timeline
1157
+ if (this.engine) {
1158
+ // Store for later retrieval
1159
+ if (!this._timelines) this._timelines = new Map();
1160
+ this._timelines.set(timelineId, timelineData);
1161
+ }
1162
+
1163
+ return {
1164
+ timeline_id: timelineId,
1165
+ name: timelineData.name,
1166
+ duration_ms,
1167
+ bpm: bpm || null,
1168
+ loop_mode,
1169
+ track_count: Object.keys(validatedTracks).length,
1170
+ tracks_summary: Object.entries(validatedTracks).map(([param, kfs]) => ({
1171
+ parameter: param,
1172
+ keyframe_count: kfs.length,
1173
+ value_range: [
1174
+ Math.min(...kfs.map(k => k.value)),
1175
+ Math.max(...kfs.map(k => k.value))
1176
+ ]
1177
+ })),
1178
+ load_code: `const tl = new ParameterTimeline((n, v) => engine.setParameter(n, v));\ntl.importTimeline(${JSON.stringify(timelineData)});\ntl.play();`,
1179
+ suggested_next_actions: ['play_transition', 'describe_visual_state', 'save_to_gallery']
1180
+ };
1181
+ }
1182
+
1183
+ /**
1184
+ * Play a smooth transition sequence
1185
+ */
1186
+ playTransition(args) {
1187
+ const { sequence } = args;
1188
+
1189
+ const transitionId = generateId('transition');
1190
+
1191
+ // Validate and normalize the sequence
1192
+ const normalizedSequence = sequence.map((step, i) => ({
1193
+ params: step.params,
1194
+ duration: step.duration || 1000,
1195
+ easing: step.easing || 'easeInOut',
1196
+ delay: step.delay || 0
1197
+ }));
1198
+
1199
+ const totalDuration = normalizedSequence.reduce(
1200
+ (sum, step) => sum + step.duration + step.delay, 0
1201
+ );
1202
+
1203
+ return {
1204
+ transition_id: transitionId,
1205
+ step_count: normalizedSequence.length,
1206
+ total_duration_ms: totalDuration,
1207
+ steps: normalizedSequence.map((step, i) => ({
1208
+ index: i,
1209
+ params: Object.keys(step.params),
1210
+ duration: step.duration,
1211
+ easing: step.easing,
1212
+ delay: step.delay
1213
+ })),
1214
+ load_code: `const animator = new TransitionAnimator(\n (n, v) => engine.setParameter(n, v),\n (n) => engine.getParameter(n)\n);\nanimator.sequence(${JSON.stringify(normalizedSequence)});`,
1215
+ suggested_next_actions: ['describe_visual_state', 'create_timeline', 'save_to_gallery']
1216
+ };
1217
+ }
1218
+
1219
+ /**
1220
+ * Apply a named color preset
1221
+ */
1222
+ applyColorPreset(args) {
1223
+ const { preset } = args;
1224
+
1225
+ // Color preset hue/saturation mappings (subset — full list in ColorPresetsSystem)
1226
+ const COLOR_PRESETS = {
1227
+ Ocean: { hue: 200, saturation: 0.8, intensity: 0.6 },
1228
+ Lava: { hue: 15, saturation: 0.9, intensity: 0.8 },
1229
+ Neon: { hue: 300, saturation: 1.0, intensity: 0.9 },
1230
+ Monochrome: { hue: 0, saturation: 0.0, intensity: 0.6 },
1231
+ Sunset: { hue: 30, saturation: 0.85, intensity: 0.7 },
1232
+ Aurora: { hue: 140, saturation: 0.7, intensity: 0.6 },
1233
+ Cyberpunk: { hue: 280, saturation: 0.9, intensity: 0.8 },
1234
+ Forest: { hue: 120, saturation: 0.6, intensity: 0.5 },
1235
+ Desert: { hue: 40, saturation: 0.5, intensity: 0.7 },
1236
+ Galaxy: { hue: 260, saturation: 0.8, intensity: 0.4 },
1237
+ Ice: { hue: 190, saturation: 0.5, intensity: 0.8 },
1238
+ Fire: { hue: 10, saturation: 1.0, intensity: 0.9 },
1239
+ Toxic: { hue: 100, saturation: 0.9, intensity: 0.7 },
1240
+ Royal: { hue: 270, saturation: 0.7, intensity: 0.5 },
1241
+ Pastel: { hue: 330, saturation: 0.3, intensity: 0.8 },
1242
+ Retro: { hue: 50, saturation: 0.7, intensity: 0.6 },
1243
+ Midnight: { hue: 240, saturation: 0.6, intensity: 0.3 },
1244
+ Tropical: { hue: 160, saturation: 0.8, intensity: 0.7 },
1245
+ Ethereal: { hue: 220, saturation: 0.4, intensity: 0.7 },
1246
+ Volcanic: { hue: 5, saturation: 0.95, intensity: 0.6 },
1247
+ Holographic: { hue: 180, saturation: 0.6, intensity: 0.8 },
1248
+ Vaporwave: { hue: 310, saturation: 0.7, intensity: 0.7 }
1249
+ };
1250
+
1251
+ const presetData = COLOR_PRESETS[preset];
1252
+ if (!presetData) {
1253
+ return {
1254
+ error: {
1255
+ type: 'ValidationError',
1256
+ code: 'INVALID_COLOR_PRESET',
1257
+ message: `Unknown color preset: ${preset}`,
1258
+ valid_options: Object.keys(COLOR_PRESETS)
1259
+ }
1260
+ };
1261
+ }
1262
+
1263
+ if (this.engine) {
1264
+ this.engine.setParameter('hue', presetData.hue);
1265
+ this.engine.setParameter('saturation', presetData.saturation);
1266
+ this.engine.setParameter('intensity', presetData.intensity);
1267
+ }
1268
+
1269
+ return {
1270
+ preset,
1271
+ applied: presetData,
1272
+ suggested_next_actions: ['set_post_processing', 'describe_visual_state', 'set_visual_parameters']
1273
+ };
1274
+ }
1275
+
1276
+ /**
1277
+ * Configure post-processing effects pipeline
1278
+ */
1279
+ setPostProcessing(args) {
1280
+ const { effects, chain_preset, clear_first = true } = args;
1281
+
1282
+ return {
1283
+ applied: true,
1284
+ effects: effects || [],
1285
+ chain_preset: chain_preset || null,
1286
+ cleared_previous: clear_first,
1287
+ load_code: effects ?
1288
+ `const pipeline = new PostProcessingPipeline(gl, canvas);\n${effects.map(e => `pipeline.addEffect('${e.name}', { intensity: ${e.intensity || 0.5} });`).join('\n')}` :
1289
+ `pipeline.applyChain('${chain_preset}');`,
1290
+ suggested_next_actions: ['describe_visual_state', 'apply_color_preset', 'create_choreography']
1291
+ };
1292
+ }
1293
+
1294
+ /**
1295
+ * Create a multi-scene choreography — the most powerful agent composition tool
1296
+ */
1297
+ createChoreography(args) {
1298
+ const { name, duration_ms, bpm, scenes } = args;
1299
+
1300
+ const choreographyId = generateId('choreo');
1301
+
1302
+ // Validate scene time ranges don't exceed duration
1303
+ const validatedScenes = scenes.map((scene, i) => ({
1304
+ index: i,
1305
+ time_start: Math.max(0, scene.time_start),
1306
+ time_end: Math.min(scene.time_end, duration_ms),
1307
+ system: scene.system,
1308
+ geometry: scene.geometry ?? 0,
1309
+ transition_in: scene.transition_in || { type: 'cut', duration: 0 },
1310
+ tracks: scene.tracks || {},
1311
+ color_preset: scene.color_preset || null,
1312
+ post_processing: scene.post_processing || [],
1313
+ audio: scene.audio || null
1314
+ }));
1315
+
1316
+ const choreography = {
1317
+ id: choreographyId,
1318
+ name: name || `Choreography ${choreographyId}`,
1319
+ duration_ms,
1320
+ bpm: bpm || null,
1321
+ scene_count: validatedScenes.length,
1322
+ scenes: validatedScenes
1323
+ };
1324
+
1325
+ // Store for later retrieval
1326
+ if (!this._choreographies) this._choreographies = new Map();
1327
+ this._choreographies.set(choreographyId, choreography);
1328
+
1329
+ return {
1330
+ choreography_id: choreographyId,
1331
+ name: choreography.name,
1332
+ duration_ms,
1333
+ bpm: bpm || null,
1334
+ scene_count: validatedScenes.length,
1335
+ scenes_summary: validatedScenes.map(s => ({
1336
+ index: s.index,
1337
+ time: `${s.time_start}ms → ${s.time_end}ms`,
1338
+ system: s.system,
1339
+ geometry: s.geometry,
1340
+ transition: s.transition_in.type,
1341
+ track_count: Object.keys(s.tracks).length,
1342
+ color_preset: s.color_preset,
1343
+ effects: s.post_processing
1344
+ })),
1345
+ choreography_json: JSON.stringify(choreography, null, 2),
1346
+ suggested_next_actions: ['describe_visual_state', 'export_package']
1347
+ };
1348
+ }
1349
+
1350
+ // ===== VISUAL FEEDBACK TOOLS (Phase 7.1 — Agent Harness) =====
1351
+
1352
+ /**
1353
+ * Capture the current visualization as a base64 PNG by compositing all canvas layers.
1354
+ * Only works in browser context where canvases exist.
1355
+ */
1356
+ async captureScreenshot(args) {
1357
+ const { width = 512, height = 512, format = 'png', quality = 0.92 } = args;
1358
+
1359
+ const isBrowser = typeof document !== 'undefined';
1360
+ if (!isBrowser) {
1361
+ return {
1362
+ error: {
1363
+ type: 'EnvironmentError',
1364
+ code: 'NO_BROWSER_CONTEXT',
1365
+ message: 'capture_screenshot requires a browser context with canvas elements',
1366
+ suggestion: 'Use the headless renderer tool (tools/headless-renderer.js) for Node.js environments'
1367
+ }
1368
+ };
1369
+ }
1370
+
1371
+ try {
1372
+ // Create composite canvas
1373
+ const composite = document.createElement('canvas');
1374
+ composite.width = width;
1375
+ composite.height = height;
1376
+ const ctx = composite.getContext('2d');
1377
+
1378
+ if (!ctx) {
1379
+ return {
1380
+ error: { type: 'SystemError', code: 'CANVAS_CONTEXT_FAILED', message: 'Could not get 2D context for compositing' }
1381
+ };
1382
+ }
1383
+
1384
+ // Fill with black background
1385
+ ctx.fillStyle = '#000000';
1386
+ ctx.fillRect(0, 0, width, height);
1387
+
1388
+ // Find all visualization canvases and composite them in z-order
1389
+ const canvases = document.querySelectorAll('canvas.visualization-canvas');
1390
+ const sortedCanvases = Array.from(canvases).sort((a, b) => {
1391
+ const zA = parseInt(a.style.zIndex || '0', 10);
1392
+ const zB = parseInt(b.style.zIndex || '0', 10);
1393
+ return zA - zB;
1394
+ });
1395
+
1396
+ for (const canvas of sortedCanvases) {
1397
+ if (canvas.width > 0 && canvas.height > 0) {
1398
+ ctx.drawImage(canvas, 0, 0, width, height);
1399
+ }
1400
+ }
1401
+
1402
+ // If no canvases found, try to find any canvas at all
1403
+ if (sortedCanvases.length === 0) {
1404
+ const anyCanvas = document.querySelector('canvas');
1405
+ if (anyCanvas && anyCanvas.width > 0) {
1406
+ ctx.drawImage(anyCanvas, 0, 0, width, height);
1407
+ }
1408
+ }
1409
+
1410
+ // Convert to data URL
1411
+ const mimeType = format === 'jpeg' ? 'image/jpeg' : format === 'webp' ? 'image/webp' : 'image/png';
1412
+ const dataUrl = composite.toDataURL(mimeType, quality);
1413
+ const base64 = dataUrl.split(',')[1];
1414
+
1415
+ // Clean up
1416
+ composite.remove();
1417
+
1418
+ return {
1419
+ format,
1420
+ width,
1421
+ height,
1422
+ mime_type: mimeType,
1423
+ data_url: dataUrl,
1424
+ base64_length: base64.length,
1425
+ canvas_count: sortedCanvases.length,
1426
+ suggested_next_actions: ['describe_visual_state', 'set_visual_parameters', 'batch_set_parameters']
1427
+ };
1428
+ } catch (err) {
1429
+ return {
1430
+ error: {
1431
+ type: 'SystemError',
1432
+ code: 'SCREENSHOT_FAILED',
1433
+ message: err.message,
1434
+ suggestion: 'Check that canvas elements exist and are rendered'
1435
+ }
1436
+ };
1437
+ }
1438
+ }
1439
+
1440
+ /**
1441
+ * Map a natural-language description to VIB3+ parameters using AestheticMapper.
1442
+ */
1443
+ async designFromDescription(args) {
1444
+ const { description, apply = false } = args;
1445
+
1446
+ if (!this._aestheticMapper) {
1447
+ this._aestheticMapper = new AestheticMapper();
1448
+ }
1449
+
1450
+ const mapped = this._aestheticMapper.mapDescription(description);
1451
+ const resolved = this._aestheticMapper.resolveToValues(description);
1452
+
1453
+ // Apply to engine if requested
1454
+ if (apply && this.engine) {
1455
+ if (resolved.system) {
1456
+ await this.engine.switchSystem(resolved.system);
1457
+ }
1458
+ if (resolved.geometry !== undefined) {
1459
+ this.engine.setParameter('geometry', resolved.geometry);
1460
+ }
1461
+ for (const [param, value] of Object.entries(resolved.params)) {
1462
+ this.engine.setParameter(param, value);
1463
+ }
1464
+ }
1465
+
1466
+ return {
1467
+ description,
1468
+ applied: apply,
1469
+ matched_words: mapped.matched_words,
1470
+ total_words: mapped.total_words,
1471
+ resolved: {
1472
+ system: resolved.system,
1473
+ geometry: resolved.geometry,
1474
+ params: resolved.params,
1475
+ color_preset: resolved.color_preset,
1476
+ post_processing: resolved.post_processing
1477
+ },
1478
+ parameter_ranges: mapped.params,
1479
+ suggested_next_actions: apply
1480
+ ? ['describe_visual_state', 'capture_screenshot', 'create_timeline']
1481
+ : ['design_from_description', 'batch_set_parameters']
1482
+ };
1483
+ }
1484
+
1485
+ /**
1486
+ * Return the full aesthetic vocabulary by category.
1487
+ */
1488
+ getAestheticVocabulary() {
1489
+ if (!this._aestheticMapper) {
1490
+ this._aestheticMapper = new AestheticMapper();
1491
+ }
1492
+
1493
+ return {
1494
+ vocabulary: this._aestheticMapper.getVocabularyByCategory(),
1495
+ all_words: this._aestheticMapper.getVocabulary(),
1496
+ word_count: this._aestheticMapper.getVocabulary().length,
1497
+ usage: 'Pass space-separated words to design_from_description. Example: "serene ocean deep minimal"',
1498
+ suggested_next_actions: ['design_from_description']
1499
+ };
1500
+ }
1501
+
1502
+ /**
1503
+ * Load and play a choreography.
1504
+ */
1505
+ async playChoreographyTool(args) {
1506
+ const { choreography, choreography_id, action = 'play', seek_percent, loop = false } = args;
1507
+
1508
+ // Resolve choreography spec
1509
+ let spec = choreography;
1510
+ if (!spec && choreography_id && this._choreographies) {
1511
+ spec = this._choreographies.get(choreography_id);
1512
+ }
1513
+
1514
+ if (!spec && action === 'play') {
1515
+ return {
1516
+ error: {
1517
+ type: 'ValidationError',
1518
+ code: 'NO_CHOREOGRAPHY',
1519
+ message: 'Provide a choreography spec or a valid choreography_id',
1520
+ suggestion: 'Use create_choreography first, then pass the result here'
1521
+ }
1522
+ };
1523
+ }
1524
+
1525
+ // Create or reuse player
1526
+ if (!this._choreographyPlayer && this.engine) {
1527
+ this._choreographyPlayer = new ChoreographyPlayer(this.engine, {
1528
+ onSceneChange: (index, scene) => {
1529
+ telemetry.recordEvent(EventType.PARAMETER_CHANGE, {
1530
+ type: 'choreography_scene',
1531
+ scene_index: index,
1532
+ system: scene.system
1533
+ });
1534
+ }
1535
+ });
1536
+ }
1537
+
1538
+ if (!this._choreographyPlayer) {
1539
+ return {
1540
+ error: {
1541
+ type: 'SystemError',
1542
+ code: 'NO_ENGINE',
1543
+ message: 'Engine not initialized — cannot play choreography',
1544
+ suggestion: 'Initialize the VIB3Engine first'
1545
+ }
1546
+ };
1547
+ }
1548
+
1549
+ const player = this._choreographyPlayer;
1550
+
1551
+ switch (action) {
1552
+ case 'play':
1553
+ if (spec) {
1554
+ player.load(spec);
1555
+ player.loopMode = loop ? 'loop' : 'once';
1556
+ }
1557
+ player.play();
1558
+ break;
1559
+ case 'pause':
1560
+ player.pause();
1561
+ break;
1562
+ case 'stop':
1563
+ player.stop();
1564
+ break;
1565
+ case 'seek':
1566
+ if (seek_percent !== undefined) {
1567
+ player.seekToPercent(seek_percent);
1568
+ }
1569
+ break;
1570
+ }
1571
+
1572
+ return {
1573
+ action,
1574
+ state: player.getState(),
1575
+ suggested_next_actions: action === 'play'
1576
+ ? ['play_choreography', 'capture_screenshot', 'describe_visual_state']
1577
+ : ['play_choreography']
1578
+ };
1579
+ }
1580
+
1581
+ /**
1582
+ * Control a previously created timeline.
1583
+ */
1584
+ controlTimeline(args) {
1585
+ const { timeline_id, action, seek_percent, speed } = args;
1586
+
1587
+ if (!this._liveTimelines) this._liveTimelines = new Map();
1588
+
1589
+ let tl = this._liveTimelines.get(timeline_id);
1590
+
1591
+ // If timeline not live yet, try to create it from stored data
1592
+ if (!tl && this._timelines && this._timelines.has(timeline_id) && this.engine) {
1593
+ const data = this._timelines.get(timeline_id);
1594
+
1595
+ tl = new ParameterTimeline(
1596
+ (name, value) => this.engine.setParameter(name, value)
1597
+ );
1598
+
1599
+ // Build import-compatible format
1600
+ const importData = {
1601
+ type: 'vib3-parameter-timeline',
1602
+ version: '1.0.0',
1603
+ duration: data.duration,
1604
+ loopMode: data.loopMode || 'once',
1605
+ bpm: data.bpm || 120,
1606
+ tracks: {}
1607
+ };
1608
+
1609
+ for (const [param, keyframes] of Object.entries(data.tracks)) {
1610
+ importData.tracks[param] = {
1611
+ enabled: true,
1612
+ keyframes: keyframes.map(kf => ({
1613
+ time: kf.time,
1614
+ value: kf.value,
1615
+ easing: kf.easing || 'easeInOut'
1616
+ }))
1617
+ };
1618
+ }
1619
+
1620
+ tl.importTimeline(importData);
1621
+ this._liveTimelines.set(timeline_id, tl);
1622
+ }
1623
+
1624
+ if (!tl) {
1625
+ return {
1626
+ error: {
1627
+ type: 'ValidationError',
1628
+ code: 'TIMELINE_NOT_FOUND',
1629
+ message: `Timeline '${timeline_id}' not found`,
1630
+ suggestion: 'Create a timeline first with create_timeline'
1631
+ }
1632
+ };
1633
+ }
1634
+
1635
+ switch (action) {
1636
+ case 'play':
1637
+ tl.play();
1638
+ break;
1639
+ case 'pause':
1640
+ tl.pause();
1641
+ break;
1642
+ case 'stop':
1643
+ tl.stop();
1644
+ break;
1645
+ case 'seek':
1646
+ if (seek_percent !== undefined) {
1647
+ tl.seekToPercent(seek_percent);
1648
+ }
1649
+ break;
1650
+ case 'set_speed':
1651
+ if (speed !== undefined) {
1652
+ tl.playbackSpeed = Math.max(0.1, Math.min(10, speed));
1653
+ }
1654
+ break;
1655
+ }
1656
+
1657
+ return {
1658
+ timeline_id,
1659
+ action,
1660
+ playing: tl.playing,
1661
+ currentTime: tl.currentTime,
1662
+ duration: tl.duration,
1663
+ progress: tl.duration > 0 ? tl.currentTime / tl.duration : 0,
1664
+ playbackSpeed: tl.playbackSpeed,
1665
+ suggested_next_actions: ['control_timeline', 'describe_visual_state', 'capture_screenshot']
1666
+ };
1667
+ }
946
1668
  }
947
1669
 
948
1670
  // Singleton instance