unreal-engine-mcp-server 0.4.0 → 0.4.3

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 (135) hide show
  1. package/.env.production +1 -1
  2. package/.github/copilot-instructions.md +45 -0
  3. package/.github/workflows/publish-mcp.yml +1 -1
  4. package/README.md +21 -5
  5. package/dist/index.js +124 -31
  6. package/dist/prompts/index.d.ts +10 -3
  7. package/dist/prompts/index.js +186 -7
  8. package/dist/resources/actors.d.ts +19 -1
  9. package/dist/resources/actors.js +55 -64
  10. package/dist/resources/assets.js +46 -62
  11. package/dist/resources/levels.d.ts +21 -3
  12. package/dist/resources/levels.js +29 -54
  13. package/dist/tools/actors.d.ts +3 -14
  14. package/dist/tools/actors.js +246 -302
  15. package/dist/tools/animation.d.ts +57 -102
  16. package/dist/tools/animation.js +429 -450
  17. package/dist/tools/assets.d.ts +13 -2
  18. package/dist/tools/assets.js +52 -44
  19. package/dist/tools/audio.d.ts +22 -13
  20. package/dist/tools/audio.js +467 -121
  21. package/dist/tools/blueprint.d.ts +32 -13
  22. package/dist/tools/blueprint.js +699 -448
  23. package/dist/tools/build_environment_advanced.d.ts +0 -1
  24. package/dist/tools/build_environment_advanced.js +190 -45
  25. package/dist/tools/consolidated-tool-definitions.js +78 -252
  26. package/dist/tools/consolidated-tool-handlers.js +506 -133
  27. package/dist/tools/debug.d.ts +72 -10
  28. package/dist/tools/debug.js +167 -31
  29. package/dist/tools/editor.d.ts +9 -2
  30. package/dist/tools/editor.js +30 -44
  31. package/dist/tools/foliage.d.ts +34 -15
  32. package/dist/tools/foliage.js +97 -107
  33. package/dist/tools/introspection.js +19 -21
  34. package/dist/tools/landscape.d.ts +1 -2
  35. package/dist/tools/landscape.js +311 -168
  36. package/dist/tools/level.d.ts +3 -28
  37. package/dist/tools/level.js +642 -192
  38. package/dist/tools/lighting.d.ts +14 -3
  39. package/dist/tools/lighting.js +236 -123
  40. package/dist/tools/materials.d.ts +25 -7
  41. package/dist/tools/materials.js +102 -79
  42. package/dist/tools/niagara.d.ts +10 -12
  43. package/dist/tools/niagara.js +74 -94
  44. package/dist/tools/performance.d.ts +12 -4
  45. package/dist/tools/performance.js +38 -79
  46. package/dist/tools/physics.d.ts +34 -10
  47. package/dist/tools/physics.js +364 -292
  48. package/dist/tools/rc.js +97 -23
  49. package/dist/tools/sequence.d.ts +1 -0
  50. package/dist/tools/sequence.js +125 -22
  51. package/dist/tools/ui.d.ts +31 -4
  52. package/dist/tools/ui.js +83 -66
  53. package/dist/tools/visual.d.ts +11 -0
  54. package/dist/tools/visual.js +245 -30
  55. package/dist/types/tool-types.d.ts +0 -6
  56. package/dist/types/tool-types.js +1 -8
  57. package/dist/unreal-bridge.d.ts +32 -2
  58. package/dist/unreal-bridge.js +621 -127
  59. package/dist/utils/elicitation.d.ts +57 -0
  60. package/dist/utils/elicitation.js +104 -0
  61. package/dist/utils/error-handler.d.ts +0 -33
  62. package/dist/utils/error-handler.js +4 -111
  63. package/dist/utils/http.d.ts +2 -22
  64. package/dist/utils/http.js +12 -75
  65. package/dist/utils/normalize.d.ts +4 -4
  66. package/dist/utils/normalize.js +15 -7
  67. package/dist/utils/python-output.d.ts +18 -0
  68. package/dist/utils/python-output.js +290 -0
  69. package/dist/utils/python.d.ts +2 -0
  70. package/dist/utils/python.js +4 -0
  71. package/dist/utils/response-validator.js +28 -2
  72. package/dist/utils/result-helpers.d.ts +27 -0
  73. package/dist/utils/result-helpers.js +147 -0
  74. package/dist/utils/safe-json.d.ts +0 -2
  75. package/dist/utils/safe-json.js +0 -43
  76. package/dist/utils/validation.d.ts +16 -0
  77. package/dist/utils/validation.js +70 -7
  78. package/mcp-config-example.json +2 -2
  79. package/package.json +10 -9
  80. package/server.json +37 -14
  81. package/src/index.ts +130 -33
  82. package/src/prompts/index.ts +211 -13
  83. package/src/resources/actors.ts +59 -44
  84. package/src/resources/assets.ts +48 -51
  85. package/src/resources/levels.ts +35 -45
  86. package/src/tools/actors.ts +269 -313
  87. package/src/tools/animation.ts +556 -539
  88. package/src/tools/assets.ts +53 -43
  89. package/src/tools/audio.ts +507 -113
  90. package/src/tools/blueprint.ts +778 -462
  91. package/src/tools/build_environment_advanced.ts +266 -64
  92. package/src/tools/consolidated-tool-definitions.ts +90 -264
  93. package/src/tools/consolidated-tool-handlers.ts +630 -121
  94. package/src/tools/debug.ts +176 -33
  95. package/src/tools/editor.ts +35 -37
  96. package/src/tools/foliage.ts +110 -104
  97. package/src/tools/introspection.ts +24 -22
  98. package/src/tools/landscape.ts +334 -181
  99. package/src/tools/level.ts +683 -182
  100. package/src/tools/lighting.ts +244 -123
  101. package/src/tools/materials.ts +114 -83
  102. package/src/tools/niagara.ts +87 -81
  103. package/src/tools/performance.ts +49 -88
  104. package/src/tools/physics.ts +393 -299
  105. package/src/tools/rc.ts +102 -24
  106. package/src/tools/sequence.ts +136 -28
  107. package/src/tools/ui.ts +101 -70
  108. package/src/tools/visual.ts +250 -29
  109. package/src/types/tool-types.ts +0 -9
  110. package/src/unreal-bridge.ts +658 -140
  111. package/src/utils/elicitation.ts +129 -0
  112. package/src/utils/error-handler.ts +4 -159
  113. package/src/utils/http.ts +16 -115
  114. package/src/utils/normalize.ts +20 -10
  115. package/src/utils/python-output.ts +351 -0
  116. package/src/utils/python.ts +3 -0
  117. package/src/utils/response-validator.ts +25 -2
  118. package/src/utils/result-helpers.ts +193 -0
  119. package/src/utils/safe-json.ts +0 -50
  120. package/src/utils/validation.ts +94 -7
  121. package/tests/run-unreal-tool-tests.mjs +720 -0
  122. package/tsconfig.json +2 -2
  123. package/dist/python-utils.d.ts +0 -29
  124. package/dist/python-utils.js +0 -54
  125. package/dist/types/index.d.ts +0 -323
  126. package/dist/types/index.js +0 -28
  127. package/dist/utils/cache-manager.d.ts +0 -64
  128. package/dist/utils/cache-manager.js +0 -176
  129. package/dist/utils/errors.d.ts +0 -133
  130. package/dist/utils/errors.js +0 -256
  131. package/src/python/editor_compat.py +0 -181
  132. package/src/python-utils.ts +0 -57
  133. package/src/types/index.ts +0 -414
  134. package/src/utils/cache-manager.ts +0 -213
  135. package/src/utils/errors.ts +0 -312
@@ -1,40 +1,95 @@
1
1
  // Debug visualization tools for Unreal Engine
2
2
  import { UnrealBridge } from '../unreal-bridge.js';
3
+ import { bestEffortInterpretedText, coerceString, interpretStandardResult } from '../utils/result-helpers.js';
4
+ import { parseStandardResult } from '../utils/python-output.js';
3
5
 
4
6
  export class DebugVisualizationTools {
5
7
  constructor(private bridge: UnrealBridge) {}
6
8
 
7
- // Execute console command (kept for legacy operations)
8
- private async _executeCommand(command: string) {
9
- return this.bridge.httpCall('/remote/object/call', 'PUT', {
10
- objectPath: '/Script/Engine.Default__KismetSystemLibrary',
11
- functionName: 'ExecuteConsoleCommand',
12
- parameters: {
13
- WorldContextObject: null,
14
- Command: command,
15
- SpecificPlayer: null
16
- },
17
- generateTransaction: false
18
- });
19
- }
20
-
21
9
  // Helper to draw via Python SystemLibrary with the editor world
22
- private async pyDraw(scriptBody: string) {
10
+ private async pyDraw(scriptBody: string, meta?: { action: string; params?: Record<string, unknown> }) {
11
+ const action = (meta?.action || 'debug_draw').replace(/\\/g, '\\\\').replace(/'/g, "\\'");
12
+ const payloadObject = meta?.params ?? {};
13
+ const payloadJson = JSON.stringify(payloadObject).replace(/\\/g, '\\\\').replace(/'/g, "\\'");
14
+ const indentedBody = scriptBody
15
+ .split(/\r?\n/)
16
+ .map(line => ` ${line}`)
17
+ .join('\n');
18
+
23
19
  const script = `
24
20
  import unreal
25
- # Strict modern API: require UnrealEditorSubsystem (UE 5.1+)
21
+ import json
22
+
23
+ payload = json.loads('${payloadJson}')
26
24
  ues = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem)
27
25
  if not ues:
28
26
  raise Exception('UnrealEditorSubsystem not available')
29
27
  world = ues.get_editor_world()
30
- ${scriptBody}
28
+ if not world:
29
+ raise Exception('Editor world unavailable')
30
+ try:
31
+ ${indentedBody}
32
+ print('DEBUG_DRAW:' + json.dumps({'action': '${action}', 'params': payload}))
33
+ print('RESULT:' + json.dumps({'success': True, 'action': '${action}', 'params': payload}))
34
+ except Exception as e:
35
+ print('DEBUG_DRAW_ERROR:' + str(e))
36
+ print('RESULT:' + json.dumps({'success': False, 'action': '${action}', 'error': str(e)}))
31
37
  `.trim()
32
38
  .replace(/\r?\n/g, '\n');
39
+
33
40
  try {
34
- await this.bridge.executePython(script);
35
- return { success: true };
41
+ const response = await this.bridge.executePython(script);
42
+ let interpreted = interpretStandardResult(response, {
43
+ successMessage: `${action} executed`,
44
+ failureMessage: `${action} failed`
45
+ });
46
+
47
+ const parsed = parseStandardResult(response);
48
+ const parsedPayload = parsed.data ?? {};
49
+ const parsedSuccessValue = (parsedPayload as any).success;
50
+ const normalizedSuccess = typeof parsedSuccessValue === 'string'
51
+ ? ['true', '1', 'yes'].includes(parsedSuccessValue.toLowerCase())
52
+ : parsedSuccessValue === true;
53
+
54
+ if (!interpreted.success && normalizedSuccess) {
55
+ interpreted = {
56
+ ...interpreted,
57
+ success: true,
58
+ error: undefined,
59
+ message: interpreted.message || `${action} executed`,
60
+ payload: { ...parsedPayload }
61
+ };
62
+ }
63
+
64
+ const finalSuccess = interpreted.success || normalizedSuccess;
65
+
66
+ const resolvedAction = coerceString(interpreted.payload.action) ?? action;
67
+ const fallbackOutput = typeof parsed.text === 'string' ? parsed.text : '';
68
+ const rawOutput = bestEffortInterpretedText(interpreted) ?? (fallbackOutput ? fallbackOutput : undefined);
69
+
70
+ if (finalSuccess) {
71
+ return {
72
+ ...interpreted.payload,
73
+ success: true,
74
+ action: resolvedAction,
75
+ warnings: interpreted.warnings,
76
+ details: interpreted.details,
77
+ rawOutput,
78
+ raw: parsed.raw ?? interpreted.raw
79
+ };
80
+ }
81
+
82
+ return {
83
+ success: false,
84
+ action: resolvedAction,
85
+ error: interpreted.error ?? `${resolvedAction} failed`,
86
+ warnings: interpreted.warnings,
87
+ details: interpreted.details,
88
+ rawOutput,
89
+ raw: parsed.raw ?? interpreted.raw
90
+ };
36
91
  } catch (e) {
37
- return { success: false, error: String(e) };
92
+ return { success: false, error: String(e), action: meta?.action || 'debug_draw' };
38
93
  }
39
94
  }
40
95
 
@@ -58,7 +113,16 @@ end = unreal.Vector(${ex}, ${ey}, ${ez})
58
113
  color = unreal.LinearColor(${sr}/255.0, ${sg}/255.0, ${sb}/255.0, ${sa}/255.0)
59
114
  unreal.SystemLibrary.draw_debug_line(world, start, end, color, ${duration}, ${thickness})
60
115
  `;
61
- return this.pyDraw(script);
116
+ return this.pyDraw(script, {
117
+ action: 'debug_line',
118
+ params: {
119
+ start: params.start,
120
+ end: params.end,
121
+ color,
122
+ duration,
123
+ thickness
124
+ }
125
+ });
62
126
  }
63
127
 
64
128
  // Draw debug box using Python SystemLibrary
@@ -85,7 +149,17 @@ rot = unreal.Rotator(${rp}, ${ry}, ${rr})
85
149
  color = unreal.LinearColor(${cr}/255.0, ${cg}/255.0, ${cb}/255.0, ${ca}/255.0)
86
150
  unreal.SystemLibrary.draw_debug_box(world, center, extent, color, rot, ${duration}, ${thickness})
87
151
  `;
88
- return this.pyDraw(script);
152
+ return this.pyDraw(script, {
153
+ action: 'debug_box',
154
+ params: {
155
+ center: params.center,
156
+ extent: params.extent,
157
+ rotation,
158
+ color,
159
+ duration,
160
+ thickness
161
+ }
162
+ });
89
163
  }
90
164
 
91
165
  // Draw debug sphere using Python SystemLibrary
@@ -108,7 +182,17 @@ center = unreal.Vector(${cx}, ${cy}, ${cz})
108
182
  color = unreal.LinearColor(${cr}/255.0, ${cg}/255.0, ${cb}/255.0, ${ca}/255.0)
109
183
  unreal.SystemLibrary.draw_debug_sphere(world, center, ${params.radius}, ${segments}, color, ${duration}, ${thickness})
110
184
  `;
111
- return this.pyDraw(script);
185
+ return this.pyDraw(script, {
186
+ action: 'debug_sphere',
187
+ params: {
188
+ center: params.center,
189
+ radius: params.radius,
190
+ segments,
191
+ color,
192
+ duration,
193
+ thickness
194
+ }
195
+ });
112
196
  }
113
197
 
114
198
  // The rest keep console-command fallbacks or editor helpers as before
@@ -128,7 +212,17 @@ unreal.SystemLibrary.draw_debug_sphere(world, center, ${params.radius}, ${segmen
128
212
  const [rp, ry, rr] = rotation;
129
213
  const [cr, cg, cb, ca] = color;
130
214
  const script = `\ncenter = unreal.Vector(${cx}, ${cy}, ${cz})\nrot = unreal.Rotator(${rp}, ${ry}, ${rr})\ncolor = unreal.LinearColor(${cr}/255.0, ${cg}/255.0, ${cb}/255.0, ${ca}/255.0)\nunreal.SystemLibrary.draw_debug_capsule(world, center, ${params.halfHeight}, ${params.radius}, rot, color, ${duration}, 1.0)\n`;
131
- return this.pyDraw(script);
215
+ return this.pyDraw(script, {
216
+ action: 'debug_capsule',
217
+ params: {
218
+ center: params.center,
219
+ halfHeight: params.halfHeight,
220
+ radius: params.radius,
221
+ rotation,
222
+ color,
223
+ duration
224
+ }
225
+ });
132
226
  }
133
227
 
134
228
  async drawDebugCone(params: {
@@ -147,7 +241,19 @@ unreal.SystemLibrary.draw_debug_sphere(world, center, ${params.radius}, ${segmen
147
241
  const [dx, dy, dz] = params.direction;
148
242
  const [cr, cg, cb, ca] = color;
149
243
  const script = `\norigin = unreal.Vector(${ox}, ${oy}, ${oz})\ndir = unreal.Vector(${dx}, ${dy}, ${dz})\ncolor = unreal.LinearColor(${cr}/255.0, ${cg}/255.0, ${cb}/255.0, ${ca}/255.0)\nunreal.SystemLibrary.draw_debug_cone(world, origin, dir, ${params.length}, ${params.angleWidth}, ${params.angleHeight}, ${params.numSides || 12}, color, ${duration}, 1.0)\n`;
150
- return this.pyDraw(script);
244
+ return this.pyDraw(script, {
245
+ action: 'debug_cone',
246
+ params: {
247
+ origin: params.origin,
248
+ direction: params.direction,
249
+ length: params.length,
250
+ angleWidth: params.angleWidth,
251
+ angleHeight: params.angleHeight,
252
+ numSides: params.numSides || 12,
253
+ color,
254
+ duration
255
+ }
256
+ });
151
257
  }
152
258
 
153
259
  async drawDebugString(params: {
@@ -162,7 +268,16 @@ unreal.SystemLibrary.draw_debug_sphere(world, center, ${params.radius}, ${segmen
162
268
  const [x, y, z] = params.location;
163
269
  const [r, g, b, a] = color;
164
270
  const script = `\nloc = unreal.Vector(${x}, ${y}, ${z})\ncolor = unreal.LinearColor(${r}/255.0, ${g}/255.0, ${b}/255.0, ${a}/255.0)\nunreal.SystemLibrary.draw_debug_string(world, loc, "${params.text.replace(/"/g, '\\"')}", None, color, ${duration})\n`;
165
- return this.pyDraw(script);
271
+ return this.pyDraw(script, {
272
+ action: 'debug_string',
273
+ params: {
274
+ location: params.location,
275
+ text: params.text,
276
+ color,
277
+ duration,
278
+ fontSize: params.fontSize
279
+ }
280
+ });
166
281
  }
167
282
 
168
283
  async drawDebugArrow(params: {
@@ -180,7 +295,17 @@ unreal.SystemLibrary.draw_debug_sphere(world, center, ${params.radius}, ${segmen
180
295
  const [ex, ey, ez] = params.end;
181
296
  const [r, g, b, a] = color;
182
297
  const script = `\nstart = unreal.Vector(${sx}, ${sy}, ${sz})\nend = unreal.Vector(${ex}, ${ey}, ${ez})\ncolor = unreal.LinearColor(${r}/255.0, ${g}/255.0, ${b}/255.0, ${a}/255.0)\nunreal.SystemLibrary.draw_debug_arrow(world, start, end, ${params.arrowSize || 10.0}, color, ${duration}, ${thickness})\n`;
183
- return this.pyDraw(script);
298
+ return this.pyDraw(script, {
299
+ action: 'debug_arrow',
300
+ params: {
301
+ start: params.start,
302
+ end: params.end,
303
+ arrowSize: params.arrowSize || 10.0,
304
+ color,
305
+ duration,
306
+ thickness
307
+ }
308
+ });
184
309
  }
185
310
 
186
311
  async drawDebugPoint(params: {
@@ -195,7 +320,15 @@ unreal.SystemLibrary.draw_debug_sphere(world, center, ${params.radius}, ${segmen
195
320
  const [x, y, z] = params.location;
196
321
  const [r, g, b, a] = color;
197
322
  const script = `\nloc = unreal.Vector(${x}, ${y}, ${z})\ncolor = unreal.LinearColor(${r}/255.0, ${g}/255.0, ${b}/255.0, ${a}/255.0)\nunreal.SystemLibrary.draw_debug_point(world, loc, ${size}, color, ${duration})\n`;
198
- return this.pyDraw(script);
323
+ return this.pyDraw(script, {
324
+ action: 'debug_point',
325
+ params: {
326
+ location: params.location,
327
+ size,
328
+ color,
329
+ duration
330
+ }
331
+ });
199
332
  }
200
333
 
201
334
  async drawDebugCoordinateSystem(params: {
@@ -232,7 +365,19 @@ unreal.SystemLibrary.draw_debug_sphere(world, center, ${params.radius}, ${segmen
232
365
  const [rp, ry, rr] = params.rotation;
233
366
  const [r, g, b, a] = color;
234
367
  const script = `\norigin = unreal.Vector(${ox}, ${oy}, ${oz})\nrot = unreal.Rotator(${rp}, ${ry}, ${rr})\ncolor = unreal.LinearColor(${r}/255.0, ${g}/255.0, ${b}/255.0, ${a}/255.0)\nunreal.SystemLibrary.draw_debug_frustum(world, origin, rot, ${params.fov}, ${aspectRatio}, ${nearPlane}, ${farPlane}, color, ${duration})\n`;
235
- return this.pyDraw(script);
368
+ return this.pyDraw(script, {
369
+ action: 'debug_frustum',
370
+ params: {
371
+ origin: params.origin,
372
+ rotation: params.rotation,
373
+ fov: params.fov,
374
+ aspectRatio,
375
+ nearPlane,
376
+ farPlane,
377
+ color,
378
+ duration
379
+ }
380
+ });
236
381
  }
237
382
 
238
383
  async clearDebugDrawings() {
@@ -243,16 +388,14 @@ unreal.SystemLibrary.draw_debug_sphere(world, center, ${params.radius}, ${segmen
243
388
  enabled: boolean;
244
389
  type?: 'Simple' | 'Complex' | 'Both';
245
390
  }) {
246
- const commands = [];
391
+ const commands: string[] = [];
247
392
  if (params.enabled) {
248
393
  const typeCmd = params.type === 'Simple' ? '1' : params.type === 'Complex' ? '2' : '3';
249
394
  commands.push(`show Collision ${typeCmd}`);
250
395
  } else {
251
396
  commands.push('show Collision 0');
252
397
  }
253
- for (const cmd of commands) {
254
- await this.bridge.executeConsoleCommand(cmd);
255
- }
398
+ await this.bridge.executeConsoleCommands(commands);
256
399
  return { success: true, message: `Collision visualization ${params.enabled ? 'enabled' : 'disabled'}` };
257
400
  }
258
401
 
@@ -1,5 +1,6 @@
1
1
  import { UnrealBridge } from '../unreal-bridge.js';
2
2
  import { toVec3Object, toRotObject } from '../utils/normalize.js';
3
+ import { bestEffortInterpretedText, coerceString, interpretStandardResult } from '../utils/result-helpers.js';
3
4
 
4
5
  export class EditorTools {
5
6
  constructor(private bridge: UnrealBridge) {}
@@ -69,26 +70,13 @@ else:
69
70
  `.trim();
70
71
 
71
72
  const resp: any = await this.bridge.executePython(pythonCmd);
72
- const out = typeof resp === 'string' ? resp : JSON.stringify(resp);
73
- const m = out.match(/RESULT:({.*})/);
74
- if (m) {
75
- try {
76
- const parsed = JSON.parse(m[1]);
77
- if (parsed.success) {
78
- const method = parsed.method || 'LevelEditorSubsystem';
79
- return { success: true, message: `PIE started (via ${method})` };
80
- }
81
- } catch {
82
- try {
83
- // Fallback: handle non-JSON python dict-style output
84
- const sanitized = m[1].replace(/'/g, '"').replace(/\bTrue\b/g, 'true').replace(/\bFalse\b/g, 'false');
85
- const parsed = JSON.parse(sanitized);
86
- if (parsed.success) {
87
- const method = parsed.method || 'LevelEditorSubsystem';
88
- return { success: true, message: `PIE started (via ${method})` };
89
- }
90
- } catch {}
91
- }
73
+ const interpreted = interpretStandardResult(resp, {
74
+ successMessage: 'PIE started',
75
+ failureMessage: 'Failed to start PIE'
76
+ });
77
+ if (interpreted.success) {
78
+ const method = coerceString(interpreted.payload.method) ?? 'LevelEditorSubsystem';
79
+ return { success: true, message: `PIE started (via ${method})` };
92
80
  }
93
81
  // If not verified, fall through to fallback
94
82
  } catch (err) {
@@ -129,19 +117,21 @@ else:
129
117
  print('RESULT:' + json.dumps({'success': False, 'error': 'LevelEditorSubsystem not available'}))
130
118
  `.trim();
131
119
  const resp: any = await this.bridge.executePython(pythonCmd);
132
- const out = typeof resp === 'string' ? resp : JSON.stringify(resp);
133
- const m = out.match(/RESULT:({.*})/);
134
- if (m) {
135
- try {
136
- const parsed = JSON.parse(m[1].replace(/'/g, '"'));
137
- if (parsed.success) {
138
- const method = parsed.method || 'LevelEditorSubsystem';
139
- return { success: true, message: `PIE stopped via ${method}` };
140
- }
141
- } catch {}
120
+ const interpreted = interpretStandardResult(resp, {
121
+ successMessage: 'PIE stopped successfully',
122
+ failureMessage: 'Failed to stop PIE'
123
+ });
124
+
125
+ if (interpreted.success) {
126
+ const method = coerceString(interpreted.payload.method) ?? 'LevelEditorSubsystem';
127
+ return { success: true, message: `PIE stopped via ${method}` };
128
+ }
129
+
130
+ if (interpreted.error) {
131
+ return { success: false, error: interpreted.error };
142
132
  }
143
- // Default success message if parsing fails
144
- return { success: true, message: 'PIE stopped successfully' };
133
+
134
+ return { success: false, error: 'Failed to stop PIE' };
145
135
  } catch {
146
136
  // Fallback to console command
147
137
  await this.bridge.executeConsoleCommand('stop');
@@ -196,12 +186,20 @@ except Exception as e:
196
186
  print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
197
187
  `.trim();
198
188
  const resp: any = await this.bridge.executePython(py);
199
- const out = typeof resp === 'string' ? resp : JSON.stringify(resp);
200
- const m = out.match(/RESULT:({.*})/);
201
- if (m) {
202
- try { const parsed = JSON.parse(m[1].replace(/'/g, '"')); return parsed.success ? { success: true, message: parsed.message } : { success: false, error: parsed.error }; } catch {}
189
+ const interpreted = interpretStandardResult(resp, {
190
+ successMessage: 'Lighting build started',
191
+ failureMessage: 'Failed to build lighting'
192
+ });
193
+
194
+ if (interpreted.success) {
195
+ return { success: true, message: interpreted.message };
203
196
  }
204
- return { success: true, message: 'Lighting build started' };
197
+
198
+ return {
199
+ success: false,
200
+ error: interpreted.error ?? 'Failed to build lighting',
201
+ details: bestEffortInterpretedText(interpreted)
202
+ };
205
203
  } catch (err) {
206
204
  return { success: false, error: `Failed to build lighting: ${err}` };
207
205
  }