ue-mcp 1.2.3 → 1.3.0

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 (255) hide show
  1. package/README.md +4 -3
  2. package/dist/action-class.js +56 -3
  3. package/dist/action-class.js.map +1 -1
  4. package/dist/bridge-timeouts.d.ts +64 -0
  5. package/dist/bridge-timeouts.js +118 -0
  6. package/dist/bridge-timeouts.js.map +1 -0
  7. package/dist/bridge.js +10 -2
  8. package/dist/bridge.js.map +1 -1
  9. package/dist/editor-control.d.ts +72 -5
  10. package/dist/editor-control.js +164 -140
  11. package/dist/editor-control.js.map +1 -1
  12. package/dist/editor-target.d.ts +55 -1
  13. package/dist/editor-target.js +107 -10
  14. package/dist/editor-target.js.map +1 -1
  15. package/dist/engine-observer.d.ts +59 -5
  16. package/dist/engine-observer.js +249 -29
  17. package/dist/engine-observer.js.map +1 -1
  18. package/dist/engine-root.d.ts +91 -0
  19. package/dist/engine-root.js +448 -0
  20. package/dist/engine-root.js.map +1 -0
  21. package/dist/epic-enrich.js +16 -1
  22. package/dist/epic-enrich.js.map +1 -1
  23. package/dist/feedback-cli.js +4 -0
  24. package/dist/feedback-cli.js.map +1 -1
  25. package/dist/feedback-deferred.d.ts +9 -0
  26. package/dist/feedback-deferred.js +2 -0
  27. package/dist/feedback-deferred.js.map +1 -1
  28. package/dist/feedback-fallback.d.ts +99 -0
  29. package/dist/feedback-fallback.js +173 -0
  30. package/dist/feedback-fallback.js.map +1 -0
  31. package/dist/flow/write-methods.js +20 -0
  32. package/dist/flow/write-methods.js.map +1 -1
  33. package/dist/index.js +41 -2
  34. package/dist/index.js.map +1 -1
  35. package/dist/lean-context.js +8 -3
  36. package/dist/lean-context.js.map +1 -1
  37. package/dist/locking.js +11 -2
  38. package/dist/locking.js.map +1 -1
  39. package/dist/plugin/loader.d.ts +4 -0
  40. package/dist/plugin/loader.js +22 -8
  41. package/dist/plugin/loader.js.map +1 -1
  42. package/dist/plugin/manifest.d.ts +72 -52
  43. package/dist/plugin/manifest.js +118 -21
  44. package/dist/plugin/manifest.js.map +1 -1
  45. package/dist/plugin-cli.js +16 -3
  46. package/dist/plugin-cli.js.map +1 -1
  47. package/dist/project.d.ts +11 -0
  48. package/dist/project.js +23 -2
  49. package/dist/project.js.map +1 -1
  50. package/dist/python-gate.d.ts +95 -0
  51. package/dist/python-gate.js +175 -0
  52. package/dist/python-gate.js.map +1 -0
  53. package/dist/tool-counts.json +16 -16
  54. package/dist/tools/animation.js +288 -24
  55. package/dist/tools/animation.js.map +1 -1
  56. package/dist/tools/asset.js +79 -14
  57. package/dist/tools/asset.js.map +1 -1
  58. package/dist/tools/audio.js +59 -5
  59. package/dist/tools/audio.js.map +1 -1
  60. package/dist/tools/blueprint.js +29 -12
  61. package/dist/tools/blueprint.js.map +1 -1
  62. package/dist/tools/chooser.js +4 -1
  63. package/dist/tools/chooser.js.map +1 -1
  64. package/dist/tools/editor.js +81 -43
  65. package/dist/tools/editor.js.map +1 -1
  66. package/dist/tools/feedback.js +214 -48
  67. package/dist/tools/feedback.js.map +1 -1
  68. package/dist/tools/foliage.js +27 -1
  69. package/dist/tools/foliage.js.map +1 -1
  70. package/dist/tools/gameplay.js +38 -11
  71. package/dist/tools/gameplay.js.map +1 -1
  72. package/dist/tools/gas.js +17 -5
  73. package/dist/tools/gas.js.map +1 -1
  74. package/dist/tools/landscape.js +22 -7
  75. package/dist/tools/landscape.js.map +1 -1
  76. package/dist/tools/level.js +322 -51
  77. package/dist/tools/level.js.map +1 -1
  78. package/dist/tools/material.js +10 -4
  79. package/dist/tools/material.js.map +1 -1
  80. package/dist/tools/niagara.js +3 -2
  81. package/dist/tools/niagara.js.map +1 -1
  82. package/dist/tools/pcg.js +6 -5
  83. package/dist/tools/pcg.js.map +1 -1
  84. package/dist/tools/plugins.js +2 -0
  85. package/dist/tools/plugins.js.map +1 -1
  86. package/dist/tools/project.js +47 -20
  87. package/dist/tools/project.js.map +1 -1
  88. package/dist/types.d.ts +24 -0
  89. package/dist/types.js +46 -3
  90. package/dist/types.js.map +1 -1
  91. package/dist/ue-mcp.default.yml +493 -125
  92. package/package.json +2 -2
  93. package/plugin/ue_mcp_bridge/NATIVE_RUNTIME_VISIBILITY.md +53 -0
  94. package/plugin/ue_mcp_bridge/NATIVE_STATIC_MESH_USAGE_PROGRESS.md +115 -0
  95. package/plugin/ue_mcp_bridge/README_MassHandlers.md +97 -0
  96. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/BridgeServer.cpp +14 -0
  97. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.cpp +21 -4
  98. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.h +9 -0
  99. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerJsonProperty.h +395 -69
  100. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.cpp +79 -3
  101. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.h +25 -0
  102. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_ControlRig.cpp +3 -3
  103. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_ControlRigSequencer.cpp +3061 -0
  104. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_IKRetargeterAuthoring.cpp +961 -0
  105. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_IKRigAuthoring.cpp +900 -0
  106. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Pose.cpp +769 -0
  107. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Sequence.cpp +44 -3
  108. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_SkeletalLive.cpp +217 -11
  109. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_StateMachine.cpp +551 -24
  110. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Validation.cpp +735 -0
  111. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.cpp +352 -73
  112. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.h +8 -0
  113. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkProperties.cpp +1 -1
  114. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkRead.cpp +503 -0
  115. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkRead.h +21 -0
  116. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkUpsert.cpp +2 -2
  117. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Enum.cpp +2 -2
  118. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Geometry.cpp +902 -0
  119. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Geometry.h +33 -0
  120. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Import.cpp +478 -134
  121. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Mesh.cpp +5 -5
  122. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_MeshBoolean.cpp +933 -0
  123. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_MeshBoolean.h +35 -0
  124. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Redirectors.cpp +478 -0
  125. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_RenderTarget.cpp +1 -1
  126. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Sockets.cpp +4 -4
  127. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Struct.cpp +2 -2
  128. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Subobject.cpp +319 -0
  129. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AudioHandlers_SoundCue.cpp +3 -3
  130. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.cpp +482 -35
  131. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.h +10 -0
  132. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Collision.cpp +502 -0
  133. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Collision.h +43 -0
  134. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Functions.cpp +89 -7
  135. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Graph.cpp +17 -51
  136. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Internal.h +107 -0
  137. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Properties.cpp +10 -10
  138. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Search.cpp +648 -0
  139. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DemoHandlers.cpp +5 -1
  140. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DiffHandlers.cpp +252 -1
  141. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DiffHandlers.h +10 -2
  142. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.cpp +252 -41
  143. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.h +4 -0
  144. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_Build.cpp +250 -27
  145. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_PIE.cpp +303 -125
  146. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_PIERuntime.cpp +73 -50
  147. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_RuntimeVisibility.cpp +757 -0
  148. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.cpp +11 -2
  149. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.h +3 -0
  150. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers_Batch.cpp +419 -0
  151. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.cpp +39 -161
  152. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.h +66 -1
  153. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_BTAuthoring.cpp +1323 -0
  154. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_BehaviorTree.cpp +1211 -0
  155. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.cpp +2 -0
  156. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.h +60 -0
  157. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers_Runtime.cpp +363 -12
  158. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/HandlerEditorState.h +84 -0
  159. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.cpp +1288 -36
  160. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.h +3 -0
  161. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers_Sculpt.cpp +41 -16
  162. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.cpp +959 -213
  163. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.h +66 -0
  164. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_BatchWrite.cpp +1571 -0
  165. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Convert.cpp +316 -0
  166. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Inspection.cpp +280 -0
  167. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_InstanceProjection.cpp +666 -0
  168. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_InstanceProjection_Internal.h +63 -0
  169. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Lights.cpp +43 -12
  170. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_MultiLevel.cpp +13 -47
  171. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_PostProcess.cpp +527 -0
  172. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Query.cpp +1169 -0
  173. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Refresh.cpp +723 -0
  174. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Save.cpp +325 -0
  175. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Spatial.cpp +360 -0
  176. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Trace.cpp +178 -58
  177. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Transient.cpp +337 -0
  178. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Volumes.cpp +25 -12
  179. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_WorldPartition.cpp +10 -9
  180. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_WorldPartitionSettings.cpp +686 -0
  181. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MassHandlers.cpp +467 -0
  182. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MassHandlers.h +22 -0
  183. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.cpp +452 -175
  184. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.h +5 -0
  185. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers_Build.cpp +597 -0
  186. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NiagaraHandlers.cpp +17 -15
  187. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PCGHandlers.cpp +38 -168
  188. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PhysicsHandlers.cpp +39 -30
  189. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.cpp +392 -78
  190. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.h +2 -0
  191. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers_Internal.h +83 -0
  192. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SkeletalMeshHandlers.cpp +200 -0
  193. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SkeletalMeshHandlers.h +15 -0
  194. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.cpp +19 -12
  195. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/StateTreeHandlers.cpp +31 -1
  196. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.cpp +307 -69
  197. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.h +84 -0
  198. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers_Extraction.cpp +8 -41
  199. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers_Properties.cpp +33 -52
  200. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/ActorSelectorTests.cpp +344 -0
  201. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AnimationControlRigTimelineTests.cpp +60 -0
  202. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AnimationPoseTests.cpp +115 -0
  203. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetBulkReadTests.cpp +177 -0
  204. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetDeleteGuardTests.cpp +276 -0
  205. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetPathResolutionTests.cpp +243 -0
  206. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetPropertyPersistTests.cpp +213 -0
  207. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetSubobjectTests.cpp +235 -0
  208. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AutomationRunnerTests.cpp +72 -0
  209. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BehaviorTreeAuthoringTests.cpp +526 -0
  210. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BehaviorTreeNodeTests.cpp +334 -0
  211. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BlueprintHandlerSurfaceTests.cpp +362 -0
  212. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BridgeProtocolTests.cpp +14 -0
  213. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/CollisionQueryTests.cpp +288 -0
  214. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/DataTableRowWriteTests.cpp +453 -0
  215. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/EditorFunctionCallTests.cpp +223 -0
  216. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/FoliageBatchTests.cpp +147 -0
  217. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/GameThreadGateTests.cpp +82 -0
  218. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/GasLiveAttributeTests.cpp +183 -0
  219. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LandscapeSampleTests.cpp +119 -0
  220. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelBatchWriteTests.cpp +176 -0
  221. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelComponentTreeInstanceCountTests.cpp +189 -0
  222. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelConvertBrushesTests.cpp +100 -0
  223. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelInstanceProjectionTests.cpp +265 -0
  224. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelPostProcessOverrideTests.cpp +258 -0
  225. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelQueryComponentsTests.cpp +195 -0
  226. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelRefreshTests.cpp +142 -0
  227. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelSaveReportTests.cpp +125 -0
  228. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelScriptHandlersTests.cpp +42 -0
  229. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelStaticMeshUsageTests.cpp +412 -0
  230. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelTraceWorldScopeTests.cpp +133 -0
  231. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelTransientActorTests.cpp +205 -0
  232. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelWorldPartitionSettingsTests.cpp +168 -0
  233. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MCPInstancedStructPathTestTypes.h +51 -0
  234. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MCPInstancedStructPathTests.cpp +379 -0
  235. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MaterialBuildTests.cpp +238 -0
  236. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MeshBooleanTests.cpp +170 -0
  237. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MeshGeometryTests.cpp +116 -0
  238. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/PackageSaveExtensionTests.cpp +161 -0
  239. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/PackageWriteGuardTests.cpp +253 -0
  240. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/RuntimeVisibilityTests.cpp +143 -0
  241. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/SequencerHandlerTests.cpp +660 -0
  242. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/SequencerScrubTests.cpp +50 -0
  243. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/SkeletonDiffTests.cpp +685 -0
  244. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/WidgetBlueprintResolveTests.cpp +145 -0
  245. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/UE_MCP_Bridge.cpp +80 -0
  246. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/HandlerFunctionCall.h +509 -0
  247. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/HandlerQuery.h +407 -0
  248. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/HandlerUtils.h +1008 -77
  249. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/UE_MCP_Bridge.Build.cs +87 -0
  250. package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Private/MCPEngineStatus.cpp +152 -14
  251. package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Private/Tests/MCPEngineStatusTests.cpp +65 -0
  252. package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Public/MCPEngineStatus.h +32 -1
  253. package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/UE_MCP_BridgeStatus.Build.cs +2 -0
  254. package/skills/ue-mcp-animation/SKILL.md +154 -0
  255. package/skills/ue-mcp-blueprint/SKILL.md +71 -0
@@ -0,0 +1,723 @@
1
+ // Refresh and inspect actions for state the editor caches (#944, #915, #914).
2
+ //
3
+ // Each of these is a case where the editor is holding a stale answer and there
4
+ // was no way to ask it to recompute, or no way to see the number it was
5
+ // actually using.
6
+ //
7
+ // #944 A placed Blueprint instance keeps the components and defaults it was
8
+ // constructed with. Changing the SCS or the construction script does
9
+ // not touch it. blueprint(run_construction_script) only runs against a
10
+ // TEMPORARY actor, and Python cannot help because
11
+ // Actor.rerun_construction_scripts() is missing from the wrapper.
12
+ // #915 Collision built from mesh data the mesh no longer has produced a
13
+ // line_trace that reported a MISS through solid geometry. A false
14
+ // negative is the worst answer a trace can give, because nothing about
15
+ // it looks wrong.
16
+ // #914 A UBoxComponent's authored BoxExtent was not readable anywhere, only
17
+ // its scaled bounds, and there was no way to ask whether two
18
+ // components actually overlap without a physics query that depends on
19
+ // the collision state #915 is about.
20
+
21
+ #include "LevelHandlers.h"
22
+
23
+ #include "Components/ActorComponent.h"
24
+ #include "Components/BoxComponent.h"
25
+ #include "Components/PrimitiveComponent.h"
26
+ #include "Components/SceneComponent.h"
27
+ #include "Dom/JsonObject.h"
28
+ #include "Dom/JsonValue.h"
29
+ #include "Editor.h"
30
+ #include "Engine/EngineTypes.h"
31
+ #include "Engine/World.h"
32
+ #include "EngineUtils.h"
33
+ #include "GameFramework/Actor.h"
34
+ #include "HandlerRegistry.h"
35
+ #include "HandlerUtils.h"
36
+
37
+ namespace
38
+ {
39
+ constexpr int32 MCPRefreshMaxActors = 5000;
40
+ constexpr int32 MCPRefreshMaxResultRows = 500;
41
+ constexpr int32 MCPRefreshDefaultMaxComponents = 2000;
42
+ constexpr int32 MCPRefreshHardMaxComponents = 20000;
43
+ constexpr int32 MCPRefreshMaxSamples = 25;
44
+
45
+ const TCHAR* MCPRefreshCollisionName(ECollisionEnabled::Type Value)
46
+ {
47
+ switch (Value)
48
+ {
49
+ case ECollisionEnabled::NoCollision: return TEXT("NoCollision");
50
+ case ECollisionEnabled::QueryOnly: return TEXT("QueryOnly");
51
+ case ECollisionEnabled::PhysicsOnly: return TEXT("PhysicsOnly");
52
+ case ECollisionEnabled::QueryAndPhysics: return TEXT("QueryAndPhysics");
53
+ case ECollisionEnabled::ProbeOnly: return TEXT("ProbeOnly");
54
+ case ECollisionEnabled::QueryAndProbe: return TEXT("QueryAndProbe");
55
+ default: return TEXT("Unknown");
56
+ }
57
+ }
58
+
59
+ /** An oriented box in world space, built from a component's LOCAL bounds so
60
+ * it keeps the component's orientation. The world bounds are axis-aligned
61
+ * and would answer a different question. */
62
+ struct FMCPOrientedBox
63
+ {
64
+ FVector Center = FVector::ZeroVector;
65
+ FVector Axis[3] = { FVector::ForwardVector, FVector::RightVector, FVector::UpVector };
66
+ FVector Extent = FVector::ZeroVector;
67
+ };
68
+
69
+ FMCPOrientedBox MCPBuildOrientedBox(USceneComponent* Component)
70
+ {
71
+ FMCPOrientedBox Box;
72
+ if (!Component)
73
+ {
74
+ return Box;
75
+ }
76
+ const FTransform Transform = Component->GetComponentTransform();
77
+ const FBoxSphereBounds LocalBounds = Component->GetLocalBounds();
78
+ Box.Center = Transform.TransformPosition(LocalBounds.Origin);
79
+ const FQuat Rotation = Transform.GetRotation();
80
+ Box.Axis[0] = Rotation.GetAxisX();
81
+ Box.Axis[1] = Rotation.GetAxisY();
82
+ Box.Axis[2] = Rotation.GetAxisZ();
83
+ const FVector Scale = Transform.GetScale3D();
84
+ Box.Extent = FVector(
85
+ LocalBounds.BoxExtent.X * FMath::Abs(Scale.X),
86
+ LocalBounds.BoxExtent.Y * FMath::Abs(Scale.Y),
87
+ LocalBounds.BoxExtent.Z * FMath::Abs(Scale.Z));
88
+ return Box;
89
+ }
90
+
91
+ /** Projected radius of an oriented box onto a unit axis. */
92
+ double MCPProjectedRadius(const FMCPOrientedBox& Box, const FVector& Axis)
93
+ {
94
+ return
95
+ Box.Extent.X * FMath::Abs(FVector::DotProduct(Box.Axis[0], Axis)) +
96
+ Box.Extent.Y * FMath::Abs(FVector::DotProduct(Box.Axis[1], Axis)) +
97
+ Box.Extent.Z * FMath::Abs(FVector::DotProduct(Box.Axis[2], Axis));
98
+ }
99
+
100
+ /**
101
+ * Separating axis test between two oriented boxes.
102
+ *
103
+ * Deliberately geometric rather than a physics overlap query: the whole
104
+ * reason #914 was filed alongside #915 is that a physics answer depends on
105
+ * collision state, and stale collision state is what produced the wrong
106
+ * answer in the first place. This reads the transforms and the bounds and
107
+ * nothing else.
108
+ *
109
+ * Returns true when the boxes intersect. OutSeparation is the largest gap
110
+ * found along any tested axis when they do not, and OutPenetration is the
111
+ * smallest overlap along any tested axis when they do.
112
+ */
113
+ bool MCPOrientedBoxesOverlap(
114
+ const FMCPOrientedBox& A,
115
+ const FMCPOrientedBox& B,
116
+ double& OutSeparation,
117
+ double& OutPenetration,
118
+ FVector& OutAxis)
119
+ {
120
+ OutSeparation = 0.0;
121
+ OutPenetration = TNumericLimits<double>::Max();
122
+ OutAxis = FVector::ZeroVector;
123
+
124
+ TArray<FVector, TInlineAllocator<15>> Axes;
125
+ for (int32 Index = 0; Index < 3; ++Index) Axes.Add(A.Axis[Index]);
126
+ for (int32 Index = 0; Index < 3; ++Index) Axes.Add(B.Axis[Index]);
127
+ for (int32 IndexA = 0; IndexA < 3; ++IndexA)
128
+ {
129
+ for (int32 IndexB = 0; IndexB < 3; ++IndexB)
130
+ {
131
+ const FVector Cross = FVector::CrossProduct(A.Axis[IndexA], B.Axis[IndexB]);
132
+ // Parallel edge pairs produce a degenerate axis. The six face
133
+ // normals already cover that case, so skipping it is correct
134
+ // rather than an approximation.
135
+ if (Cross.SizeSquared() > UE_KINDA_SMALL_NUMBER)
136
+ {
137
+ Axes.Add(Cross.GetSafeNormal());
138
+ }
139
+ }
140
+ }
141
+
142
+ const FVector Delta = B.Center - A.Center;
143
+ bool bOverlapping = true;
144
+ for (const FVector& Axis : Axes)
145
+ {
146
+ if (Axis.IsNearlyZero())
147
+ {
148
+ continue;
149
+ }
150
+ const FVector Unit = Axis.GetSafeNormal();
151
+ const double Distance = FMath::Abs(FVector::DotProduct(Delta, Unit));
152
+ const double Reach = MCPProjectedRadius(A, Unit) + MCPProjectedRadius(B, Unit);
153
+ const double Gap = Distance - Reach;
154
+ if (Gap > 0.0)
155
+ {
156
+ bOverlapping = false;
157
+ if (Gap > OutSeparation)
158
+ {
159
+ OutSeparation = Gap;
160
+ OutAxis = Unit;
161
+ }
162
+ }
163
+ else if (bOverlapping && -Gap < OutPenetration)
164
+ {
165
+ OutPenetration = -Gap;
166
+ OutAxis = Unit;
167
+ }
168
+ }
169
+
170
+ if (!bOverlapping)
171
+ {
172
+ OutPenetration = 0.0;
173
+ }
174
+ else if (OutPenetration == TNumericLimits<double>::Max())
175
+ {
176
+ // Two zero-extent boxes overlap with every separating axis
177
+ // degenerate, so no axis ever narrowed the seed. Reporting the seed
178
+ // would hand the caller 1.79e308 as a penetration depth in
179
+ // centimetres, which reads as a number rather than as "there was
180
+ // nothing to measure".
181
+ OutPenetration = 0.0;
182
+ OutAxis = FVector::ZeroVector;
183
+ }
184
+ return bOverlapping;
185
+ }
186
+
187
+ UActorComponent* MCPRefreshFindComponent(AActor* Actor, const FString& ComponentName)
188
+ {
189
+ if (!Actor) return nullptr;
190
+ for (UActorComponent* Component : Actor->GetComponents())
191
+ {
192
+ if (Component && Component->GetName().Equals(ComponentName, ESearchCase::IgnoreCase))
193
+ {
194
+ return Component;
195
+ }
196
+ }
197
+ return nullptr;
198
+ }
199
+
200
+ TSharedPtr<FJsonObject> MCPRefreshDescribeBounds(const FBoxSphereBounds& Bounds)
201
+ {
202
+ TSharedPtr<FJsonObject> Object = MakeShared<FJsonObject>();
203
+ Object->SetObjectField(TEXT("origin"), MCPVec3ToJsonObject(Bounds.Origin));
204
+ Object->SetObjectField(TEXT("boxExtent"), MCPVec3ToJsonObject(Bounds.BoxExtent));
205
+ Object->SetNumberField(TEXT("sphereRadius"), Bounds.SphereRadius);
206
+ return Object;
207
+ }
208
+
209
+ /** Everything #914 asked to see about one side of the comparison. */
210
+ TSharedPtr<FJsonObject> MCPRefreshDescribeComponent(USceneComponent* Component)
211
+ {
212
+ TSharedPtr<FJsonObject> Object = MakeShared<FJsonObject>();
213
+ Object->SetStringField(TEXT("componentName"), Component->GetName());
214
+ Object->SetStringField(TEXT("componentClass"), Component->GetClass()->GetName());
215
+
216
+ const FTransform Transform = Component->GetComponentTransform();
217
+ TSharedPtr<FJsonObject> TransformObject = MakeShared<FJsonObject>();
218
+ TransformObject->SetObjectField(TEXT("location"), MCPVec3ToJsonObject(Transform.GetLocation()));
219
+ TransformObject->SetObjectField(TEXT("rotation"), MCPRotatorToJsonObject(Transform.Rotator()));
220
+ TransformObject->SetObjectField(TEXT("scale"), MCPVec3ToJsonObject(Transform.GetScale3D()));
221
+ Object->SetObjectField(TEXT("worldTransform"), TransformObject);
222
+
223
+ Object->SetObjectField(TEXT("localBounds"), MCPRefreshDescribeBounds(Component->GetLocalBounds()));
224
+ Object->SetObjectField(TEXT("worldBounds"), MCPRefreshDescribeBounds(Component->GetBounds()));
225
+
226
+ // #914's original ask: the UNSCALED authored extent, which is what the
227
+ // details panel shows and what no existing read returned.
228
+ if (const UBoxComponent* BoxComponent = Cast<UBoxComponent>(Component))
229
+ {
230
+ Object->SetObjectField(TEXT("boxExtentUnscaled"),
231
+ MCPVec3ToJsonObject(BoxComponent->GetUnscaledBoxExtent()));
232
+ Object->SetObjectField(TEXT("boxExtentScaled"),
233
+ MCPVec3ToJsonObject(BoxComponent->GetScaledBoxExtent()));
234
+ }
235
+ return Object;
236
+ }
237
+ }
238
+
239
+ // ---------------------------------------------------------------------------
240
+ // rerun_construction_scripts (#944)
241
+ // ---------------------------------------------------------------------------
242
+ TSharedPtr<FJsonValue> FLevelHandlers::RerunConstruction(const TSharedPtr<FJsonObject>& Params)
243
+ {
244
+ MCP_CHECK_GAME_THREAD();
245
+
246
+ const FString WorldScope = OptionalString(Params, TEXT("world"), TEXT("editor"));
247
+ UWorld* World = ResolveWorldFromParams(Params, *WorldScope);
248
+ if (!World)
249
+ {
250
+ return MCPError(FString::Printf(TEXT("World not available for scope '%s'"), *WorldScope));
251
+ }
252
+
253
+ TArray<FString> ActorLabels;
254
+ {
255
+ const TArray<TSharedPtr<FJsonValue>>* LabelValues = nullptr;
256
+ if (Params->TryGetArrayField(TEXT("actorLabels"), LabelValues) && LabelValues)
257
+ {
258
+ ActorLabels = JsonArrayToStringList(LabelValues);
259
+ }
260
+ }
261
+ const FString ClassSpec = OptionalString(Params, TEXT("className"));
262
+ if (ActorLabels.IsEmpty() && ClassSpec.IsEmpty())
263
+ {
264
+ // Rerunning every construction script in a map destroys and rebuilds
265
+ // every generated component in it. That is not a default.
266
+ return MCPError(TEXT("Pass 'actorLabels' or 'className'. This rebuilds generated components, so it will not run against a whole level implicitly."));
267
+ }
268
+
269
+ UClass* ActorClass = nullptr;
270
+ FString ClassFallback;
271
+ if (!ClassSpec.IsEmpty())
272
+ {
273
+ ActorClass = MCPResolveClassOfType(ClassSpec, AActor::StaticClass(), true);
274
+ if (!ActorClass)
275
+ {
276
+ ClassFallback = ClassSpec;
277
+ }
278
+ }
279
+ const bool bMatchSubclasses = OptionalBool(Params, TEXT("matchSubclasses"), true);
280
+
281
+ TArray<AActor*> Targets;
282
+ TSet<FString> WantedLabels(ActorLabels);
283
+ TSet<FString> FoundLabels;
284
+ for (TActorIterator<AActor> It(World); It; ++It)
285
+ {
286
+ AActor* Actor = *It;
287
+ if (!Actor) continue;
288
+
289
+ bool bMatches = false;
290
+ const FString Label = Actor->GetActorLabel();
291
+ if (WantedLabels.Contains(Label))
292
+ {
293
+ bMatches = true;
294
+ FoundLabels.Add(Label);
295
+ }
296
+ if (!bMatches && ActorClass)
297
+ {
298
+ bMatches = bMatchSubclasses ? Actor->IsA(ActorClass) : (Actor->GetClass() == ActorClass);
299
+ }
300
+ if (!bMatches && !ClassFallback.IsEmpty())
301
+ {
302
+ bMatches = Actor->GetClass()->GetName().Contains(ClassFallback, ESearchCase::IgnoreCase);
303
+ }
304
+ if (bMatches)
305
+ {
306
+ Targets.Add(Actor);
307
+ }
308
+ }
309
+ if (Targets.Num() > MCPRefreshMaxActors)
310
+ {
311
+ return MCPError(FString::Printf(
312
+ TEXT("Matched %d actors, over the maximum of %d. Narrow the selector."),
313
+ Targets.Num(), MCPRefreshMaxActors));
314
+ }
315
+ Targets.Sort([](const AActor& A, const AActor& B) { return A.GetPathName() < B.GetPathName(); });
316
+
317
+ int32 Reran = 0;
318
+ int32 WithoutScript = 0;
319
+ TArray<FString> Failed;
320
+ TArray<FString> Samples;
321
+
322
+ #if WITH_EDITOR
323
+ for (AActor* Actor : Targets)
324
+ {
325
+ if (!IsValid(Actor))
326
+ {
327
+ // Do not call GetName() on the pointer the guard just rejected. A
328
+ // garbage-but-unfreed UObject happens to answer today, which is
329
+ // exactly why this would survive testing and then not survive a
330
+ // real collection.
331
+ Failed.Add(TEXT("an actor in the selection is pending destruction"));
332
+ continue;
333
+ }
334
+ // An actor with no user construction script still gets its generated
335
+ // components rebuilt, so this is reported rather than skipped: a
336
+ // caller expecting a Blueprint change to land needs to know it hit an
337
+ // actor whose class has no script to run.
338
+ if (!Actor->HasNonTrivialUserConstructionScript())
339
+ {
340
+ ++WithoutScript;
341
+ }
342
+ Actor->Modify();
343
+ Actor->RerunConstructionScripts();
344
+ ++Reran;
345
+ if (Samples.Num() < MCPRefreshMaxSamples)
346
+ {
347
+ Samples.Add(FString::Printf(TEXT("%s (%s)"), *Actor->GetActorLabel(), *Actor->GetClass()->GetName()));
348
+ }
349
+ }
350
+ #else
351
+ for (AActor* Actor : Targets)
352
+ {
353
+ Failed.Add(FString::Printf(
354
+ TEXT("%s: RerunConstructionScripts is editor-only"), *Actor->GetActorLabel()));
355
+ }
356
+ #endif
357
+
358
+ TArray<FString> MissingLabels;
359
+ for (const FString& Label : ActorLabels)
360
+ {
361
+ if (!FoundLabels.Contains(Label))
362
+ {
363
+ MissingLabels.Add(Label);
364
+ }
365
+ }
366
+
367
+ auto Result = MCPSuccess();
368
+ Result->SetBoolField(TEXT("success"), Failed.IsEmpty() && MissingLabels.IsEmpty());
369
+ if (!MissingLabels.IsEmpty())
370
+ {
371
+ Result->SetStringField(TEXT("error"), FString::Printf(
372
+ TEXT("%d requested actor label(s) matched nothing in this world; see missingLabels."),
373
+ MissingLabels.Num()));
374
+ }
375
+ Result->SetNumberField(TEXT("matched"), Targets.Num());
376
+ Result->SetNumberField(TEXT("reran"), Reran);
377
+ Result->SetNumberField(TEXT("actorsWithNoUserConstructionScript"), WithoutScript);
378
+ Result->SetArrayField(TEXT("failed"), MCPStringListToJson(Failed));
379
+ Result->SetArrayField(TEXT("missingLabels"), MCPStringListToJson(MissingLabels));
380
+ Result->SetArrayField(TEXT("samples"), MCPStringListToJson(Samples));
381
+ if (Reran > 0)
382
+ {
383
+ MCPSetUpdated(Result);
384
+ Result->SetStringField(TEXT("saveNote"),
385
+ TEXT("Reconstructed actors leave the level dirty and it is NOT saved. Call level(save) when you are done."));
386
+ }
387
+ return MCPResult(Result);
388
+ }
389
+
390
+ // ---------------------------------------------------------------------------
391
+ // recreate_physics_state (#915)
392
+ // ---------------------------------------------------------------------------
393
+ TSharedPtr<FJsonValue> FLevelHandlers::RecreatePhysicsState(const TSharedPtr<FJsonObject>& Params)
394
+ {
395
+ MCP_CHECK_GAME_THREAD();
396
+
397
+ const FString WorldScope = OptionalString(Params, TEXT("world"), TEXT("editor"));
398
+ UWorld* World = ResolveWorldFromParams(Params, *WorldScope);
399
+ if (!World)
400
+ {
401
+ return MCPError(FString::Printf(TEXT("World not available for scope '%s'"), *WorldScope));
402
+ }
403
+
404
+ TArray<FString> ActorLabels;
405
+ {
406
+ const TArray<TSharedPtr<FJsonValue>>* LabelValues = nullptr;
407
+ if (Params->TryGetArrayField(TEXT("actorLabels"), LabelValues) && LabelValues)
408
+ {
409
+ ActorLabels = JsonArrayToStringList(LabelValues);
410
+ }
411
+ }
412
+ const FString LabelPrefix = OptionalString(Params, TEXT("labelPrefix"));
413
+ const FString Tag = OptionalString(Params, TEXT("tag"));
414
+ const FString ActorClassSpec = OptionalString(Params, TEXT("classFilter"));
415
+ const FString ComponentClassSpec = OptionalString(Params, TEXT("componentClass"));
416
+ const FString ComponentNameContains = OptionalString(Params, TEXT("componentNameContains"));
417
+
418
+ if (ActorLabels.IsEmpty() && LabelPrefix.IsEmpty() && Tag.IsEmpty() &&
419
+ ActorClassSpec.IsEmpty() && ComponentClassSpec.IsEmpty() && ComponentNameContains.IsEmpty())
420
+ {
421
+ // A world-wide physics rebuild is a long, disruptive operation, and the
422
+ // bug this action exists for is always localised to the meshes that
423
+ // changed. Refusing an unbounded call is the point.
424
+ return MCPError(TEXT("Pass at least one bound: actorLabels, labelPrefix, tag, classFilter, componentClass or componentNameContains. This never refreshes a whole world implicitly."));
425
+ }
426
+
427
+ UClass* ActorClass = nullptr;
428
+ if (!ActorClassSpec.IsEmpty())
429
+ {
430
+ ActorClass = MCPResolveClassOfType(ActorClassSpec, AActor::StaticClass(), true);
431
+ if (!ActorClass)
432
+ {
433
+ return MCPClassNotFoundError(ActorClassSpec, TEXT("classFilter"));
434
+ }
435
+ }
436
+ UClass* ComponentClass = nullptr;
437
+ if (!ComponentClassSpec.IsEmpty())
438
+ {
439
+ ComponentClass = MCPResolveClassOfType(ComponentClassSpec, UActorComponent::StaticClass(), true);
440
+ if (!ComponentClass)
441
+ {
442
+ return MCPClassNotFoundError(ComponentClassSpec, TEXT("componentClass"));
443
+ }
444
+ }
445
+
446
+ // dryRun defaults to TRUE: this is the preflight #915 asked for. Seeing
447
+ // which components are about to be rebuilt, and what their collision state
448
+ // is now, is most of the value.
449
+ const bool bDryRun = OptionalBool(Params, TEXT("dryRun"), true);
450
+ const int32 MaxComponents = FMath::Clamp(
451
+ OptionalInt(Params, TEXT("maxComponents"), MCPRefreshDefaultMaxComponents),
452
+ 1, MCPRefreshHardMaxComponents);
453
+
454
+ TSet<FString> WantedLabels(ActorLabels);
455
+ TSet<FString> FoundLabels;
456
+ TArray<UPrimitiveComponent*> Targets;
457
+ for (TActorIterator<AActor> It(World); It; ++It)
458
+ {
459
+ AActor* Actor = *It;
460
+ if (!Actor) continue;
461
+ const FString Label = Actor->GetActorLabel();
462
+
463
+ if (!WantedLabels.IsEmpty())
464
+ {
465
+ if (!WantedLabels.Contains(Label)) continue;
466
+ FoundLabels.Add(Label);
467
+ }
468
+ if (!LabelPrefix.IsEmpty() && !Label.StartsWith(LabelPrefix, ESearchCase::CaseSensitive)) continue;
469
+ if (!Tag.IsEmpty() && !Actor->ActorHasTag(FName(*Tag))) continue;
470
+ if (ActorClass && !Actor->IsA(ActorClass)) continue;
471
+
472
+ for (UActorComponent* Component : Actor->GetComponents())
473
+ {
474
+ UPrimitiveComponent* Primitive = Cast<UPrimitiveComponent>(Component);
475
+ if (!Primitive) continue;
476
+ if (ComponentClass && !Primitive->IsA(ComponentClass)) continue;
477
+ if (!ComponentNameContains.IsEmpty() &&
478
+ !Primitive->GetName().Contains(ComponentNameContains, ESearchCase::IgnoreCase))
479
+ {
480
+ continue;
481
+ }
482
+ Targets.Add(Primitive);
483
+ }
484
+ }
485
+
486
+ Targets.Sort([](const UPrimitiveComponent& A, const UPrimitiveComponent& B)
487
+ {
488
+ return A.GetPathName() < B.GetPathName();
489
+ });
490
+
491
+ auto Result = MCPSuccess();
492
+ Result->SetBoolField(TEXT("dryRun"), bDryRun);
493
+ Result->SetNumberField(TEXT("matchedComponents"), Targets.Num());
494
+
495
+ TArray<FString> MissingLabels;
496
+ for (const FString& Label : ActorLabels)
497
+ {
498
+ if (!FoundLabels.Contains(Label)) MissingLabels.Add(Label);
499
+ }
500
+ if (!MissingLabels.IsEmpty())
501
+ {
502
+ Result->SetArrayField(TEXT("missingLabels"), MCPStringListToJson(MissingLabels));
503
+ }
504
+
505
+ if (Targets.Num() > MaxComponents)
506
+ {
507
+ Result->SetBoolField(TEXT("success"), false);
508
+ Result->SetStringField(TEXT("error"), FString::Printf(
509
+ TEXT("Matched %d components, over maxComponents (%d). Narrow the bounds or raise it deliberately."),
510
+ Targets.Num(), MaxComponents));
511
+ return MCPResult(Result);
512
+ }
513
+
514
+ TArray<TSharedPtr<FJsonValue>> Rows;
515
+ int32 Recreated = 0;
516
+ int32 PhysicsStateAfter = 0;
517
+ int32 NoCollisionCount = 0;
518
+ TMap<FString, int32> CollisionSummary;
519
+
520
+ for (UPrimitiveComponent* Primitive : Targets)
521
+ {
522
+ const ECollisionEnabled::Type Before = Primitive->GetCollisionEnabled();
523
+ const bool bStateBefore = Primitive->IsPhysicsStateCreated();
524
+
525
+ if (!bDryRun)
526
+ {
527
+ Primitive->RecreatePhysicsState();
528
+ ++Recreated;
529
+ }
530
+
531
+ const ECollisionEnabled::Type After = Primitive->GetCollisionEnabled();
532
+ const bool bStateAfter = Primitive->IsPhysicsStateCreated();
533
+ if (bStateAfter) ++PhysicsStateAfter;
534
+ if (After == ECollisionEnabled::NoCollision) ++NoCollisionCount;
535
+ ++CollisionSummary.FindOrAdd(MCPRefreshCollisionName(After));
536
+
537
+ if (Rows.Num() < MCPRefreshMaxResultRows)
538
+ {
539
+ TSharedPtr<FJsonObject> Row = MakeShared<FJsonObject>();
540
+ Row->SetStringField(TEXT("actorLabel"), Primitive->GetOwner() ? Primitive->GetOwner()->GetActorLabel() : FString());
541
+ Row->SetStringField(TEXT("componentName"), Primitive->GetName());
542
+ Row->SetStringField(TEXT("componentClass"), Primitive->GetClass()->GetName());
543
+ Row->SetStringField(TEXT("status"), bDryRun ? TEXT("would_recreate") : TEXT("recreated"));
544
+ Row->SetStringField(TEXT("collisionEnabledBefore"), MCPRefreshCollisionName(Before));
545
+ Row->SetStringField(TEXT("collisionEnabledAfter"), MCPRefreshCollisionName(After));
546
+ Row->SetStringField(TEXT("collisionProfile"), Primitive->GetCollisionProfileName().ToString());
547
+ Row->SetBoolField(TEXT("physicsStateCreatedBefore"), bStateBefore);
548
+ Row->SetBoolField(TEXT("physicsStateCreatedAfter"), bStateAfter);
549
+ Row->SetBoolField(TEXT("registered"), Primitive->IsRegistered());
550
+ Rows.Add(MakeShared<FJsonValueObject>(Row));
551
+ }
552
+ }
553
+
554
+ TSharedPtr<FJsonObject> Summary = MakeShared<FJsonObject>();
555
+ for (const TPair<FString, int32>& Pair : CollisionSummary)
556
+ {
557
+ Summary->SetNumberField(Pair.Key, Pair.Value);
558
+ }
559
+
560
+ Result->SetNumberField(bDryRun ? TEXT("wouldRecreate") : TEXT("recreated"),
561
+ bDryRun ? Targets.Num() : Recreated);
562
+ Result->SetNumberField(TEXT("physicsStateCreated"), PhysicsStateAfter);
563
+ Result->SetNumberField(TEXT("componentsWithNoCollision"), NoCollisionCount);
564
+ Result->SetObjectField(TEXT("collisionSummary"), Summary);
565
+ Result->SetNumberField(TEXT("returnedResults"), Rows.Num());
566
+ Result->SetBoolField(TEXT("resultsTruncated"), Rows.Num() < Targets.Num());
567
+ Result->SetArrayField(TEXT("results"), Rows);
568
+ if (Targets.IsEmpty())
569
+ {
570
+ Result->SetStringField(TEXT("zeroMatchNote"),
571
+ TEXT("Nothing matched. The bounds are AND-ed: an actor selector and a componentClass must both hold. Check with level(query_components, countOnly:true) using the same filters."));
572
+ }
573
+ if (bDryRun)
574
+ {
575
+ Result->SetStringField(TEXT("dryRunNote"),
576
+ TEXT("dryRun defaults to TRUE for this action: the preflight shows which components would be rebuilt and their current collision state. Pass dryRun=false to rebuild them."));
577
+ }
578
+ else
579
+ {
580
+ Result->SetStringField(TEXT("traceNote"),
581
+ TEXT("Re-run the trace that gave the wrong answer. A false-negative line_trace caused by stale collision looks exactly like a real miss, so the only proof this worked is the trace."));
582
+ }
583
+ return MCPResult(Result);
584
+ }
585
+
586
+ // ---------------------------------------------------------------------------
587
+ // test_component_overlap (#914)
588
+ // ---------------------------------------------------------------------------
589
+ TSharedPtr<FJsonValue> FLevelHandlers::TestComponentOverlap(const TSharedPtr<FJsonObject>& Params)
590
+ {
591
+ MCP_CHECK_GAME_THREAD();
592
+
593
+ const FString WorldScope = OptionalString(Params, TEXT("world"), TEXT("editor"));
594
+ UWorld* World = ResolveWorldFromParams(Params, *WorldScope);
595
+ if (!World)
596
+ {
597
+ return MCPError(FString::Printf(TEXT("World not available for scope '%s'"), *WorldScope));
598
+ }
599
+
600
+ FString ActorLabelA;
601
+ if (auto Err = RequireStringAlt(Params, TEXT("actorLabelA"), TEXT("actorPathA"), ActorLabelA)) return Err;
602
+ FString ActorLabelB;
603
+ if (auto Err = RequireStringAlt(Params, TEXT("actorLabelB"), TEXT("actorPathB"), ActorLabelB)) return Err;
604
+ const FString ComponentNameA = OptionalString(Params, TEXT("componentNameA"));
605
+ const FString ComponentNameB = OptionalString(Params, TEXT("componentNameB"));
606
+
607
+ FString Method = OptionalString(Params, TEXT("method"), TEXT("OBB")).ToUpper();
608
+ if (Method != TEXT("OBB") && Method != TEXT("AABB"))
609
+ {
610
+ return MCPError(TEXT("'method' must be either 'OBB' (oriented, default) or 'AABB' (axis-aligned world bounds)"));
611
+ }
612
+
613
+ // #983: each side is selected by its own label / path pair, so an overlap
614
+ // test between two copy-pasted props refuses rather than answering about
615
+ // two actors the caller did not name.
616
+ auto ResolveSide = [&](const TCHAR* LabelKey, const TCHAR* PathKey, const FString& ComponentName,
617
+ USceneComponent*& OutComponent, AActor*& OutActor, TSharedPtr<FJsonValue>& OutError) -> bool
618
+ {
619
+ FMCPActorSelector Selector;
620
+ Selector.LabelKey = LabelKey;
621
+ Selector.PathKey = PathKey;
622
+ AActor* Actor = MCPResolveActor(World, Params, OutError, Selector);
623
+ if (!Actor) return false;
624
+ OutActor = Actor;
625
+ const FString ActorLabel = Actor->GetActorLabel();
626
+ if (ComponentName.IsEmpty())
627
+ {
628
+ OutComponent = Actor->GetRootComponent();
629
+ if (!OutComponent)
630
+ {
631
+ OutError = MCPError(FString::Printf(TEXT("Actor '%s' has no root component"), *ActorLabel));
632
+ return false;
633
+ }
634
+ return true;
635
+ }
636
+ UActorComponent* Component = MCPRefreshFindComponent(Actor, ComponentName);
637
+ OutComponent = Cast<USceneComponent>(Component);
638
+ if (!OutComponent)
639
+ {
640
+ OutError = MCPError(FString::Printf(
641
+ TEXT("No SceneComponent named '%s' on actor '%s'"), *ComponentName, *ActorLabel));
642
+ return false;
643
+ }
644
+ return true;
645
+ };
646
+
647
+ USceneComponent* ComponentA = nullptr;
648
+ USceneComponent* ComponentB = nullptr;
649
+ AActor* ActorA = nullptr;
650
+ AActor* ActorB = nullptr;
651
+ TSharedPtr<FJsonValue> ResolveError;
652
+ if (!ResolveSide(TEXT("actorLabelA"), TEXT("actorPathA"), ComponentNameA, ComponentA, ActorA, ResolveError)) return ResolveError;
653
+ if (!ResolveSide(TEXT("actorLabelB"), TEXT("actorPathB"), ComponentNameB, ComponentB, ActorB, ResolveError)) return ResolveError;
654
+ if (ComponentA == ComponentB)
655
+ {
656
+ return MCPError(TEXT("Both sides resolved to the same component; an overlap test between a component and itself has no answer"));
657
+ }
658
+ ActorLabelA = ActorA->GetActorLabel();
659
+ ActorLabelB = ActorB->GetActorLabel();
660
+
661
+ auto Result = MCPSuccess();
662
+ Result->SetStringField(TEXT("method"), Method);
663
+ Result->SetStringField(TEXT("actorLabelA"), ActorLabelA);
664
+ Result->SetStringField(TEXT("actorPathA"), ActorA->GetPathName());
665
+ Result->SetStringField(TEXT("actorLabelB"), ActorLabelB);
666
+ Result->SetStringField(TEXT("actorPathB"), ActorB->GetPathName());
667
+
668
+ TSharedPtr<FJsonObject> SideA = MCPRefreshDescribeComponent(ComponentA);
669
+ SideA->SetStringField(TEXT("actorLabel"), ActorLabelA);
670
+ SideA->SetStringField(TEXT("actorPath"), ActorA->GetPathName());
671
+ TSharedPtr<FJsonObject> SideB = MCPRefreshDescribeComponent(ComponentB);
672
+ SideB->SetStringField(TEXT("actorLabel"), ActorLabelB);
673
+ SideB->SetStringField(TEXT("actorPath"), ActorB->GetPathName());
674
+ Result->SetObjectField(TEXT("a"), SideA);
675
+ Result->SetObjectField(TEXT("b"), SideB);
676
+
677
+ bool bOverlap = false;
678
+ double Separation = 0.0;
679
+ double Penetration = 0.0;
680
+ FVector Axis = FVector::ZeroVector;
681
+
682
+ if (Method == TEXT("AABB"))
683
+ {
684
+ const FBox BoxA = ComponentA->GetBounds().GetBox();
685
+ const FBox BoxB = ComponentB->GetBounds().GetBox();
686
+ bOverlap = BoxA.Intersect(BoxB);
687
+ // Per-axis gap or overlap depth, which is what a caller tuning a
688
+ // placement actually wants back.
689
+ FVector PerAxis = FVector::ZeroVector;
690
+ for (int32 Index = 0; Index < 3; ++Index)
691
+ {
692
+ const double Gap = FMath::Max(
693
+ BoxA.Min[Index] - BoxB.Max[Index],
694
+ BoxB.Min[Index] - BoxA.Max[Index]);
695
+ PerAxis[Index] = Gap;
696
+ }
697
+ if (bOverlap)
698
+ {
699
+ // Every axis gap is negative when they intersect; the shallowest
700
+ // is the penetration depth.
701
+ Penetration = -FMath::Max3(PerAxis.X, PerAxis.Y, PerAxis.Z);
702
+ }
703
+ else
704
+ {
705
+ Separation = FMath::Max3(PerAxis.X, PerAxis.Y, PerAxis.Z);
706
+ }
707
+ Result->SetObjectField(TEXT("perAxisGap"), MCPVec3ToJsonObject(PerAxis));
708
+ }
709
+ else
710
+ {
711
+ const FMCPOrientedBox BoxA = MCPBuildOrientedBox(ComponentA);
712
+ const FMCPOrientedBox BoxB = MCPBuildOrientedBox(ComponentB);
713
+ bOverlap = MCPOrientedBoxesOverlap(BoxA, BoxB, Separation, Penetration, Axis);
714
+ Result->SetObjectField(TEXT("separatingAxis"), MCPVec3ToJsonObject(Axis));
715
+ }
716
+
717
+ Result->SetBoolField(TEXT("overlap"), bOverlap);
718
+ Result->SetNumberField(TEXT("separation"), bOverlap ? 0.0 : Separation);
719
+ Result->SetNumberField(TEXT("penetration"), bOverlap ? Penetration : 0.0);
720
+ Result->SetStringField(TEXT("note"),
721
+ TEXT("This is a geometric test over the components' bounds and transforms. It deliberately does not run a physics overlap query, because a physics answer depends on collision state and stale collision state is what makes a trace lie. Use level(recreate_physics_state) when you need the physics answer to agree."));
722
+ return MCPResult(Result);
723
+ }