@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.
Files changed (136) 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 +127 -0
  27. package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-15.md +142 -0
  28. package/DOCS/dev-tracks/README.md +10 -0
  29. package/README.md +26 -13
  30. package/cpp/CMakeLists.txt +236 -0
  31. package/cpp/bindings/embind.cpp +269 -0
  32. package/cpp/build.sh +129 -0
  33. package/cpp/geometry/Crystal.cpp +103 -0
  34. package/cpp/geometry/Fractal.cpp +136 -0
  35. package/cpp/geometry/GeometryGenerator.cpp +262 -0
  36. package/cpp/geometry/KleinBottle.cpp +71 -0
  37. package/cpp/geometry/Sphere.cpp +134 -0
  38. package/cpp/geometry/Tesseract.cpp +94 -0
  39. package/cpp/geometry/Tetrahedron.cpp +83 -0
  40. package/cpp/geometry/Torus.cpp +65 -0
  41. package/cpp/geometry/WarpFunctions.cpp +238 -0
  42. package/cpp/geometry/Wave.cpp +85 -0
  43. package/cpp/include/vib3_ffi.h +238 -0
  44. package/cpp/math/Mat4x4.cpp +409 -0
  45. package/cpp/math/Mat4x4.hpp +209 -0
  46. package/cpp/math/Projection.cpp +142 -0
  47. package/cpp/math/Projection.hpp +148 -0
  48. package/cpp/math/Rotor4D.cpp +322 -0
  49. package/cpp/math/Rotor4D.hpp +204 -0
  50. package/cpp/math/Vec4.cpp +303 -0
  51. package/cpp/math/Vec4.hpp +225 -0
  52. package/cpp/src/vib3_ffi.cpp +607 -0
  53. package/cpp/tests/Geometry_test.cpp +213 -0
  54. package/cpp/tests/Mat4x4_test.cpp +494 -0
  55. package/cpp/tests/Projection_test.cpp +298 -0
  56. package/cpp/tests/Rotor4D_test.cpp +423 -0
  57. package/cpp/tests/Vec4_test.cpp +489 -0
  58. package/package.json +40 -27
  59. package/src/agent/index.js +1 -3
  60. package/src/agent/mcp/MCPServer.js +918 -0
  61. package/src/agent/mcp/index.js +1 -1
  62. package/src/agent/mcp/stdio-server.js +264 -0
  63. package/src/agent/mcp/tools.js +454 -0
  64. package/src/cli/index.js +374 -44
  65. package/src/core/CanvasManager.js +97 -204
  66. package/src/core/ErrorReporter.js +1 -1
  67. package/src/core/Parameters.js +1 -1
  68. package/src/core/VIB3Engine.js +93 -4
  69. package/src/core/VitalitySystem.js +53 -0
  70. package/src/core/index.js +18 -0
  71. package/src/core/renderers/FacetedRendererAdapter.js +10 -9
  72. package/src/core/renderers/HolographicRendererAdapter.js +13 -9
  73. package/src/core/renderers/QuantumRendererAdapter.js +11 -7
  74. package/src/creative/AestheticMapper.js +628 -0
  75. package/src/creative/ChoreographyPlayer.js +481 -0
  76. package/src/creative/index.js +11 -0
  77. package/src/export/TradingCardManager.js +3 -4
  78. package/src/export/index.js +11 -1
  79. package/src/faceted/FacetedSystem.js +241 -388
  80. package/src/holograms/HolographicVisualizer.js +29 -12
  81. package/src/holograms/RealHolographicSystem.js +194 -43
  82. package/src/math/index.js +7 -7
  83. package/src/polychora/PolychoraSystem.js +77 -0
  84. package/src/quantum/QuantumEngine.js +103 -66
  85. package/src/quantum/QuantumVisualizer.js +7 -2
  86. package/src/reactivity/index.js +3 -5
  87. package/src/render/LayerPresetManager.js +372 -0
  88. package/src/render/LayerReactivityBridge.js +344 -0
  89. package/src/render/LayerRelationshipGraph.js +610 -0
  90. package/src/render/MultiCanvasBridge.js +148 -25
  91. package/src/render/UnifiedRenderBridge.js +3 -0
  92. package/src/render/index.js +27 -2
  93. package/src/scene/index.js +4 -4
  94. package/src/shaders/faceted/faceted.frag.glsl +220 -80
  95. package/src/shaders/faceted/faceted.frag.wgsl +138 -97
  96. package/src/shaders/holographic/holographic.frag.glsl +28 -9
  97. package/src/shaders/holographic/holographic.frag.wgsl +107 -38
  98. package/src/shaders/quantum/quantum.frag.glsl +1 -0
  99. package/src/shaders/quantum/quantum.frag.wgsl +1 -1
  100. package/src/testing/ParallelTestFramework.js +2 -2
  101. package/src/viewer/GalleryUI.js +17 -0
  102. package/src/viewer/ViewerPortal.js +2 -2
  103. package/src/viewer/index.js +1 -1
  104. package/tools/headless-renderer.js +258 -0
  105. package/tools/shader-sync-verify.js +8 -4
  106. package/tools/site-analysis/all-reports.json +32 -0
  107. package/tools/site-analysis/combined-analysis.md +50 -0
  108. package/tools/site-analyzer.mjs +779 -0
  109. package/tools/visual-catalog/capture.js +276 -0
  110. package/tools/visual-catalog/composite.js +138 -0
  111. package/types/adaptive-sdk.d.ts +204 -5
  112. package/types/agent/cli.d.ts +78 -0
  113. package/types/agent/index.d.ts +18 -0
  114. package/types/agent/mcp.d.ts +87 -0
  115. package/types/agent/telemetry.d.ts +190 -0
  116. package/types/core/VIB3Engine.d.ts +26 -0
  117. package/types/core/index.d.ts +261 -0
  118. package/types/creative/AestheticMapper.d.ts +72 -0
  119. package/types/creative/ChoreographyPlayer.d.ts +96 -0
  120. package/types/creative/index.d.ts +17 -0
  121. package/types/export/index.d.ts +243 -0
  122. package/types/geometry/index.d.ts +164 -0
  123. package/types/math/index.d.ts +214 -0
  124. package/types/render/LayerPresetManager.d.ts +78 -0
  125. package/types/render/LayerReactivityBridge.d.ts +85 -0
  126. package/types/render/LayerRelationshipGraph.d.ts +174 -0
  127. package/types/render/index.d.ts +3 -0
  128. package/types/scene/index.d.ts +204 -0
  129. package/types/systems/index.d.ts +244 -0
  130. package/types/variations/index.d.ts +62 -0
  131. package/types/viewer/index.d.ts +225 -0
  132. /package/DOCS/{DEV_TRACK_PLAN_2026-01-07.md → archive/DEV_TRACK_PLAN_2026-01-07.md} +0 -0
  133. /package/DOCS/{SESSION_014_PLAN.md → archive/SESSION_014_PLAN.md} +0 -0
  134. /package/DOCS/{SESSION_LOG_2026-01-07.md → archive/SESSION_LOG_2026-01-07.md} +0 -0
  135. /package/DOCS/{STRATEGIC_BLUEPRINT_2026-01-07.md → archive/STRATEGIC_BLUEPRINT_2026-01-07.md} +0 -0
  136. /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