unreal-engine-mcp-server 0.5.0 → 0.5.2

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 (188) hide show
  1. package/.env.example +1 -1
  2. package/.github/release-drafter-config.yml +51 -0
  3. package/.github/workflows/greetings.yml +5 -1
  4. package/.github/workflows/labeler.yml +2 -1
  5. package/.github/workflows/publish-mcp.yml +2 -4
  6. package/.github/workflows/release-drafter.yml +3 -2
  7. package/.github/workflows/release.yml +3 -3
  8. package/CHANGELOG.md +109 -0
  9. package/CONTRIBUTING.md +1 -1
  10. package/GEMINI.md +115 -0
  11. package/Public/Plugin_setup_guide.mp4 +0 -0
  12. package/README.md +166 -200
  13. package/dist/automation/bridge.d.ts +1 -2
  14. package/dist/automation/bridge.js +24 -23
  15. package/dist/automation/connection-manager.d.ts +1 -0
  16. package/dist/automation/connection-manager.js +10 -0
  17. package/dist/automation/message-handler.js +5 -4
  18. package/dist/automation/request-tracker.d.ts +4 -0
  19. package/dist/automation/request-tracker.js +11 -3
  20. package/dist/config.d.ts +0 -1
  21. package/dist/config.js +0 -1
  22. package/dist/constants.d.ts +4 -0
  23. package/dist/constants.js +4 -0
  24. package/dist/graphql/loaders.d.ts +64 -0
  25. package/dist/graphql/loaders.js +117 -0
  26. package/dist/graphql/resolvers.d.ts +3 -3
  27. package/dist/graphql/resolvers.js +33 -30
  28. package/dist/graphql/server.js +3 -1
  29. package/dist/graphql/types.d.ts +2 -0
  30. package/dist/index.d.ts +2 -0
  31. package/dist/index.js +13 -2
  32. package/dist/server-setup.d.ts +0 -1
  33. package/dist/server-setup.js +0 -40
  34. package/dist/tools/actors.d.ts +58 -24
  35. package/dist/tools/actors.js +22 -6
  36. package/dist/tools/assets.d.ts +19 -71
  37. package/dist/tools/assets.js +28 -22
  38. package/dist/tools/base-tool.d.ts +4 -4
  39. package/dist/tools/base-tool.js +1 -1
  40. package/dist/tools/blueprint.d.ts +45 -61
  41. package/dist/tools/blueprint.js +43 -14
  42. package/dist/tools/consolidated-tool-definitions.js +2 -1
  43. package/dist/tools/consolidated-tool-handlers.js +96 -110
  44. package/dist/tools/dynamic-handler-registry.d.ts +11 -9
  45. package/dist/tools/dynamic-handler-registry.js +17 -95
  46. package/dist/tools/editor.d.ts +19 -193
  47. package/dist/tools/editor.js +11 -2
  48. package/dist/tools/environment.d.ts +8 -14
  49. package/dist/tools/foliage.d.ts +18 -143
  50. package/dist/tools/foliage.js +4 -2
  51. package/dist/tools/handlers/actor-handlers.d.ts +1 -1
  52. package/dist/tools/handlers/actor-handlers.js +14 -13
  53. package/dist/tools/handlers/asset-handlers.js +454 -454
  54. package/dist/tools/handlers/sequence-handlers.d.ts +1 -1
  55. package/dist/tools/handlers/sequence-handlers.js +24 -13
  56. package/dist/tools/introspection.d.ts +1 -1
  57. package/dist/tools/introspection.js +1 -1
  58. package/dist/tools/landscape.d.ts +16 -116
  59. package/dist/tools/landscape.js +7 -3
  60. package/dist/tools/level.d.ts +22 -103
  61. package/dist/tools/level.js +26 -18
  62. package/dist/tools/lighting.d.ts +54 -7
  63. package/dist/tools/lighting.js +9 -5
  64. package/dist/tools/materials.d.ts +1 -1
  65. package/dist/tools/materials.js +5 -1
  66. package/dist/tools/niagara.js +37 -2
  67. package/dist/tools/performance.d.ts +0 -1
  68. package/dist/tools/performance.js +0 -1
  69. package/dist/tools/physics.js +5 -1
  70. package/dist/tools/sequence.d.ts +24 -24
  71. package/dist/tools/sequence.js +13 -0
  72. package/dist/tools/ui.d.ts +0 -2
  73. package/dist/types/automation-responses.d.ts +115 -0
  74. package/dist/types/automation-responses.js +2 -0
  75. package/dist/types/responses.d.ts +249 -0
  76. package/dist/types/responses.js +2 -0
  77. package/dist/types/tool-interfaces.d.ts +135 -135
  78. package/dist/types/tool-types.d.ts +2 -0
  79. package/dist/unreal-bridge.js +4 -4
  80. package/dist/utils/command-validator.js +7 -5
  81. package/dist/utils/error-handler.d.ts +24 -2
  82. package/dist/utils/error-handler.js +58 -23
  83. package/dist/utils/normalize.d.ts +7 -4
  84. package/dist/utils/normalize.js +12 -10
  85. package/dist/utils/path-security.d.ts +2 -0
  86. package/dist/utils/path-security.js +24 -0
  87. package/dist/utils/response-factory.d.ts +4 -4
  88. package/dist/utils/response-factory.js +15 -21
  89. package/dist/utils/response-validator.js +88 -73
  90. package/dist/utils/unreal-command-queue.d.ts +2 -0
  91. package/dist/utils/unreal-command-queue.js +8 -1
  92. package/docs/Migration-Guide-v0.5.0.md +1 -9
  93. package/docs/handler-mapping.md +4 -2
  94. package/docs/testing-guide.md +2 -2
  95. package/package.json +12 -6
  96. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSubsystem.cpp +298 -33
  97. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AnimationHandlers.cpp +7 -8
  98. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintGraphHandlers.cpp +229 -319
  99. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers.cpp +98 -0
  100. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EffectHandlers.cpp +24 -0
  101. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +96 -0
  102. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +52 -5
  103. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ProcessRequest.cpp +5 -268
  104. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +57 -2
  105. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpConnectionManager.cpp +0 -1
  106. package/scripts/run-all-tests.mjs +25 -20
  107. package/server.json +3 -2
  108. package/src/automation/bridge.ts +27 -25
  109. package/src/automation/connection-manager.ts +18 -0
  110. package/src/automation/message-handler.ts +33 -8
  111. package/src/automation/request-tracker.ts +39 -7
  112. package/src/config.ts +1 -1
  113. package/src/constants.ts +7 -0
  114. package/src/graphql/loaders.ts +244 -0
  115. package/src/graphql/resolvers.ts +47 -49
  116. package/src/graphql/server.ts +3 -1
  117. package/src/graphql/types.ts +3 -0
  118. package/src/index.ts +15 -2
  119. package/src/resources/assets.ts +5 -4
  120. package/src/server/tool-registry.ts +3 -3
  121. package/src/server-setup.ts +3 -37
  122. package/src/tools/actors.ts +77 -44
  123. package/src/tools/animation.ts +1 -0
  124. package/src/tools/assets.ts +76 -65
  125. package/src/tools/base-tool.ts +3 -3
  126. package/src/tools/blueprint.ts +170 -104
  127. package/src/tools/consolidated-tool-definitions.ts +2 -1
  128. package/src/tools/consolidated-tool-handlers.ts +129 -150
  129. package/src/tools/dynamic-handler-registry.ts +22 -140
  130. package/src/tools/editor.ts +43 -29
  131. package/src/tools/environment.ts +21 -27
  132. package/src/tools/foliage.ts +28 -25
  133. package/src/tools/handlers/actor-handlers.ts +16 -17
  134. package/src/tools/handlers/asset-handlers.ts +484 -484
  135. package/src/tools/handlers/sequence-handlers.ts +85 -62
  136. package/src/tools/introspection.ts +7 -7
  137. package/src/tools/landscape.ts +34 -28
  138. package/src/tools/level.ts +100 -80
  139. package/src/tools/lighting.ts +25 -20
  140. package/src/tools/materials.ts +9 -3
  141. package/src/tools/niagara.ts +44 -2
  142. package/src/tools/performance.ts +1 -2
  143. package/src/tools/physics.ts +7 -1
  144. package/src/tools/sequence.ts +42 -26
  145. package/src/tools/ui.ts +1 -3
  146. package/src/types/automation-responses.ts +119 -0
  147. package/src/types/responses.ts +355 -0
  148. package/src/types/tool-interfaces.ts +135 -135
  149. package/src/types/tool-types.ts +4 -0
  150. package/src/unreal-bridge.ts +71 -26
  151. package/src/utils/command-validator.ts +47 -5
  152. package/src/utils/error-handler.ts +128 -45
  153. package/src/utils/normalize.test.ts +162 -0
  154. package/src/utils/normalize.ts +38 -16
  155. package/src/utils/path-security.ts +43 -0
  156. package/src/utils/response-factory.ts +29 -24
  157. package/src/utils/response-validator.ts +103 -87
  158. package/src/utils/safe-json.test.ts +90 -0
  159. package/src/utils/unreal-command-queue.ts +13 -1
  160. package/src/utils/validation.test.ts +184 -0
  161. package/tests/test-animation.mjs +358 -33
  162. package/tests/test-asset-graph.mjs +311 -0
  163. package/tests/test-audio.mjs +314 -116
  164. package/tests/test-behavior-tree.mjs +327 -144
  165. package/tests/test-blueprint-graph.mjs +343 -12
  166. package/tests/test-control-editor.mjs +85 -53
  167. package/tests/test-graphql.mjs +58 -8
  168. package/tests/test-input.mjs +349 -0
  169. package/tests/test-inspect.mjs +291 -61
  170. package/tests/test-landscape.mjs +304 -48
  171. package/tests/test-lighting.mjs +428 -0
  172. package/tests/test-manage-level.mjs +70 -51
  173. package/tests/test-performance.mjs +539 -0
  174. package/tests/test-sequence.mjs +82 -46
  175. package/tests/test-system.mjs +72 -33
  176. package/tests/test-wasm.mjs +98 -8
  177. package/vitest.config.ts +35 -0
  178. package/.github/release-drafter.yml +0 -148
  179. package/dist/prompts/index.d.ts +0 -21
  180. package/dist/prompts/index.js +0 -217
  181. package/dist/tools/blueprint/helpers.d.ts +0 -29
  182. package/dist/tools/blueprint/helpers.js +0 -182
  183. package/src/prompts/index.ts +0 -249
  184. package/src/tools/blueprint/helpers.ts +0 -189
  185. package/tests/test-blueprint-events.mjs +0 -35
  186. package/tests/test-extra-tools.mjs +0 -38
  187. package/tests/test-render.mjs +0 -33
  188. package/tests/test-search-assets.mjs +0 -66
@@ -2,6 +2,7 @@
2
2
  import { UnrealBridge } from '../unreal-bridge.js';
3
3
  import { AutomationBridge } from '../automation/index.js';
4
4
  import { ensureVector3 } from '../utils/validation.js';
5
+ import { wasmIntegration } from '../wasm/index.js';
5
6
 
6
7
  export class LightingTools {
7
8
  constructor(private bridge: UnrealBridge, private automationBridge?: AutomationBridge) { }
@@ -39,7 +40,7 @@ export class LightingTools {
39
40
  name: string;
40
41
  location?: [number, number, number];
41
42
  rotation?: [number, number, number] | { pitch: number, yaw: number, roll: number };
42
- properties?: Record<string, any>;
43
+ properties?: Record<string, unknown>;
43
44
  }
44
45
  ) {
45
46
  if (!this.automationBridge) {
@@ -47,13 +48,17 @@ export class LightingTools {
47
48
  }
48
49
 
49
50
  try {
50
- const payload: Record<string, any> = {
51
+ const payload: Record<string, unknown> = {
51
52
  lightClass,
52
53
  name: params.name,
53
54
  };
54
55
 
55
56
  if (params.location) {
56
- payload.location = { x: params.location[0], y: params.location[1], z: params.location[2] };
57
+ // Use WASM vectorAdd for light location processing
58
+ const zeroVector: [number, number, number] = [0, 0, 0];
59
+ const processedLocation = wasmIntegration.vectorAdd(zeroVector, params.location);
60
+ console.error('[WASM] Using vectorAdd for light positioning');
61
+ payload.location = { x: processedLocation[0], y: processedLocation[1], z: processedLocation[2] };
57
62
  }
58
63
 
59
64
  if (params.rotation) {
@@ -93,7 +98,7 @@ export class LightingTools {
93
98
  castShadows?: boolean;
94
99
  temperature?: number;
95
100
  useAsAtmosphereSunLight?: boolean;
96
- properties?: Record<string, any>;
101
+ properties?: Record<string, unknown>;
97
102
  }) {
98
103
  const name = this.normalizeName(params.name);
99
104
  if (!this.automationBridge) {
@@ -144,7 +149,7 @@ export class LightingTools {
144
149
  const rot = params.rotation || [0, 0, 0];
145
150
 
146
151
  // Build properties for the light
147
- const properties: Record<string, any> = params.properties || {};
152
+ const properties: Record<string, unknown> = params.properties || {};
148
153
  if (params.intensity !== undefined) {
149
154
  properties.intensity = params.intensity;
150
155
  }
@@ -170,9 +175,9 @@ export class LightingTools {
170
175
  });
171
176
 
172
177
  return { success: true, message: `Directional light '${name}' spawned` };
173
- } catch (e: any) {
178
+ } catch (e: unknown) {
174
179
  // Don't mask errors as "not implemented" - report the actual error from the bridge
175
- return { success: false, error: `Failed to create directional light: ${e?.message ?? e}` } as any;
180
+ return { success: false, error: `Failed to create directional light: ${(e instanceof Error ? e.message : String(e)) ?? e}` };
176
181
  }
177
182
  }
178
183
 
@@ -242,7 +247,7 @@ export class LightingTools {
242
247
  }
243
248
 
244
249
  // Build properties for the light
245
- const properties: Record<string, any> = {};
250
+ const properties: Record<string, unknown> = {};
246
251
  if (params.intensity !== undefined) {
247
252
  properties.intensity = params.intensity;
248
253
  }
@@ -268,9 +273,9 @@ export class LightingTools {
268
273
  });
269
274
 
270
275
  return { success: true, message: `Point light '${name}' spawned at ${location.join(', ')}` };
271
- } catch (e: any) {
276
+ } catch (e: unknown) {
272
277
  // Don't mask errors as "not implemented" - report the actual error from the bridge
273
- return { success: false, error: `Failed to create point light: ${e?.message ?? e}` } as any;
278
+ return { success: false, error: `Failed to create point light: ${(e instanceof Error ? e.message : String(e)) ?? e}` };
274
279
  }
275
280
  }
276
281
 
@@ -364,7 +369,7 @@ export class LightingTools {
364
369
  }
365
370
  }
366
371
  // Build properties for the light
367
- const properties: Record<string, any> = {};
372
+ const properties: Record<string, unknown> = {};
368
373
  if (params.intensity !== undefined) {
369
374
  properties.intensity = params.intensity;
370
375
  }
@@ -393,9 +398,9 @@ export class LightingTools {
393
398
  });
394
399
 
395
400
  return { success: true, message: `Spot light '${name}' spawned at ${params.location.join(', ')}` };
396
- } catch (e: any) {
401
+ } catch (e: unknown) {
397
402
  // Don't mask errors as "not implemented" - report the actual error from the bridge
398
- return { success: false, error: `Failed to create spot light: ${e?.message ?? e}` } as any;
403
+ return { success: false, error: `Failed to create spot light: ${(e instanceof Error ? e.message : String(e)) ?? e}` };
399
404
  }
400
405
  }
401
406
 
@@ -480,7 +485,7 @@ export class LightingTools {
480
485
  }
481
486
  }
482
487
  // Build properties for the light
483
- const properties: Record<string, any> = {};
488
+ const properties: Record<string, unknown> = {};
484
489
  if (params.intensity !== undefined) {
485
490
  properties.intensity = params.intensity;
486
491
  }
@@ -503,9 +508,9 @@ export class LightingTools {
503
508
  });
504
509
 
505
510
  return { success: true, message: `Rect light '${name}' spawned at ${params.location.join(', ')}` };
506
- } catch (e: any) {
511
+ } catch (e: unknown) {
507
512
  // Don't mask errors as "not implemented" - report the actual error from the bridge
508
- return { success: false, error: `Failed to create rect light: ${e?.message ?? e}` } as any;
513
+ return { success: false, error: `Failed to create rect light: ${(e instanceof Error ? e.message : String(e)) ?? e}` };
509
514
  }
510
515
  }
511
516
 
@@ -576,13 +581,13 @@ export class LightingTools {
576
581
  }
577
582
 
578
583
  try {
579
- const properties: Record<string, any> = {};
584
+ const properties: Record<string, unknown> = {};
580
585
  if (params.intensity !== undefined) properties.Intensity = params.intensity;
581
586
  if (params.castShadows !== undefined) properties.CastShadows = params.castShadows;
582
587
  if (params.realTimeCapture !== undefined) properties.RealTimeCapture = params.realTimeCapture;
583
588
  if (params.color) properties.LightColor = { r: params.color[0], g: params.color[1], b: params.color[2], a: 1.0 };
584
589
 
585
- const payload: Record<string, any> = {
590
+ const payload: Record<string, unknown> = {
586
591
  name,
587
592
  sourceType: params.sourceType || 'CapturedScene',
588
593
  location: params.location,
@@ -806,12 +811,12 @@ export class LightingTools {
806
811
  success: true,
807
812
  message: response.message || 'Lighting build started',
808
813
  ...(response.result || {})
809
- } as any;
814
+ };
810
815
  } catch (error) {
811
816
  return {
812
817
  success: false,
813
818
  error: `Failed to build lighting: ${error instanceof Error ? error.message : String(error)}`
814
- } as any;
819
+ };
815
820
  }
816
821
  }
817
822
 
@@ -120,7 +120,7 @@ export class MaterialTools {
120
120
  }
121
121
  }
122
122
 
123
- async createMaterialInstance(name: string, path: string, parentMaterial: string, parameters?: Record<string, any>) {
123
+ async createMaterialInstance(name: string, path: string, parentMaterial: string, parameters?: Record<string, unknown>) {
124
124
  try {
125
125
  if (!name || name.trim() === '') {
126
126
  return { success: false, error: 'Material instance name cannot be empty' };
@@ -146,8 +146,14 @@ export class MaterialTools {
146
146
  if (!(errTxt.toLowerCase().includes('unknown') || errTxt.includes('UNKNOWN_PLUGIN_ACTION'))) {
147
147
  return { success: false, error: resp?.error ?? resp?.message ?? 'CREATE_MATERIAL_INSTANCE_FAILED' } as any;
148
148
  }
149
- } catch (_e) {
150
- // fall back to python path below
149
+ } catch (e) {
150
+ // If the error is simply generic or unknown action, we fall back.
151
+ // But if it's a specific error, we might log it.
152
+ // For now, let's at least not silence everything.
153
+ const msg = e instanceof Error ? e.message : String(e);
154
+ if (!msg.includes('unknown') && !msg.includes('UNKNOWN_PLUGIN_ACTION')) {
155
+ console.warn(`[MaterialTools] Plugin create_material_instance failed with specific error (falling back to python): ${msg}`);
156
+ }
151
157
  }
152
158
  }
153
159
 
@@ -1,6 +1,7 @@
1
1
  import { UnrealBridge } from '../unreal-bridge.js';
2
2
  import { AutomationBridge } from '../automation/index.js';
3
3
  import { sanitizeAssetName, validateAssetParams } from '../utils/validation.js';
4
+ import { wasmIntegration } from '../wasm/index.js';
4
5
 
5
6
  type Vector3 = [number, number, number];
6
7
 
@@ -30,6 +31,18 @@ export class NiagaraTools {
30
31
  throw new Error('Automation Bridge not available. Niagara system creation requires plugin support.');
31
32
  }
32
33
 
34
+ // Process emitter params with WASM
35
+ if (params.emitters) {
36
+ for (const emitter of params.emitters) {
37
+ if (emitter.shapeSize) {
38
+ const zeroVector: [number, number, number] = [0, 0, 0];
39
+ const processedSize = wasmIntegration.vectorAdd(zeroVector, emitter.shapeSize);
40
+ console.error('[WASM] Using vectorAdd for Niagara emitter shape size');
41
+ emitter.shapeSize = [processedSize[0], processedSize[1], processedSize[2]];
42
+ }
43
+ }
44
+ }
45
+
33
46
  const path = params.savePath || '/Game/Effects/Niagara';
34
47
  const response: any = await this.automationBridge.sendAutomationRequest(
35
48
  'create_niagara_system',
@@ -118,8 +131,22 @@ export class NiagaraTools {
118
131
  };
119
132
 
120
133
  const requestPayload: Record<string, unknown> = { systemPath: params.systemPath };
121
- const start = toVector(params.start);
122
- const end = toVector(params.end);
134
+ let start = toVector(params.start);
135
+ let end = toVector(params.end);
136
+
137
+ // Use WASM for vector processing if available
138
+ const zeroVector: [number, number, number] = [0, 0, 0];
139
+ if (start) {
140
+ const processed = wasmIntegration.vectorAdd(zeroVector, start);
141
+ console.error('[WASM] Using vectorAdd for Niagara ribbon start');
142
+ start = [processed[0], processed[1], processed[2]];
143
+ }
144
+ if (end) {
145
+ const processed = wasmIntegration.vectorAdd(zeroVector, end);
146
+ console.error('[WASM] Using vectorAdd for Niagara ribbon end');
147
+ end = [processed[0], processed[1], processed[2]];
148
+ }
149
+
123
150
  if (start) requestPayload.start = start;
124
151
  if (end) requestPayload.end = end;
125
152
  if (params.color) requestPayload.color = params.color;
@@ -202,6 +229,21 @@ export class NiagaraTools {
202
229
  return { success: false, error: 'AUTOMATION_BRIDGE_UNAVAILABLE', message: 'addEmitter requires automation bridge' } as const;
203
230
  }
204
231
 
232
+ // Use WASM for velocity processing
233
+ if (params.properties) {
234
+ const zeroVector: [number, number, number] = [0, 0, 0];
235
+ if (params.properties.velocityMin) {
236
+ const processed = wasmIntegration.vectorAdd(zeroVector, params.properties.velocityMin);
237
+ console.error('[WASM] Using vectorAdd for Niagara velocity min');
238
+ params.properties.velocityMin = [processed[0], processed[1], processed[2]];
239
+ }
240
+ if (params.properties.velocityMax) {
241
+ const processed = wasmIntegration.vectorAdd(zeroVector, params.properties.velocityMax);
242
+ console.error('[WASM] Using vectorAdd for Niagara velocity max');
243
+ params.properties.velocityMax = [processed[0], processed[1], processed[2]];
244
+ }
245
+ }
246
+
205
247
  try {
206
248
  const resp: any = await this.automationBridge.sendAutomationRequest('manage_niagara_graph', {
207
249
  subAction: 'add_emitter',
@@ -474,7 +474,7 @@ export class PerformanceTools {
474
474
  // Draw call optimization
475
475
  async optimizeDrawCalls(params: {
476
476
  enableInstancing?: boolean;
477
- enableBatching?: boolean; // no-op (deprecated internal toggle)
477
+
478
478
  mergeActors?: boolean;
479
479
  actors?: string[];
480
480
  }) {
@@ -495,7 +495,6 @@ export class PerformanceTools {
495
495
 
496
496
  const payload: any = {
497
497
  enableInstancing: params.enableInstancing,
498
- enableBatching: params.enableBatching,
499
498
  mergeActors: params.mergeActors,
500
499
  actors: actors
501
500
  };
@@ -3,6 +3,7 @@ import { UnrealBridge } from '../unreal-bridge.js';
3
3
  import { AutomationBridge } from '../automation/index.js';
4
4
  import { validateAssetParams, resolveSkeletalMeshPath, concurrencyDelay } from '../utils/validation.js';
5
5
  import { coerceString, coerceStringArray } from '../utils/result-helpers.js';
6
+ import { wasmIntegration } from '../wasm/index.js';
6
7
 
7
8
  export class PhysicsTools {
8
9
  constructor(private bridge: UnrealBridge, private automationBridge?: AutomationBridge) { }
@@ -485,10 +486,15 @@ export class PhysicsTools {
485
486
  }
486
487
 
487
488
  try {
489
+ // Use WASM for vector normalization/validation
490
+ const zeroVector: [number, number, number] = [0, 0, 0];
491
+ const normalizedVector = wasmIntegration.vectorAdd(zeroVector, params.vector);
492
+ console.error('[WASM] Using vectorAdd for physics force vector processing');
493
+
488
494
  const response = await this.automationBridge.sendAutomationRequest('apply_force', {
489
495
  actorName: params.actorName,
490
496
  forceType: params.forceType,
491
- vector: params.vector,
497
+ vector: normalizedVector,
492
498
  boneName: params.boneName,
493
499
  isLocal: params.isLocal
494
500
  }, {
@@ -1,5 +1,7 @@
1
1
  import { BaseTool } from './base-tool.js';
2
- import { ISequenceTools } from '../types/tool-interfaces.js';
2
+ import { ISequenceTools, StandardActionResponse } from '../types/tool-interfaces.js';
3
+ import { SequenceResponse } from '../types/automation-responses.js';
4
+ import { wasmIntegration } from '../wasm/index.js';
3
5
 
4
6
  export interface LevelSequence {
5
7
  path: string;
@@ -39,7 +41,7 @@ export class SequenceTools extends BaseTool implements ISequenceTools {
39
41
  const finalTimeout = typeof timeoutMs === 'number' && timeoutMs > 0 ? timeoutMs : defaultTimeout;
40
42
 
41
43
  try {
42
- const response = await this.sendAutomationRequest(
44
+ const response = await this.sendAutomationRequest<SequenceResponse>(
43
45
  action,
44
46
  payload,
45
47
  { timeoutMs: finalTimeout, waitForEvent: false }
@@ -49,7 +51,7 @@ export class SequenceTools extends BaseTool implements ISequenceTools {
49
51
  const result = response.result ?? response;
50
52
 
51
53
  return { success, message: response.message ?? undefined, error: response.success === false ? (response.error ?? response.message) : undefined, result, requestId: response.requestId } as any;
52
- } catch (err: any) {
54
+ } catch (err: unknown) {
53
55
  return { success: false, error: String(err), message: String(err) } as const;
54
56
  }
55
57
  }
@@ -61,7 +63,7 @@ export class SequenceTools extends BaseTool implements ISequenceTools {
61
63
  return txt.includes('unknown_action') || txt.includes('unknown automation action') || txt.includes('not_implemented') || txt === 'unknown_plugin_action';
62
64
  }
63
65
 
64
- async create(params: { name: string; path?: string; timeoutMs?: number }) {
66
+ async create(params: { name: string; path?: string; timeoutMs?: number }): Promise<StandardActionResponse> {
65
67
  const name = params.name?.trim();
66
68
  const base = (params.path || '/Game/Sequences').replace(/\/$/, '');
67
69
  if (!name) return { success: false, error: 'name is required' };
@@ -79,7 +81,7 @@ export class SequenceTools extends BaseTool implements ISequenceTools {
79
81
  return resp;
80
82
  }
81
83
 
82
- async open(params: { path: string }) {
84
+ async open(params: { path: string }): Promise<StandardActionResponse> {
83
85
  const path = params.path?.trim();
84
86
  const resp = await this.sendAction('sequence_open', { path });
85
87
  if (!resp.success && this.isUnknownActionResponse(resp)) {
@@ -91,7 +93,7 @@ export class SequenceTools extends BaseTool implements ISequenceTools {
91
93
  return resp;
92
94
  }
93
95
 
94
- async addCamera(params: { spawnable?: boolean; path?: string }) {
96
+ async addCamera(params: { spawnable?: boolean; path?: string }): Promise<StandardActionResponse> {
95
97
  const path = this.resolveSequencePath(params.path);
96
98
  const resp = await this.sendAction('sequence_add_camera', { path, spawnable: params.spawnable !== false });
97
99
  if (!resp.success && this.isUnknownActionResponse(resp)) {
@@ -100,7 +102,7 @@ export class SequenceTools extends BaseTool implements ISequenceTools {
100
102
  return resp;
101
103
  }
102
104
 
103
- async addActor(params: { actorName: string; createBinding?: boolean; path?: string }) {
105
+ async addActor(params: { actorName: string; createBinding?: boolean; path?: string }): Promise<StandardActionResponse> {
104
106
  const path = this.resolveSequencePath(params.path);
105
107
  const resp = await this.sendAction('sequence_add_actor', { path, actorName: params.actorName, createBinding: params.createBinding });
106
108
  if (!resp.success && this.isUnknownActionResponse(resp)) {
@@ -112,7 +114,7 @@ export class SequenceTools extends BaseTool implements ISequenceTools {
112
114
  /**
113
115
  * Play the current level sequence
114
116
  */
115
- async play(params?: { path?: string; startTime?: number; loopMode?: 'once' | 'loop' | 'pingpong' }) {
117
+ async play(params?: { path?: string; startTime?: number; loopMode?: 'once' | 'loop' | 'pingpong' }): Promise<StandardActionResponse> {
116
118
  const path = this.resolveSequencePath(params?.path);
117
119
  const resp = await this.sendAction('sequence_play', { path, startTime: params?.startTime, loopMode: params?.loopMode });
118
120
  if (!resp.success && this.isUnknownActionResponse(resp)) {
@@ -124,7 +126,7 @@ export class SequenceTools extends BaseTool implements ISequenceTools {
124
126
  /**
125
127
  * Pause the current level sequence
126
128
  */
127
- async pause(params?: { path?: string }) {
129
+ async pause(params?: { path?: string }): Promise<StandardActionResponse> {
128
130
  const path = this.resolveSequencePath(params?.path);
129
131
  const resp = await this.sendAction('sequence_pause', { path });
130
132
  if (!resp.success && this.isUnknownActionResponse(resp)) {
@@ -136,7 +138,7 @@ export class SequenceTools extends BaseTool implements ISequenceTools {
136
138
  /**
137
139
  * Stop/close the current level sequence
138
140
  */
139
- async stop(params?: { path?: string }) {
141
+ async stop(params?: { path?: string }): Promise<StandardActionResponse> {
140
142
  const path = this.resolveSequencePath(params?.path);
141
143
  const resp = await this.sendAction('sequence_stop', { path });
142
144
  if (!resp.success && this.isUnknownActionResponse(resp)) {
@@ -154,7 +156,7 @@ export class SequenceTools extends BaseTool implements ISequenceTools {
154
156
  lengthInFrames?: number;
155
157
  playbackStart?: number;
156
158
  playbackEnd?: number;
157
- }) {
159
+ }): Promise<StandardActionResponse> {
158
160
  const payload: Record<string, unknown> = {
159
161
  path: params.path,
160
162
  frameRate: params.frameRate,
@@ -172,7 +174,7 @@ export class SequenceTools extends BaseTool implements ISequenceTools {
172
174
  /**
173
175
  * Set display rate (fps)
174
176
  */
175
- async setDisplayRate(params: { path?: string; frameRate: string | number }) {
177
+ async setDisplayRate(params: { path?: string; frameRate: string | number }): Promise<StandardActionResponse> {
176
178
  const resp = await this.sendAction('sequence_set_display_rate', { path: params.path, frameRate: params.frameRate });
177
179
  if (!resp.success && this.isUnknownActionResponse(resp)) {
178
180
  return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_set_display_rate' } as const;
@@ -183,7 +185,7 @@ export class SequenceTools extends BaseTool implements ISequenceTools {
183
185
  /**
184
186
  * Get sequence properties
185
187
  */
186
- async getSequenceProperties(params: { path?: string }) {
188
+ async getSequenceProperties(params: { path?: string }): Promise<StandardActionResponse> {
187
189
  const resp = await this.sendAction('sequence_get_properties', { path: params.path });
188
190
  if (!resp.success && this.isUnknownActionResponse(resp)) {
189
191
  return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_get_properties' } as const;
@@ -194,7 +196,7 @@ export class SequenceTools extends BaseTool implements ISequenceTools {
194
196
  /**
195
197
  * Set playback speed/rate
196
198
  */
197
- async setPlaybackSpeed(params: { speed: number; path?: string }) {
199
+ async setPlaybackSpeed(params: { speed: number; path?: string }): Promise<StandardActionResponse> {
198
200
  const path = this.resolveSequencePath(params.path);
199
201
  const resp = await this.sendAction('sequence_set_playback_speed', { path, speed: params.speed });
200
202
  if (!resp.success && this.isUnknownActionResponse(resp)) {
@@ -206,7 +208,7 @@ export class SequenceTools extends BaseTool implements ISequenceTools {
206
208
  /**
207
209
  * Get all bindings in the current sequence
208
210
  */
209
- async getBindings(params?: { path?: string }) {
211
+ async getBindings(params?: { path?: string }): Promise<StandardActionResponse> {
210
212
  const resp = await this.sendAction('sequence_get_bindings', { path: params?.path });
211
213
  if (!resp.success && this.isUnknownActionResponse(resp)) {
212
214
  return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_get_bindings' } as const;
@@ -217,7 +219,7 @@ export class SequenceTools extends BaseTool implements ISequenceTools {
217
219
  /**
218
220
  * Add multiple actors to sequence at once
219
221
  */
220
- async addActors(params: { actorNames: string[]; path?: string }) {
222
+ async addActors(params: { actorNames: string[]; path?: string }): Promise<StandardActionResponse> {
221
223
  const path = this.resolveSequencePath(params.path);
222
224
  const resp = await this.sendAction('sequence_add_actors', { path, actorNames: params.actorNames });
223
225
  if (!resp.success && this.isUnknownActionResponse(resp)) {
@@ -229,7 +231,7 @@ export class SequenceTools extends BaseTool implements ISequenceTools {
229
231
  /**
230
232
  * Remove actors from binding
231
233
  */
232
- async removeActors(params: { actorNames: string[]; path?: string }) {
234
+ async removeActors(params: { actorNames: string[]; path?: string }): Promise<StandardActionResponse> {
233
235
  const path = this.resolveSequencePath(params.path);
234
236
  const resp = await this.sendAction('sequence_remove_actors', { path, actorNames: params.actorNames });
235
237
  if (!resp.success && this.isUnknownActionResponse(resp)) {
@@ -241,7 +243,7 @@ export class SequenceTools extends BaseTool implements ISequenceTools {
241
243
  /**
242
244
  * Create a spawnable from an actor class
243
245
  */
244
- async addSpawnableFromClass(params: { className: string; path?: string }) {
246
+ async addSpawnableFromClass(params: { className: string; path?: string }): Promise<StandardActionResponse> {
245
247
  const resp = await this.sendAction('sequence_add_spawnable_from_class', { className: params.className, path: params.path });
246
248
  if (!resp.success && this.isUnknownActionResponse(resp)) {
247
249
  return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_add_spawnable_from_class' } as const;
@@ -249,7 +251,7 @@ export class SequenceTools extends BaseTool implements ISequenceTools {
249
251
  return resp;
250
252
  }
251
253
 
252
- async list(params?: { path?: string }) {
254
+ async list(params?: { path?: string }): Promise<StandardActionResponse> {
253
255
  const resp = await this.sendAction('sequence_list', { path: params?.path });
254
256
  if (!resp.success && this.isUnknownActionResponse(resp)) {
255
257
  return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_list' } as const;
@@ -265,7 +267,7 @@ export class SequenceTools extends BaseTool implements ISequenceTools {
265
267
  return resp;
266
268
  }
267
269
 
268
- async duplicate(params: { path: string; destinationPath: string }) {
270
+ async duplicate(params: { path: string; destinationPath: string }): Promise<StandardActionResponse> {
269
271
  const resp = await this.sendAction('sequence_duplicate', { path: params.path, destinationPath: params.destinationPath });
270
272
  if (!resp.success && this.isUnknownActionResponse(resp)) {
271
273
  return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_duplicate' } as const;
@@ -273,7 +275,7 @@ export class SequenceTools extends BaseTool implements ISequenceTools {
273
275
  return resp;
274
276
  }
275
277
 
276
- async rename(params: { path: string; newName: string }) {
278
+ async rename(params: { path: string; newName: string }): Promise<StandardActionResponse> {
277
279
  const resp = await this.sendAction('sequence_rename', { path: params.path, newName: params.newName });
278
280
  if (!resp.success && this.isUnknownActionResponse(resp)) {
279
281
  return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_rename' } as const;
@@ -281,7 +283,7 @@ export class SequenceTools extends BaseTool implements ISequenceTools {
281
283
  return resp;
282
284
  }
283
285
 
284
- async deleteSequence(params: { path: string }) {
286
+ async deleteSequence(params: { path: string }): Promise<StandardActionResponse> {
285
287
  const resp = await this.sendAction('sequence_delete', { path: params.path });
286
288
  if (!resp.success && this.isUnknownActionResponse(resp)) {
287
289
  return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_delete' } as const;
@@ -289,7 +291,7 @@ export class SequenceTools extends BaseTool implements ISequenceTools {
289
291
  return resp;
290
292
  }
291
293
 
292
- async getMetadata(params: { path?: string }) {
294
+ async getMetadata(params: { path?: string }): Promise<StandardActionResponse> {
293
295
  const resp = await this.sendAction('sequence_get_metadata', { path: params.path });
294
296
  if (!resp.success && this.isUnknownActionResponse(resp)) {
295
297
  return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_get_metadata' } as const;
@@ -311,7 +313,7 @@ export class SequenceTools extends BaseTool implements ISequenceTools {
311
313
  rotation?: { roll: number; pitch: number; yaw: number };
312
314
  scale?: { x: number; y: number; z: number };
313
315
  };
314
- }) {
316
+ }): Promise<StandardActionResponse> {
315
317
  const resp = await this.sendAction('sequence_add_keyframe', {
316
318
  path: params.path,
317
319
  bindingId: params.bindingId,
@@ -320,6 +322,20 @@ export class SequenceTools extends BaseTool implements ISequenceTools {
320
322
  frame: params.frame,
321
323
  value: params.value
322
324
  });
325
+
326
+ // Use WASM for transform processing
327
+ if (params.property === 'Transform' && params.value) {
328
+ const loc = params.value.location;
329
+ const rot = params.value.rotation;
330
+ const scale = params.value.scale;
331
+ if (loc && rot && scale) {
332
+ const locArr: [number, number, number] = [loc.x, loc.y, loc.z];
333
+ const rotArr: [number, number, number] = [rot.pitch, rot.yaw, rot.roll];
334
+ const scaleArr: [number, number, number] = [scale.x, scale.y, scale.z];
335
+ wasmIntegration.composeTransform(locArr, rotArr, scaleArr);
336
+ console.error('[WASM] Using composeTransform for keyframe validation');
337
+ }
338
+ }
323
339
  if (!resp.success && this.isUnknownActionResponse(resp)) {
324
340
  return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_add_keyframe' } as const;
325
341
  }
@@ -329,7 +345,7 @@ export class SequenceTools extends BaseTool implements ISequenceTools {
329
345
  /**
330
346
  * List tracks in a sequence
331
347
  */
332
- async listTracks(params: { path: string }) {
348
+ async listTracks(params: { path: string }): Promise<StandardActionResponse> {
333
349
  const resp = await this.sendAction('sequence_list_tracks', { path: params.path });
334
350
  if (!resp.success && this.isUnknownActionResponse(resp)) {
335
351
  return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_list_tracks' } as const;
@@ -348,7 +364,7 @@ export class SequenceTools extends BaseTool implements ISequenceTools {
348
364
  /**
349
365
  * Set playback work range
350
366
  */
351
- async setWorkRange(params: { path?: string; start: number; end: number }) {
367
+ async setWorkRange(params: { path?: string; start: number; end: number }): Promise<StandardActionResponse> {
352
368
  const resp = await this.sendAction('sequence_set_work_range', { path: params.path, start: params.start, end: params.end });
353
369
  if (!resp.success && this.isUnknownActionResponse(resp)) {
354
370
  return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_set_work_range' } as const;
package/src/tools/ui.ts CHANGED
@@ -59,7 +59,7 @@ export class UITools {
59
59
  package_path: path,
60
60
  factory_class: 'WidgetBlueprintFactory',
61
61
  asset_class: 'unreal.WidgetBlueprint'
62
- } as Record<string, any>;
62
+ } as Record<string, unknown>;
63
63
 
64
64
  const resp = await this.bridge.executeEditorFunction('CREATE_ASSET', payload as any);
65
65
  const result = resp && typeof resp === 'object' ? (resp.result ?? resp) : resp;
@@ -123,7 +123,6 @@ export class UITools {
123
123
  async setWidgetText(_params: {
124
124
  key: string; // The widget name to find
125
125
  value: string; // The text to set
126
- componentName?: string; // Legacy/Unused in new impl
127
126
  }) {
128
127
  if (!this.automationBridge) {
129
128
  throw new Error('Automation bridge required for setting widget text');
@@ -149,7 +148,6 @@ export class UITools {
149
148
  async setWidgetImage(_params: {
150
149
  key: string;
151
150
  texturePath: string;
152
- componentName?: string; // Unused
153
151
  }) {
154
152
  if (!this.automationBridge) {
155
153
  throw new Error('Automation bridge required for setting widget images');