unreal-engine-mcp-server 0.3.1 → 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 (144) 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 +22 -7
  5. package/dist/index.js +137 -46
  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.d.ts +3 -2
  11. package/dist/resources/assets.js +117 -109
  12. package/dist/resources/levels.d.ts +21 -3
  13. package/dist/resources/levels.js +31 -56
  14. package/dist/tools/actors.d.ts +3 -14
  15. package/dist/tools/actors.js +246 -302
  16. package/dist/tools/animation.d.ts +57 -102
  17. package/dist/tools/animation.js +429 -450
  18. package/dist/tools/assets.d.ts +13 -2
  19. package/dist/tools/assets.js +58 -46
  20. package/dist/tools/audio.d.ts +22 -13
  21. package/dist/tools/audio.js +467 -121
  22. package/dist/tools/blueprint.d.ts +32 -13
  23. package/dist/tools/blueprint.js +699 -448
  24. package/dist/tools/build_environment_advanced.d.ts +0 -1
  25. package/dist/tools/build_environment_advanced.js +236 -87
  26. package/dist/tools/consolidated-tool-definitions.d.ts +232 -15
  27. package/dist/tools/consolidated-tool-definitions.js +124 -255
  28. package/dist/tools/consolidated-tool-handlers.js +749 -766
  29. package/dist/tools/debug.d.ts +72 -10
  30. package/dist/tools/debug.js +170 -36
  31. package/dist/tools/editor.d.ts +9 -2
  32. package/dist/tools/editor.js +30 -44
  33. package/dist/tools/foliage.d.ts +34 -15
  34. package/dist/tools/foliage.js +97 -107
  35. package/dist/tools/introspection.js +19 -21
  36. package/dist/tools/landscape.d.ts +1 -2
  37. package/dist/tools/landscape.js +311 -168
  38. package/dist/tools/level.d.ts +3 -28
  39. package/dist/tools/level.js +642 -192
  40. package/dist/tools/lighting.d.ts +14 -3
  41. package/dist/tools/lighting.js +236 -123
  42. package/dist/tools/materials.d.ts +25 -7
  43. package/dist/tools/materials.js +102 -79
  44. package/dist/tools/niagara.d.ts +10 -12
  45. package/dist/tools/niagara.js +74 -94
  46. package/dist/tools/performance.d.ts +12 -4
  47. package/dist/tools/performance.js +38 -79
  48. package/dist/tools/physics.d.ts +34 -10
  49. package/dist/tools/physics.js +364 -292
  50. package/dist/tools/rc.js +98 -24
  51. package/dist/tools/sequence.d.ts +1 -0
  52. package/dist/tools/sequence.js +146 -24
  53. package/dist/tools/ui.d.ts +31 -4
  54. package/dist/tools/ui.js +83 -66
  55. package/dist/tools/visual.d.ts +11 -0
  56. package/dist/tools/visual.js +245 -30
  57. package/dist/types/tool-types.d.ts +0 -6
  58. package/dist/types/tool-types.js +1 -8
  59. package/dist/unreal-bridge.d.ts +32 -2
  60. package/dist/unreal-bridge.js +621 -127
  61. package/dist/utils/elicitation.d.ts +57 -0
  62. package/dist/utils/elicitation.js +104 -0
  63. package/dist/utils/error-handler.d.ts +0 -33
  64. package/dist/utils/error-handler.js +4 -111
  65. package/dist/utils/http.d.ts +2 -22
  66. package/dist/utils/http.js +12 -75
  67. package/dist/utils/normalize.d.ts +4 -4
  68. package/dist/utils/normalize.js +15 -7
  69. package/dist/utils/python-output.d.ts +18 -0
  70. package/dist/utils/python-output.js +290 -0
  71. package/dist/utils/python.d.ts +2 -0
  72. package/dist/utils/python.js +4 -0
  73. package/dist/utils/response-validator.d.ts +6 -1
  74. package/dist/utils/response-validator.js +66 -13
  75. package/dist/utils/result-helpers.d.ts +27 -0
  76. package/dist/utils/result-helpers.js +147 -0
  77. package/dist/utils/safe-json.d.ts +0 -2
  78. package/dist/utils/safe-json.js +0 -43
  79. package/dist/utils/validation.d.ts +16 -0
  80. package/dist/utils/validation.js +70 -7
  81. package/mcp-config-example.json +2 -2
  82. package/package.json +11 -10
  83. package/server.json +37 -14
  84. package/src/index.ts +146 -50
  85. package/src/prompts/index.ts +211 -13
  86. package/src/resources/actors.ts +59 -44
  87. package/src/resources/assets.ts +123 -102
  88. package/src/resources/levels.ts +37 -47
  89. package/src/tools/actors.ts +269 -313
  90. package/src/tools/animation.ts +556 -539
  91. package/src/tools/assets.ts +59 -45
  92. package/src/tools/audio.ts +507 -113
  93. package/src/tools/blueprint.ts +778 -462
  94. package/src/tools/build_environment_advanced.ts +312 -106
  95. package/src/tools/consolidated-tool-definitions.ts +136 -267
  96. package/src/tools/consolidated-tool-handlers.ts +871 -795
  97. package/src/tools/debug.ts +179 -38
  98. package/src/tools/editor.ts +35 -37
  99. package/src/tools/foliage.ts +110 -104
  100. package/src/tools/introspection.ts +24 -22
  101. package/src/tools/landscape.ts +334 -181
  102. package/src/tools/level.ts +683 -182
  103. package/src/tools/lighting.ts +244 -123
  104. package/src/tools/materials.ts +114 -83
  105. package/src/tools/niagara.ts +87 -81
  106. package/src/tools/performance.ts +49 -88
  107. package/src/tools/physics.ts +393 -299
  108. package/src/tools/rc.ts +103 -25
  109. package/src/tools/sequence.ts +157 -30
  110. package/src/tools/ui.ts +101 -70
  111. package/src/tools/visual.ts +250 -29
  112. package/src/types/tool-types.ts +0 -9
  113. package/src/unreal-bridge.ts +658 -140
  114. package/src/utils/elicitation.ts +129 -0
  115. package/src/utils/error-handler.ts +4 -159
  116. package/src/utils/http.ts +16 -115
  117. package/src/utils/normalize.ts +20 -10
  118. package/src/utils/python-output.ts +351 -0
  119. package/src/utils/python.ts +3 -0
  120. package/src/utils/response-validator.ts +68 -17
  121. package/src/utils/result-helpers.ts +193 -0
  122. package/src/utils/safe-json.ts +0 -50
  123. package/src/utils/validation.ts +94 -7
  124. package/tests/run-unreal-tool-tests.mjs +720 -0
  125. package/tsconfig.json +2 -2
  126. package/dist/python-utils.d.ts +0 -29
  127. package/dist/python-utils.js +0 -54
  128. package/dist/tools/tool-definitions.d.ts +0 -4919
  129. package/dist/tools/tool-definitions.js +0 -1065
  130. package/dist/tools/tool-handlers.d.ts +0 -47
  131. package/dist/tools/tool-handlers.js +0 -863
  132. package/dist/types/index.d.ts +0 -323
  133. package/dist/types/index.js +0 -28
  134. package/dist/utils/cache-manager.d.ts +0 -64
  135. package/dist/utils/cache-manager.js +0 -176
  136. package/dist/utils/errors.d.ts +0 -133
  137. package/dist/utils/errors.js +0 -256
  138. package/src/python/editor_compat.py +0 -181
  139. package/src/python-utils.ts +0 -57
  140. package/src/tools/tool-definitions.ts +0 -1081
  141. package/src/tools/tool-handlers.ts +0 -973
  142. package/src/types/index.ts +0 -414
  143. package/src/utils/cache-manager.ts +0 -213
  144. package/src/utils/errors.ts +0 -312
@@ -1,42 +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
- # Use modern UnrealEditorSubsystem instead of deprecated EditorLevelLibrary
21
+ import json
22
+
23
+ payload = json.loads('${payloadJson}')
26
24
  ues = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem)
27
- if ues:
28
- world = ues.get_editor_world()
29
- else:
30
- # Fallback to deprecated API if subsystem not available
31
- world = unreal.EditorLevelLibrary.get_editor_world()
32
- ${scriptBody}
25
+ if not ues:
26
+ raise Exception('UnrealEditorSubsystem not available')
27
+ world = ues.get_editor_world()
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)}))
33
37
  `.trim()
34
38
  .replace(/\r?\n/g, '\n');
39
+
35
40
  try {
36
- await this.bridge.executePython(script);
37
- 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
+ };
38
91
  } catch (e) {
39
- return { success: false, error: String(e) };
92
+ return { success: false, error: String(e), action: meta?.action || 'debug_draw' };
40
93
  }
41
94
  }
42
95
 
@@ -60,7 +113,16 @@ end = unreal.Vector(${ex}, ${ey}, ${ez})
60
113
  color = unreal.LinearColor(${sr}/255.0, ${sg}/255.0, ${sb}/255.0, ${sa}/255.0)
61
114
  unreal.SystemLibrary.draw_debug_line(world, start, end, color, ${duration}, ${thickness})
62
115
  `;
63
- 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
+ });
64
126
  }
65
127
 
66
128
  // Draw debug box using Python SystemLibrary
@@ -87,7 +149,17 @@ rot = unreal.Rotator(${rp}, ${ry}, ${rr})
87
149
  color = unreal.LinearColor(${cr}/255.0, ${cg}/255.0, ${cb}/255.0, ${ca}/255.0)
88
150
  unreal.SystemLibrary.draw_debug_box(world, center, extent, color, rot, ${duration}, ${thickness})
89
151
  `;
90
- 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
+ });
91
163
  }
92
164
 
93
165
  // Draw debug sphere using Python SystemLibrary
@@ -110,7 +182,17 @@ center = unreal.Vector(${cx}, ${cy}, ${cz})
110
182
  color = unreal.LinearColor(${cr}/255.0, ${cg}/255.0, ${cb}/255.0, ${ca}/255.0)
111
183
  unreal.SystemLibrary.draw_debug_sphere(world, center, ${params.radius}, ${segments}, color, ${duration}, ${thickness})
112
184
  `;
113
- 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
+ });
114
196
  }
115
197
 
116
198
  // The rest keep console-command fallbacks or editor helpers as before
@@ -130,7 +212,17 @@ unreal.SystemLibrary.draw_debug_sphere(world, center, ${params.radius}, ${segmen
130
212
  const [rp, ry, rr] = rotation;
131
213
  const [cr, cg, cb, ca] = color;
132
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`;
133
- 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
+ });
134
226
  }
135
227
 
136
228
  async drawDebugCone(params: {
@@ -149,7 +241,19 @@ unreal.SystemLibrary.draw_debug_sphere(world, center, ${params.radius}, ${segmen
149
241
  const [dx, dy, dz] = params.direction;
150
242
  const [cr, cg, cb, ca] = color;
151
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`;
152
- 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
+ });
153
257
  }
154
258
 
155
259
  async drawDebugString(params: {
@@ -164,7 +268,16 @@ unreal.SystemLibrary.draw_debug_sphere(world, center, ${params.radius}, ${segmen
164
268
  const [x, y, z] = params.location;
165
269
  const [r, g, b, a] = color;
166
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`;
167
- 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
+ });
168
281
  }
169
282
 
170
283
  async drawDebugArrow(params: {
@@ -182,7 +295,17 @@ unreal.SystemLibrary.draw_debug_sphere(world, center, ${params.radius}, ${segmen
182
295
  const [ex, ey, ez] = params.end;
183
296
  const [r, g, b, a] = color;
184
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`;
185
- 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
+ });
186
309
  }
187
310
 
188
311
  async drawDebugPoint(params: {
@@ -197,7 +320,15 @@ unreal.SystemLibrary.draw_debug_sphere(world, center, ${params.radius}, ${segmen
197
320
  const [x, y, z] = params.location;
198
321
  const [r, g, b, a] = color;
199
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`;
200
- 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
+ });
201
332
  }
202
333
 
203
334
  async drawDebugCoordinateSystem(params: {
@@ -234,7 +365,19 @@ unreal.SystemLibrary.draw_debug_sphere(world, center, ${params.radius}, ${segmen
234
365
  const [rp, ry, rr] = params.rotation;
235
366
  const [r, g, b, a] = color;
236
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`;
237
- 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
+ });
238
381
  }
239
382
 
240
383
  async clearDebugDrawings() {
@@ -245,16 +388,14 @@ unreal.SystemLibrary.draw_debug_sphere(world, center, ${params.radius}, ${segmen
245
388
  enabled: boolean;
246
389
  type?: 'Simple' | 'Complex' | 'Both';
247
390
  }) {
248
- const commands = [];
391
+ const commands: string[] = [];
249
392
  if (params.enabled) {
250
393
  const typeCmd = params.type === 'Simple' ? '1' : params.type === 'Complex' ? '2' : '3';
251
394
  commands.push(`show Collision ${typeCmd}`);
252
395
  } else {
253
396
  commands.push('show Collision 0');
254
397
  }
255
- for (const cmd of commands) {
256
- await this.bridge.executeConsoleCommand(cmd);
257
- }
398
+ await this.bridge.executeConsoleCommands(commands);
258
399
  return { success: true, message: `Collision visualization ${params.enabled ? 'enabled' : 'disabled'}` };
259
400
  }
260
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
  }