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
@@ -61,17 +61,11 @@ TSharedPtr<FJsonValue> FWidgetHandlers::GetWidgetProperties(const TSharedPtr<FJs
61
61
  FString WidgetName;
62
62
  if (auto Err = RequireString(Params, TEXT("widgetName"), WidgetName)) return Err;
63
63
 
64
- UObject* LoadedAsset = UEditorAssetLibrary::LoadAsset(AssetPath);
65
- UWidgetBlueprint* WidgetBP = Cast<UWidgetBlueprint>(LoadedAsset);
66
- if (!WidgetBP)
67
- {
68
- return MCPError(FString::Printf(TEXT("Failed to load WidgetBlueprint at '%s'"), *AssetPath));
69
- }
64
+ TSharedPtr<FJsonValue> ResolveError;
65
+ UWidgetBlueprint* WidgetBP = MCPWidget::ResolveWidgetBlueprintOrError(AssetPath, ResolveError);
66
+ if (!WidgetBP) return ResolveError;
70
67
 
71
- if (!WidgetBP->WidgetTree)
72
- {
73
- return MCPError(TEXT("WidgetTree is null"));
74
- }
68
+ if (!WidgetBP->WidgetTree) return MCPWidget::MissingWidgetTreeError(AssetPath);
75
69
 
76
70
  // Find the widget
77
71
  UWidget* FoundWidget = nullptr;
@@ -265,16 +259,10 @@ TSharedPtr<FJsonValue> FWidgetHandlers::GetWidgetFullProperties(const TSharedPtr
265
259
 
266
260
  const bool bIncludeSubtree = OptionalBool(Params, TEXT("includeSubtree"), false);
267
261
 
268
- UObject* LoadedAsset = UEditorAssetLibrary::LoadAsset(AssetPath);
269
- UWidgetBlueprint* WidgetBP = Cast<UWidgetBlueprint>(LoadedAsset);
270
- if (!WidgetBP)
271
- {
272
- return MCPError(FString::Printf(TEXT("Failed to load WidgetBlueprint at '%s'"), *AssetPath));
273
- }
274
- if (!WidgetBP->WidgetTree)
275
- {
276
- return MCPError(TEXT("WidgetTree is null"));
277
- }
262
+ TSharedPtr<FJsonValue> ResolveError;
263
+ UWidgetBlueprint* WidgetBP = MCPWidget::ResolveWidgetBlueprintOrError(AssetPath, ResolveError);
264
+ if (!WidgetBP) return ResolveError;
265
+ if (!WidgetBP->WidgetTree) return MCPWidget::MissingWidgetTreeError(AssetPath);
278
266
 
279
267
  UWidget* FoundWidget = nullptr;
280
268
  WidgetBP->WidgetTree->ForEachWidget([&](UWidget* Widget)
@@ -369,11 +357,9 @@ TSharedPtr<FJsonValue> FWidgetHandlers::ListWidgetBindings(const TSharedPtr<FJso
369
357
  const FString FilterWidget = OptionalString(Params, TEXT("filterWidgetName"));
370
358
  const FString FilterProperty = OptionalString(Params, TEXT("filterProperty"));
371
359
 
372
- UWidgetBlueprint* WidgetBP = Cast<UWidgetBlueprint>(UEditorAssetLibrary::LoadAsset(AssetPath));
373
- if (!WidgetBP)
374
- {
375
- return MCPError(FString::Printf(TEXT("Failed to load WidgetBlueprint at '%s'"), *AssetPath));
376
- }
360
+ TSharedPtr<FJsonValue> ResolveError;
361
+ UWidgetBlueprint* WidgetBP = MCPWidget::ResolveWidgetBlueprintOrError(AssetPath, ResolveError);
362
+ if (!WidgetBP) return ResolveError;
377
363
 
378
364
  TArray<TSharedPtr<FJsonValue>> BindingsArr;
379
365
  for (const FDelegateEditorBinding& B : WidgetBP->Bindings)
@@ -411,11 +397,9 @@ TSharedPtr<FJsonValue> FWidgetHandlers::ClearWidgetBinding(const TSharedPtr<FJso
411
397
 
412
398
  const FString PropertyName = OptionalString(Params, TEXT("propertyName"));
413
399
 
414
- UWidgetBlueprint* WidgetBP = Cast<UWidgetBlueprint>(UEditorAssetLibrary::LoadAsset(AssetPath));
415
- if (!WidgetBP)
416
- {
417
- return MCPError(FString::Printf(TEXT("Failed to load WidgetBlueprint at '%s'"), *AssetPath));
418
- }
400
+ TSharedPtr<FJsonValue> ResolveError;
401
+ UWidgetBlueprint* WidgetBP = MCPWidget::ResolveWidgetBlueprintOrError(AssetPath, ResolveError);
402
+ if (!WidgetBP) return ResolveError;
419
403
 
420
404
  WidgetBP->Modify();
421
405
  const int32 Removed = WidgetBP->Bindings.RemoveAll([&](const FDelegateEditorBinding& B)
@@ -456,17 +440,11 @@ TSharedPtr<FJsonValue> FWidgetHandlers::SetWidgetProperty(const TSharedPtr<FJson
456
440
  FString PropertyValue;
457
441
  if (auto Err = RequireStringAlt(Params, TEXT("propertyValue"), TEXT("value"), PropertyValue)) return Err;
458
442
 
459
- UObject* LoadedAsset = UEditorAssetLibrary::LoadAsset(AssetPath);
460
- UWidgetBlueprint* WidgetBP = Cast<UWidgetBlueprint>(LoadedAsset);
461
- if (!WidgetBP)
462
- {
463
- return MCPError(FString::Printf(TEXT("Failed to load WidgetBlueprint at '%s'"), *AssetPath));
464
- }
443
+ TSharedPtr<FJsonValue> ResolveError;
444
+ UWidgetBlueprint* WidgetBP = MCPWidget::ResolveWidgetBlueprintOrError(AssetPath, ResolveError);
445
+ if (!WidgetBP) return ResolveError;
465
446
 
466
- if (!WidgetBP->WidgetTree)
467
- {
468
- return MCPError(TEXT("WidgetTree is null"));
469
- }
447
+ if (!WidgetBP->WidgetTree) return MCPWidget::MissingWidgetTreeError(AssetPath);
470
448
 
471
449
  // Find the widget
472
450
  UWidget* FoundWidget = nullptr;
@@ -1047,12 +1025,9 @@ TSharedPtr<FJsonValue> FWidgetHandlers::ReadWidgetAnimations(const TSharedPtr<FJ
1047
1025
  FString AssetPath;
1048
1026
  if (auto Err = RequireStringAlt(Params, TEXT("assetPath"), TEXT("path"), AssetPath)) return Err;
1049
1027
 
1050
- UObject* LoadedAsset = UEditorAssetLibrary::LoadAsset(AssetPath);
1051
- UWidgetBlueprint* WidgetBP = Cast<UWidgetBlueprint>(LoadedAsset);
1052
- if (!WidgetBP)
1053
- {
1054
- return MCPError(FString::Printf(TEXT("Failed to load WidgetBlueprint at '%s'"), *AssetPath));
1055
- }
1028
+ TSharedPtr<FJsonValue> ResolveError;
1029
+ UWidgetBlueprint* WidgetBP = MCPWidget::ResolveWidgetBlueprintOrError(AssetPath, ResolveError);
1030
+ if (!WidgetBP) return ResolveError;
1056
1031
 
1057
1032
  TArray<TSharedPtr<FJsonValue>> AnimationsArray;
1058
1033
 
@@ -1178,8 +1153,10 @@ TSharedPtr<FJsonValue> FWidgetHandlers::SetWidgetStyle(const TSharedPtr<FJsonObj
1178
1153
  TSharedPtr<FJsonValue> ValueField = Params->TryGetField(TEXT("value"));
1179
1154
  if (!ValueField.IsValid()) return MCPError(TEXT("Missing 'value' (a JSON object/scalar for the style)"));
1180
1155
 
1181
- UWidgetBlueprint* WidgetBP = Cast<UWidgetBlueprint>(UEditorAssetLibrary::LoadAsset(AssetPath));
1182
- if (!WidgetBP || !WidgetBP->WidgetTree) return MCPError(FString::Printf(TEXT("WidgetBlueprint not found: %s"), *AssetPath));
1156
+ TSharedPtr<FJsonValue> ResolveError;
1157
+ UWidgetBlueprint* WidgetBP = MCPWidget::ResolveWidgetBlueprintOrError(AssetPath, ResolveError);
1158
+ if (!WidgetBP) return ResolveError;
1159
+ if (!WidgetBP->WidgetTree) return MCPWidget::MissingWidgetTreeError(AssetPath);
1183
1160
 
1184
1161
  UWidget* Widget = FindWidgetByName(WidgetBP, WidgetName);
1185
1162
  if (!Widget) return MCPError(FString::Printf(TEXT("Widget not found: %s"), *WidgetName));
@@ -1217,8 +1194,10 @@ TSharedPtr<FJsonValue> FWidgetHandlers::BulkSetWidgetProperties(const TSharedPtr
1217
1194
  return MCPError(TEXT("Missing 'properties' array ([{widgetName, propertyName, value}])"));
1218
1195
  }
1219
1196
 
1220
- UWidgetBlueprint* WidgetBP = Cast<UWidgetBlueprint>(UEditorAssetLibrary::LoadAsset(AssetPath));
1221
- if (!WidgetBP || !WidgetBP->WidgetTree) return MCPError(FString::Printf(TEXT("WidgetBlueprint not found: %s"), *AssetPath));
1197
+ TSharedPtr<FJsonValue> ResolveError;
1198
+ UWidgetBlueprint* WidgetBP = MCPWidget::ResolveWidgetBlueprintOrError(AssetPath, ResolveError);
1199
+ if (!WidgetBP) return ResolveError;
1200
+ if (!WidgetBP->WidgetTree) return MCPWidget::MissingWidgetTreeError(AssetPath);
1222
1201
 
1223
1202
  TArray<TSharedPtr<FJsonValue>> Results;
1224
1203
  int32 Applied = 0, Failed = 0;
@@ -1286,8 +1265,10 @@ TSharedPtr<FJsonValue> FWidgetHandlers::ReorderChild(const TSharedPtr<FJsonObjec
1286
1265
  if (!Params->HasField(TEXT("index"))) return MCPError(TEXT("Missing 'index'"));
1287
1266
  const int32 NewIndex = (int32)Params->GetNumberField(TEXT("index"));
1288
1267
 
1289
- UWidgetBlueprint* WidgetBP = Cast<UWidgetBlueprint>(UEditorAssetLibrary::LoadAsset(AssetPath));
1290
- if (!WidgetBP || !WidgetBP->WidgetTree) return MCPError(FString::Printf(TEXT("WidgetBlueprint not found: %s"), *AssetPath));
1268
+ TSharedPtr<FJsonValue> ResolveError;
1269
+ UWidgetBlueprint* WidgetBP = MCPWidget::ResolveWidgetBlueprintOrError(AssetPath, ResolveError);
1270
+ if (!WidgetBP) return ResolveError;
1271
+ if (!WidgetBP->WidgetTree) return MCPWidget::MissingWidgetTreeError(AssetPath);
1291
1272
  UWidget* Widget = FindWidgetByName(WidgetBP, WidgetName);
1292
1273
  if (!Widget) return MCPError(FString::Printf(TEXT("Widget not found: %s"), *WidgetName));
1293
1274
 
@@ -0,0 +1,344 @@
1
+ // Coverage for the shared actor selector (#983).
2
+ //
3
+ // The reported harm was a level holding several actors labelled
4
+ // BP_SnappyRoad2, a write aimed at the one the user had selected, and the edit
5
+ // landing on a road at the other end of the map with a success response. So
6
+ // the case that matters here is two actors sharing a label: the call must
7
+ // refuse, and it must name both candidates with their actorPath so the retry
8
+ // is a copy of one field.
9
+ //
10
+ // The two actors are transient verification actors (level(spawn_transient_actor)),
11
+ // which cannot be saved into the map and are destroyed before the test returns.
12
+ // run_automation_tests dispatches every EditorContext test against whatever
13
+ // project the bridge is attached to, so nothing here may persist anything.
14
+
15
+ #if WITH_DEV_AUTOMATION_TESTS
16
+
17
+ #include "HandlerRegistry.h"
18
+ #include "HandlerUtils.h"
19
+ #include "Handlers/LevelHandlers.h"
20
+ #include "Engine/World.h"
21
+ #include "EngineUtils.h"
22
+ #include "GameFramework/Actor.h"
23
+ #include "Misc/AutomationTest.h"
24
+
25
+ namespace
26
+ {
27
+ const TCHAR* MCPSelectorTestLabel = TEXT("UEMCP_AmbiguousSelectorTest");
28
+
29
+ TSharedPtr<FJsonObject> MCPSelectorRun(
30
+ FMCPHandlerRegistry& Registry,
31
+ const TCHAR* Method,
32
+ const TSharedPtr<FJsonObject>& Request)
33
+ {
34
+ const TSharedPtr<FJsonValue> Response = Registry.ExecuteHandler(Method, Request);
35
+ if (!Response.IsValid() || Response->Type != EJson::Object)
36
+ {
37
+ return nullptr;
38
+ }
39
+ return Response->AsObject();
40
+ }
41
+
42
+ /** Spawn one transient StaticMeshActor carrying the shared test label.
43
+ * Returns its object path, or an empty string when there is no world to
44
+ * spawn into (a context this test must skip rather than fail). */
45
+ FString MCPSelectorSpawn(FMCPHandlerRegistry& Registry)
46
+ {
47
+ TSharedPtr<FJsonObject> Request = MakeShared<FJsonObject>();
48
+ Request->SetStringField(TEXT("actorClass"), TEXT("StaticMeshActor"));
49
+ Request->SetStringField(TEXT("label"), MCPSelectorTestLabel);
50
+ Request->SetBoolField(TEXT("hideFromOutliner"), true);
51
+ const TSharedPtr<FJsonObject> Response =
52
+ MCPSelectorRun(Registry, TEXT("spawn_transient_actor"), Request);
53
+ if (!Response.IsValid() || !Response->GetBoolField(TEXT("success")))
54
+ {
55
+ return FString();
56
+ }
57
+ const TSharedPtr<FJsonObject> Actor = Response->GetObjectField(TEXT("actor"));
58
+ return Actor.IsValid() ? Actor->GetStringField(TEXT("actorPath")) : FString();
59
+ }
60
+
61
+ void MCPSelectorDestroy(FMCPHandlerRegistry& Registry, const FString& ActorPath)
62
+ {
63
+ if (ActorPath.IsEmpty()) return;
64
+ TSharedPtr<FJsonObject> Request = MakeShared<FJsonObject>();
65
+ Request->SetStringField(TEXT("actorPath"), ActorPath);
66
+ MCPSelectorRun(Registry, TEXT("destroy_transient_actor"), Request);
67
+ }
68
+
69
+ /** A boolean field that may be absent, without a JSON warning for the miss. */
70
+ bool MCPSelectorFlag(const TSharedPtr<FJsonObject>& Object, const TCHAR* Field)
71
+ {
72
+ bool bValue = false;
73
+ return Object.IsValid() && Object->TryGetBoolField(Field, bValue) && bValue;
74
+ }
75
+
76
+ /** Every actorPath in an ambiguity refusal's candidates array. */
77
+ TArray<FString> MCPSelectorCandidatePaths(const TSharedPtr<FJsonObject>& Response)
78
+ {
79
+ TArray<FString> Paths;
80
+ if (!Response.IsValid()) return Paths;
81
+ const TArray<TSharedPtr<FJsonValue>>* Candidates = nullptr;
82
+ if (!Response->TryGetArrayField(TEXT("candidates"), Candidates) || !Candidates) return Paths;
83
+ for (const TSharedPtr<FJsonValue>& Entry : *Candidates)
84
+ {
85
+ if (!Entry.IsValid() || Entry->Type != EJson::Object) continue;
86
+ FString Path;
87
+ if (Entry->AsObject()->TryGetStringField(TEXT("actorPath"), Path)) Paths.Add(Path);
88
+ }
89
+ return Paths;
90
+ }
91
+ }
92
+
93
+ IMPLEMENT_SIMPLE_AUTOMATION_TEST(
94
+ FMCPActorSelectorAmbiguityTest,
95
+ "UE.MCP.Level.ActorSelector.DuplicateLabelIsRefused",
96
+ EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
97
+
98
+ bool FMCPActorSelectorAmbiguityTest::RunTest(const FString& Parameters)
99
+ {
100
+ FMCPHandlerRegistry Registry;
101
+ FLevelHandlers::RegisterHandlers(Registry);
102
+
103
+ UWorld* World = GetEditorWorld();
104
+ if (!World)
105
+ {
106
+ // No editor world in this context, and this test may not open a map.
107
+ return true;
108
+ }
109
+
110
+ const FString FirstPath = MCPSelectorSpawn(Registry);
111
+ if (FirstPath.IsEmpty())
112
+ {
113
+ return true;
114
+ }
115
+ const FString SecondPath = MCPSelectorSpawn(Registry);
116
+ if (SecondPath.IsEmpty())
117
+ {
118
+ MCPSelectorDestroy(Registry, FirstPath);
119
+ return true;
120
+ }
121
+
122
+ TestNotEqual(TEXT("the two same-labelled actors have different object paths"), FirstPath, SecondPath);
123
+
124
+ // The search itself sees both, which is the fact every refusal rests on.
125
+ {
126
+ TArray<AActor*> Matches;
127
+ MCPCollectActorsByToken(World, MCPSelectorTestLabel, EMCPActorMatch::Label, Matches);
128
+ TestEqual(TEXT("the label names both actors"), Matches.Num(), 2);
129
+
130
+ // Sorted by object path, so two runs produce the same candidate order
131
+ // and the error is reproducible rather than iterator-dependent.
132
+ if (Matches.Num() == 2)
133
+ {
134
+ TestTrue(TEXT("matches come back in a stable path order"),
135
+ Matches[0]->GetPathName().Compare(Matches[1]->GetPathName()) < 0);
136
+ }
137
+ }
138
+
139
+ // The path selector resolves to exactly one, with no label involved.
140
+ {
141
+ AActor* ByPath = MCPFindActorByPath(World, FirstPath);
142
+ TestTrue(TEXT("the path resolves"), ByPath != nullptr);
143
+ if (ByPath) TestEqual(TEXT("and to the actor it names"), ByPath->GetPathName(), FirstPath);
144
+ }
145
+
146
+ // The reported case: a label matching two actors is refused, and the
147
+ // refusal names both candidates with their actorPath.
148
+ {
149
+ TSharedPtr<FJsonObject> Request = MakeShared<FJsonObject>();
150
+ Request->SetStringField(TEXT("actorLabel"), MCPSelectorTestLabel);
151
+ const TSharedPtr<FJsonObject> Response =
152
+ MCPSelectorRun(Registry, TEXT("get_actor_details"), Request);
153
+ TestTrue(TEXT("the ambiguous call answers"), Response.IsValid());
154
+ if (Response.IsValid())
155
+ {
156
+ TestFalse(TEXT("an ambiguous label fails rather than picking one"),
157
+ Response->GetBoolField(TEXT("success")));
158
+ TestTrue(TEXT("and says it was ambiguous"),
159
+ MCPSelectorFlag(Response, TEXT("ambiguous")));
160
+ TestEqual(TEXT("and reports how many actors matched"),
161
+ static_cast<int32>(Response->GetNumberField(TEXT("matchCount"))), 2);
162
+ TestEqual(TEXT("and names the selector that was ambiguous"),
163
+ Response->GetStringField(TEXT("selector")), FString(TEXT("actorLabel")));
164
+ TestTrue(TEXT("and the message points at actorPath for the retry"),
165
+ Response->GetStringField(TEXT("error")).Contains(TEXT("actorPath")));
166
+
167
+ const TArray<FString> Paths = MCPSelectorCandidatePaths(Response);
168
+ TestEqual(TEXT("both candidates are listed"), Paths.Num(), 2);
169
+ TestTrue(TEXT("the first actor is named"), Paths.Contains(FirstPath));
170
+ TestTrue(TEXT("the second actor is named"), Paths.Contains(SecondPath));
171
+ }
172
+ }
173
+
174
+ // A write refuses on the same terms. This is the half that mattered: the
175
+ // report was a silent edit, not a silent read.
176
+ {
177
+ TSharedPtr<FJsonObject> Request = MakeShared<FJsonObject>();
178
+ Request->SetStringField(TEXT("actorLabel"), MCPSelectorTestLabel);
179
+ Request->SetStringField(TEXT("propertyName"), TEXT("bHidden"));
180
+ Request->SetBoolField(TEXT("value"), true);
181
+ const TSharedPtr<FJsonObject> Response =
182
+ MCPSelectorRun(Registry, TEXT("set_actor_property"), Request);
183
+ if (Response.IsValid())
184
+ {
185
+ TestFalse(TEXT("an ambiguous write is refused"), Response->GetBoolField(TEXT("success")));
186
+ TestTrue(TEXT("and is refused for ambiguity, not for anything else"),
187
+ MCPSelectorFlag(Response, TEXT("ambiguous")));
188
+ TestEqual(TEXT("naming both candidates"), MCPSelectorCandidatePaths(Response).Num(), 2);
189
+ }
190
+ }
191
+
192
+ // delete_actor is idempotent on a miss, but "already deleted" would be a
193
+ // lie while two namesakes are still standing.
194
+ {
195
+ TSharedPtr<FJsonObject> Request = MakeShared<FJsonObject>();
196
+ Request->SetStringField(TEXT("actorLabel"), MCPSelectorTestLabel);
197
+ const TSharedPtr<FJsonObject> Response =
198
+ MCPSelectorRun(Registry, TEXT("delete_actor"), Request);
199
+ if (Response.IsValid())
200
+ {
201
+ TestFalse(TEXT("an ambiguous delete is refused"), Response->GetBoolField(TEXT("success")));
202
+ TestTrue(TEXT("and does not claim the actor was already deleted"),
203
+ MCPSelectorFlag(Response, TEXT("ambiguous")));
204
+ }
205
+ }
206
+
207
+ // The unambiguous selector gets through, and answers about the actor it
208
+ // names rather than its namesake.
209
+ {
210
+ TSharedPtr<FJsonObject> Request = MakeShared<FJsonObject>();
211
+ Request->SetStringField(TEXT("actorPath"), SecondPath);
212
+ const TSharedPtr<FJsonObject> Response =
213
+ MCPSelectorRun(Registry, TEXT("get_actor_details"), Request);
214
+ TestTrue(TEXT("actorPath alone resolves"),
215
+ Response.IsValid() && Response->GetBoolField(TEXT("success")));
216
+ if (Response.IsValid() && Response->GetBoolField(TEXT("success")))
217
+ {
218
+ TestEqual(TEXT("and answers about that actor"),
219
+ Response->GetStringField(TEXT("path")), SecondPath);
220
+ TestEqual(TEXT("and returns actorPath so the round trip closes"),
221
+ Response->GetStringField(TEXT("actorPath")), SecondPath);
222
+ }
223
+ }
224
+
225
+ // Both given: the path wins, because it is the one that cannot be wrong.
226
+ {
227
+ TSharedPtr<FJsonObject> Request = MakeShared<FJsonObject>();
228
+ Request->SetStringField(TEXT("actorLabel"), MCPSelectorTestLabel);
229
+ Request->SetStringField(TEXT("actorPath"), FirstPath);
230
+ const TSharedPtr<FJsonObject> Response =
231
+ MCPSelectorRun(Registry, TEXT("get_actor_details"), Request);
232
+ TestTrue(TEXT("a path alongside an ambiguous label still resolves"),
233
+ Response.IsValid() && Response->GetBoolField(TEXT("success")));
234
+ if (Response.IsValid() && Response->GetBoolField(TEXT("success")))
235
+ {
236
+ TestEqual(TEXT("and the path decides"), Response->GetStringField(TEXT("actorPath")), FirstPath);
237
+ }
238
+ }
239
+
240
+ // A path that names nothing is an error about the path. Falling through to
241
+ // the label would demote a precise miss to a fuzzy hit, which is the same
242
+ // class of bug this issue is about.
243
+ {
244
+ TSharedPtr<FJsonObject> Request = MakeShared<FJsonObject>();
245
+ Request->SetStringField(TEXT("actorLabel"), MCPSelectorTestLabel);
246
+ Request->SetStringField(TEXT("actorPath"), TEXT("/Game/NoSuchMap.NoSuchMap:PersistentLevel.NoSuchActor_0"));
247
+ const TSharedPtr<FJsonObject> Response =
248
+ MCPSelectorRun(Registry, TEXT("get_actor_details"), Request);
249
+ if (Response.IsValid())
250
+ {
251
+ TestFalse(TEXT("an unresolvable actorPath fails"), Response->GetBoolField(TEXT("success")));
252
+ TestTrue(TEXT("and the message names the path, not the label"),
253
+ Response->GetStringField(TEXT("error")).Contains(TEXT("NoSuchActor_0")));
254
+ }
255
+ }
256
+
257
+ MCPSelectorDestroy(Registry, FirstPath);
258
+ MCPSelectorDestroy(Registry, SecondPath);
259
+
260
+ // The cleanup really happened: a later test must not inherit two actors
261
+ // sharing a label.
262
+ {
263
+ TArray<AActor*> Remaining;
264
+ MCPCollectActorsByToken(World, MCPSelectorTestLabel, EMCPActorMatch::Label, Remaining);
265
+ TestEqual(TEXT("both verification actors were destroyed"), Remaining.Num(), 0);
266
+ }
267
+
268
+ return true;
269
+ }
270
+
271
+ IMPLEMENT_SIMPLE_AUTOMATION_TEST(
272
+ FMCPActorSelectorContractTest,
273
+ "UE.MCP.Level.ActorSelector.Contract",
274
+ EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
275
+
276
+ bool FMCPActorSelectorContractTest::RunTest(const FString& Parameters)
277
+ {
278
+ FMCPHandlerRegistry Registry;
279
+ FLevelHandlers::RegisterHandlers(Registry);
280
+
281
+ // Neither selector is a parameter error that names both, so a caller who
282
+ // only knows actorLabel learns that actorPath exists.
283
+ {
284
+ const TSharedPtr<FJsonValue> Response =
285
+ Registry.ExecuteHandler(TEXT("get_actor_details"), MakeShared<FJsonObject>());
286
+ if (Response.IsValid() && Response->Type == EJson::Object)
287
+ {
288
+ const TSharedPtr<FJsonObject> Object = Response->AsObject();
289
+ TestFalse(TEXT("no selector fails"), Object->GetBoolField(TEXT("success")));
290
+ const FString Error = Object->GetStringField(TEXT("error"));
291
+ TestTrue(TEXT("and names actorLabel"), Error.Contains(TEXT("actorLabel")));
292
+ TestTrue(TEXT("and names actorPath"), Error.Contains(TEXT("actorPath")));
293
+ }
294
+ }
295
+
296
+ // The refusal is a shape a caller can branch on without parsing prose.
297
+ {
298
+ UWorld* World = GetEditorWorld();
299
+ TArray<AActor*> Candidates;
300
+ if (World)
301
+ {
302
+ for (TActorIterator<AActor> It(World); It && Candidates.Num() < 2; ++It)
303
+ {
304
+ if (IsValid(*It)) Candidates.Add(*It);
305
+ }
306
+ }
307
+ if (Candidates.Num() == 2)
308
+ {
309
+ const TSharedPtr<FJsonValue> Error = MCPAmbiguousActorError(
310
+ TEXT("SharedLabel"), TEXT("actorLabel"), TEXT("actorPath"), TEXT("editor label"), Candidates);
311
+ TestTrue(TEXT("the refusal is an object"), Error.IsValid() && Error->Type == EJson::Object);
312
+ if (Error.IsValid() && Error->Type == EJson::Object)
313
+ {
314
+ const TSharedPtr<FJsonObject> Object = Error->AsObject();
315
+ TestFalse(TEXT("it is a failure"), Object->GetBoolField(TEXT("success")));
316
+ TestTrue(TEXT("flagged ambiguous"), MCPSelectorFlag(Object, TEXT("ambiguous")));
317
+ TestTrue(TEXT("and MCPIsAmbiguousActorError agrees"), MCPIsAmbiguousActorError(Error));
318
+ TestEqual(TEXT("echoing the value that was ambiguous"),
319
+ Object->GetStringField(TEXT("selectorValue")), FString(TEXT("SharedLabel")));
320
+ TestEqual(TEXT("with a candidate row per actor"),
321
+ MCPSelectorCandidatePaths(Object).Num(), 2);
322
+
323
+ // Each row carries enough to tell two namesakes apart without a
324
+ // follow-up call: class, folder and location, not just a path.
325
+ const TArray<TSharedPtr<FJsonValue>>* Rows = nullptr;
326
+ if (Object->TryGetArrayField(TEXT("candidates"), Rows) && Rows && Rows->Num() > 0)
327
+ {
328
+ const TSharedPtr<FJsonObject> Row = (*Rows)[0]->AsObject();
329
+ TestTrue(TEXT("a candidate row carries actorLabel"), Row->HasField(TEXT("actorLabel")));
330
+ TestTrue(TEXT("a candidate row carries actorClass"), Row->HasField(TEXT("actorClass")));
331
+ TestTrue(TEXT("a candidate row carries location"), Row->HasField(TEXT("location")));
332
+ }
333
+ }
334
+ }
335
+ }
336
+
337
+ // A plain error is not mistaken for an ambiguity refusal.
338
+ TestFalse(TEXT("a plain error is not ambiguous"), MCPIsAmbiguousActorError(MCPError(TEXT("nope"))));
339
+ TestFalse(TEXT("an unset error is not ambiguous"), MCPIsAmbiguousActorError(TSharedPtr<FJsonValue>()));
340
+
341
+ return true;
342
+ }
343
+
344
+ #endif // WITH_DEV_AUTOMATION_TESTS
@@ -0,0 +1,60 @@
1
+ #if WITH_DEV_AUTOMATION_TESTS && (ENGINE_MAJOR_VERSION > 5 || (ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 8))
2
+
3
+ #include "Animation/AnimData/IAnimationDataController.h"
4
+ #include "Animation/AnimSequence.h"
5
+ #include "Animation/Skeleton.h"
6
+ #include "Misc/AutomationTest.h"
7
+ #include "ReferenceSkeleton.h"
8
+ #include "Sections/MovieSceneSkeletalAnimationSection.h"
9
+
10
+ IMPLEMENT_SIMPLE_AUTOMATION_TEST(
11
+ FAnimationControlRigRawTimelineRateScaleTest,
12
+ "UE.MCP.Animation.ControlRig.RawTimelineRateScale",
13
+ EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
14
+
15
+ bool FAnimationControlRigRawTimelineRateScaleTest::RunTest(const FString& Parameters)
16
+ {
17
+ const FFrameRate DisplayRate(25, 1);
18
+ USkeleton* Skeleton = NewObject<USkeleton>(GetTransientPackage());
19
+ {
20
+ FReferenceSkeletonModifier Modifier(Skeleton);
21
+ Modifier.Add(FMeshBoneInfo(TEXT("root"), TEXT("root"), INDEX_NONE), FTransform::Identity);
22
+ }
23
+ UAnimSequence* SourceAnimation = NewObject<UAnimSequence>(GetTransientPackage());
24
+ SourceAnimation->SetSkeleton(Skeleton);
25
+ IAnimationDataController& Controller = SourceAnimation->GetController();
26
+ Controller.InitializeModel();
27
+ Controller.OpenBracket(FText::FromString(TEXT("Build raw-timeline test animation")), false);
28
+ Controller.SetFrameRate(DisplayRate, false);
29
+ Controller.SetNumberOfFrames(FFrameNumber(40), false);
30
+ Controller.CloseBracket(false);
31
+ SourceAnimation->RateScale = 3.06608796f;
32
+
33
+ UMovieSceneSkeletalAnimationSection* Section =
34
+ NewObject<UMovieSceneSkeletalAnimationSection>(GetTransientPackage());
35
+ Section->Params.Animation = SourceAnimation;
36
+ Section->Params.PlayRate = 1.0f / SourceAnimation->RateScale;
37
+ Section->SetRange(TRange<FFrameNumber>(FFrameNumber(0), FFrameNumber(41)));
38
+
39
+ double PreviousTime = -1.0;
40
+ for (int32 Frame = 0; Frame <= 40; ++Frame)
41
+ {
42
+ const double ExpectedTime = static_cast<double>(Frame) / DisplayRate.AsDecimal();
43
+ const double MappedTime = Section->MapTimeToAnimation(FFrameTime(Frame), DisplayRate);
44
+ TestTrue(
45
+ *FString::Printf(TEXT("frame %d maps to raw source time"), Frame),
46
+ FMath::IsNearlyEqual(MappedTime, ExpectedTime, 0.0001));
47
+ TestTrue(
48
+ *FString::Printf(TEXT("frame %d does not loop backwards"), Frame),
49
+ MappedTime >= PreviousTime);
50
+ PreviousTime = MappedTime;
51
+ }
52
+
53
+ TestTrue(
54
+ TEXT("the final frame reaches the source end once"),
55
+ FMath::IsNearlyEqual(PreviousTime, SourceAnimation->GetPlayLength(), 0.0001));
56
+ TestEqual(TEXT("the source RateScale is unchanged"), SourceAnimation->RateScale, 3.06608796f);
57
+ return true;
58
+ }
59
+
60
+ #endif
@@ -0,0 +1,115 @@
1
+ // Coverage for the evaluated-pose reads - animation(sample_pose),
2
+ // animation(get_live_bone_transforms) and animation(measure_natural_speed) -
3
+ // that is safe to run anywhere.
4
+ //
5
+ // run_automation_tests dispatches every EditorContext/EngineFilter test in the
6
+ // process when it is called without a filter, against whatever project the
7
+ // bridge is attached to. All three are reads, and none of the cases below name
8
+ // a real asset or actor, so the suite holds in any project. Evaluating an actual
9
+ // clip is exercised by the smoke suite against the dedicated test project.
10
+
11
+ #if WITH_DEV_AUTOMATION_TESTS
12
+
13
+ #include "HandlerRegistry.h"
14
+ #include "Handlers/AnimationHandlers.h"
15
+ #include "Misc/AutomationTest.h"
16
+
17
+ namespace
18
+ {
19
+ TSharedPtr<FJsonObject> MakePoseResponseObject(const TSharedPtr<FJsonValue>& Response)
20
+ {
21
+ return (Response.IsValid() && Response->Type == EJson::Object) ? Response->AsObject() : nullptr;
22
+ }
23
+ }
24
+
25
+ IMPLEMENT_SIMPLE_AUTOMATION_TEST(
26
+ FAnimationPoseRegistrationTest,
27
+ "UE.MCP.Animation.Pose.RegistrationAndValidation",
28
+ EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
29
+
30
+ bool FAnimationPoseRegistrationTest::RunTest(const FString& Parameters)
31
+ {
32
+ FMCPHandlerRegistry Registry;
33
+ FAnimationHandlers::RegisterHandlers(Registry);
34
+ TestTrue(TEXT("sample_pose is registered"), Registry.HasHandler(TEXT("sample_pose")));
35
+ TestTrue(TEXT("get_live_bone_transforms is registered"), Registry.HasHandler(TEXT("get_live_bone_transforms")));
36
+ TestTrue(TEXT("measure_natural_speed is registered"), Registry.HasHandler(TEXT("measure_natural_speed")));
37
+
38
+ // sample_pose without an asset names the parameter it wants.
39
+ {
40
+ const TSharedPtr<FJsonObject> Missing = MakePoseResponseObject(
41
+ Registry.ExecuteHandler(TEXT("sample_pose"), MakeShared<FJsonObject>()));
42
+ TestTrue(TEXT("sample_pose missing assetPath returns an object"), Missing.IsValid());
43
+ if (Missing.IsValid())
44
+ {
45
+ TestFalse(TEXT("sample_pose missing assetPath is unsuccessful"), Missing->GetBoolField(TEXT("success")));
46
+ TestTrue(TEXT("sample_pose missing assetPath names assetPath"),
47
+ Missing->GetStringField(TEXT("error")).Contains(TEXT("assetPath")));
48
+ }
49
+ }
50
+
51
+ // An unresolvable asset reports the path rather than dereferencing null.
52
+ {
53
+ TSharedPtr<FJsonObject> Request = MakeShared<FJsonObject>();
54
+ Request->SetStringField(TEXT("assetPath"), TEXT("/Game/UEMCP/DoesNotExist_SamplePoseTest"));
55
+ const TSharedPtr<FJsonObject> NotFound = MakePoseResponseObject(
56
+ Registry.ExecuteHandler(TEXT("sample_pose"), Request));
57
+ TestTrue(TEXT("sample_pose unknown asset returns an object"), NotFound.IsValid());
58
+ if (NotFound.IsValid())
59
+ {
60
+ TestFalse(TEXT("sample_pose unknown asset is unsuccessful"), NotFound->GetBoolField(TEXT("success")));
61
+ TestTrue(TEXT("sample_pose unknown asset echoes the path"),
62
+ NotFound->GetStringField(TEXT("error")).Contains(TEXT("DoesNotExist_SamplePoseTest")));
63
+ }
64
+ }
65
+
66
+ // measure_natural_speed validates its foot set before it touches an asset,
67
+ // because a speed measured without knowing which bones are feet is a number
68
+ // with no meaning rather than an approximation.
69
+ {
70
+ TSharedPtr<FJsonObject> Request = MakeShared<FJsonObject>();
71
+ Request->SetStringField(TEXT("assetPath"), TEXT("/Game/UEMCP/DoesNotExist_NaturalSpeedTest"));
72
+ const TSharedPtr<FJsonObject> Missing = MakePoseResponseObject(
73
+ Registry.ExecuteHandler(TEXT("measure_natural_speed"), Request));
74
+ TestTrue(TEXT("measure_natural_speed missing footBones returns an object"), Missing.IsValid());
75
+ if (Missing.IsValid())
76
+ {
77
+ TestFalse(TEXT("measure_natural_speed missing footBones is unsuccessful"), Missing->GetBoolField(TEXT("success")));
78
+ TestTrue(TEXT("measure_natural_speed missing footBones names footBones"),
79
+ Missing->GetStringField(TEXT("error")).Contains(TEXT("footBones")));
80
+ }
81
+ }
82
+
83
+ // get_live_bone_transforms needs an actor, and rejects a space it cannot
84
+ // serve before it goes looking for one.
85
+ {
86
+ const TSharedPtr<FJsonObject> Missing = MakePoseResponseObject(
87
+ Registry.ExecuteHandler(TEXT("get_live_bone_transforms"), MakeShared<FJsonObject>()));
88
+ TestTrue(TEXT("get_live_bone_transforms missing actorLabel returns an object"), Missing.IsValid());
89
+ if (Missing.IsValid())
90
+ {
91
+ TestFalse(TEXT("get_live_bone_transforms missing actorLabel is unsuccessful"), Missing->GetBoolField(TEXT("success")));
92
+ TestTrue(TEXT("get_live_bone_transforms missing actorLabel names actorLabel"),
93
+ Missing->GetStringField(TEXT("error")).Contains(TEXT("actorLabel")));
94
+ }
95
+ }
96
+ {
97
+ TSharedPtr<FJsonObject> Request = MakeShared<FJsonObject>();
98
+ Request->SetStringField(TEXT("actorLabel"), TEXT("UEMCP_DoesNotExist_LiveBoneTest"));
99
+ Request->SetStringField(TEXT("space"), TEXT("skeleton"));
100
+ const TSharedPtr<FJsonObject> Rejected = MakePoseResponseObject(
101
+ Registry.ExecuteHandler(TEXT("get_live_bone_transforms"), Request));
102
+ TestTrue(TEXT("get_live_bone_transforms bad space returns an object"), Rejected.IsValid());
103
+ if (Rejected.IsValid())
104
+ {
105
+ TestFalse(TEXT("get_live_bone_transforms bad space is unsuccessful"), Rejected->GetBoolField(TEXT("success")));
106
+ const FString Error = Rejected->GetStringField(TEXT("error"));
107
+ TestTrue(TEXT("get_live_bone_transforms bad space lists world"), Error.Contains(TEXT("world")));
108
+ TestTrue(TEXT("get_live_bone_transforms bad space lists component"), Error.Contains(TEXT("component")));
109
+ }
110
+ }
111
+
112
+ return true;
113
+ }
114
+
115
+ #endif // WITH_DEV_AUTOMATION_TESTS