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,666 @@
1
+ // Bounded batch projection for ISMC/HISMC instances.
2
+ // Registration stays in LevelHandlers.cpp::RegisterHandlers.
3
+
4
+ #include "LevelHandlers.h"
5
+ #include "LevelHandlers_InstanceProjection_Internal.h"
6
+ #include "HandlerUtils.h"
7
+ #include "Editor.h"
8
+ #include "ScopedTransaction.h"
9
+ #include "CollisionQueryParams.h"
10
+ #include "Components/InstancedStaticMeshComponent.h"
11
+ #include "Components/PrimitiveComponent.h"
12
+ #include "Engine/CollisionProfile.h"
13
+ #include "Engine/EngineTypes.h"
14
+ #include "Engine/HitResult.h"
15
+ #include "Engine/World.h"
16
+ #include "EngineUtils.h"
17
+ #include "GameFramework/Actor.h"
18
+ #include "UObject/Package.h"
19
+
20
+ namespace UEMCPInstanceProjection
21
+ {
22
+ bool ParseMissPolicy(const FString& InValue, EMissPolicy& OutPolicy)
23
+ {
24
+ const FString Value = InValue.TrimStartAndEnd();
25
+ if (Value.Equals(TEXT("error"), ESearchCase::IgnoreCase))
26
+ {
27
+ OutPolicy = EMissPolicy::Error;
28
+ return true;
29
+ }
30
+ if (Value.Equals(TEXT("skip"), ESearchCase::IgnoreCase))
31
+ {
32
+ OutPolicy = EMissPolicy::Skip;
33
+ return true;
34
+ }
35
+ return false;
36
+ }
37
+
38
+ FVector ApplySurfaceOffset(const FVector& ImpactPoint, const FVector& ImpactNormal, double SurfaceOffset)
39
+ {
40
+ return ImpactPoint + ImpactNormal.GetSafeNormal() * SurfaceOffset;
41
+ }
42
+ }
43
+
44
+ namespace
45
+ {
46
+ using namespace UEMCPInstanceProjection;
47
+
48
+ UInstancedStaticMeshComponent* ResolveProjectionComponent(AActor* Actor, const FString& ComponentName, FString& OutError)
49
+ {
50
+ if (!Actor)
51
+ {
52
+ OutError = TEXT("Target actor is null");
53
+ return nullptr;
54
+ }
55
+
56
+ TArray<UInstancedStaticMeshComponent*> Matches;
57
+ for (UActorComponent* Component : Actor->GetComponents())
58
+ {
59
+ if (UInstancedStaticMeshComponent* ISMC = Cast<UInstancedStaticMeshComponent>(Component))
60
+ {
61
+ if (ComponentName.IsEmpty() || ISMC->GetName() == ComponentName)
62
+ {
63
+ Matches.Add(ISMC);
64
+ }
65
+ }
66
+ }
67
+
68
+ if (Matches.Num() == 1) return Matches[0];
69
+ if (Matches.IsEmpty())
70
+ {
71
+ OutError = ComponentName.IsEmpty()
72
+ ? FString::Printf(TEXT("Actor '%s' has no InstancedStaticMeshComponent"), *Actor->GetActorLabel())
73
+ : FString::Printf(TEXT("Actor '%s' has no InstancedStaticMeshComponent named '%s'"), *Actor->GetActorLabel(), *ComponentName);
74
+ return nullptr;
75
+ }
76
+
77
+ TArray<FString> Names;
78
+ for (const UInstancedStaticMeshComponent* Match : Matches) Names.Add(Match->GetName());
79
+ OutError = FString::Printf(
80
+ TEXT("Actor '%s' has %d instanced mesh components (%s); pass componentName to select exactly one"),
81
+ *Actor->GetActorLabel(), Matches.Num(), *FString::Join(Names, TEXT(", ")));
82
+ return nullptr;
83
+ }
84
+
85
+ bool ResolveProjectionTraceChannel(const FString& InName, ECollisionChannel& OutChannel, FString& OutResolvedName)
86
+ {
87
+ FString Name = InName.TrimStartAndEnd();
88
+ if (Name.StartsWith(TEXT("ECC_"))) Name = Name.RightChop(4);
89
+ if (Name.IsEmpty()) Name = TEXT("Visibility");
90
+
91
+ if (const UCollisionProfile* Profile = UCollisionProfile::Get())
92
+ {
93
+ for (int32 Index = 0; Index < ECC_MAX; ++Index)
94
+ {
95
+ const FName ChannelName = Profile->ReturnChannelNameFromContainerIndex(Index);
96
+ if (!ChannelName.IsNone() && ChannelName.ToString().Equals(Name, ESearchCase::IgnoreCase))
97
+ {
98
+ OutChannel = static_cast<ECollisionChannel>(Index);
99
+ OutResolvedName = ChannelName.ToString();
100
+ return true;
101
+ }
102
+ }
103
+ }
104
+
105
+ struct FBuiltInChannel { const TCHAR* Name; ECollisionChannel Channel; };
106
+ static const FBuiltInChannel BuiltIns[] = {
107
+ { TEXT("WorldStatic"), ECC_WorldStatic }, { TEXT("WorldDynamic"), ECC_WorldDynamic },
108
+ { TEXT("Pawn"), ECC_Pawn }, { TEXT("Visibility"), ECC_Visibility },
109
+ { TEXT("Camera"), ECC_Camera }, { TEXT("PhysicsBody"), ECC_PhysicsBody },
110
+ { TEXT("Vehicle"), ECC_Vehicle }, { TEXT("Destructible"), ECC_Destructible },
111
+ };
112
+ for (const FBuiltInChannel& Entry : BuiltIns)
113
+ {
114
+ if (Name.Equals(Entry.Name, ESearchCase::IgnoreCase))
115
+ {
116
+ OutChannel = Entry.Channel;
117
+ OutResolvedName = Entry.Name;
118
+ return true;
119
+ }
120
+ }
121
+ return false;
122
+ }
123
+
124
+ TSharedPtr<FJsonValue> ParseInstanceIndices(
125
+ const TSharedPtr<FJsonObject>& Params,
126
+ int32 InstanceCount,
127
+ int32 MaxInstances,
128
+ TArray<int32>& OutIndices)
129
+ {
130
+ const TArray<TSharedPtr<FJsonValue>>* Values = nullptr;
131
+ if (!Params->HasField(TEXT("instanceIndices")))
132
+ {
133
+ if (InstanceCount > MaxInstances)
134
+ {
135
+ return MCPError(FString::Printf(
136
+ TEXT("Refusing to project all %d instances because maxInstances is %d. Pass a bounded instanceIndices subset or explicitly raise maxInstances up to %d."),
137
+ InstanceCount, MaxInstances, HardMaxInstances));
138
+ }
139
+ OutIndices.Reserve(InstanceCount);
140
+ for (int32 Index = 0; Index < InstanceCount; ++Index) OutIndices.Add(Index);
141
+ return nullptr;
142
+ }
143
+ if (!Params->TryGetArrayField(TEXT("instanceIndices"), Values) || !Values)
144
+ {
145
+ return MCPError(TEXT("instanceIndices must be an array of integers"));
146
+ }
147
+
148
+ if (Values->IsEmpty()) return MCPError(TEXT("instanceIndices must not be empty"));
149
+ if (Values->Num() > MaxInstances)
150
+ {
151
+ return MCPError(FString::Printf(
152
+ TEXT("Refusing to project %d requested instances because maxInstances is %d. Raise maxInstances only for an intentionally bounded batch (hard limit %d)."),
153
+ Values->Num(), MaxInstances, HardMaxInstances));
154
+ }
155
+ TSet<int32> Seen;
156
+ OutIndices.Reserve(Values->Num());
157
+ for (int32 Position = 0; Position < Values->Num(); ++Position)
158
+ {
159
+ double Raw = 0.0;
160
+ if (!(*Values)[Position].IsValid()
161
+ || !(*Values)[Position]->TryGetNumber(Raw)
162
+ || !FMath::IsFinite(Raw)
163
+ || Raw != FMath::RoundToDouble(Raw))
164
+ {
165
+ return MCPError(FString::Printf(TEXT("instanceIndices[%d] must be an integer"), Position));
166
+ }
167
+ const int64 WideIndex = FMath::RoundToInt64(Raw);
168
+ if (WideIndex < 0 || WideIndex >= InstanceCount)
169
+ {
170
+ return MCPError(FString::Printf(
171
+ TEXT("instanceIndices[%d] is %lld, outside the valid range 0..%d"),
172
+ Position, WideIndex, InstanceCount - 1));
173
+ }
174
+ const int32 Index = static_cast<int32>(WideIndex);
175
+ if (Seen.Contains(Index))
176
+ {
177
+ return MCPError(FString::Printf(TEXT("instanceIndices contains duplicate index %d"), Index));
178
+ }
179
+ Seen.Add(Index);
180
+ OutIndices.Add(Index);
181
+ }
182
+ OutIndices.Sort();
183
+ return nullptr;
184
+ }
185
+
186
+ bool HitMatchesSurface(
187
+ const FHitResult& Hit,
188
+ UClass* RequiredClass,
189
+ const TSet<FString>& RequiredLabels)
190
+ {
191
+ AActor* HitActor = Hit.GetActor();
192
+ if (!HitActor) return false;
193
+ if (RequiredClass && !HitActor->IsA(RequiredClass)) return false;
194
+ if (!RequiredLabels.IsEmpty() && !RequiredLabels.Contains(HitActor->GetActorLabel())) return false;
195
+ return true;
196
+ }
197
+
198
+ bool FindMatchingSurfaceHit(
199
+ UWorld* World,
200
+ const FVector& Start,
201
+ const FVector& End,
202
+ ECollisionChannel Channel,
203
+ bool bTraceComplex,
204
+ AActor* SourceActor,
205
+ UClass* RequiredClass,
206
+ const TSet<FString>& RequiredLabels,
207
+ FHitResult& OutHit,
208
+ bool& bOutRejectedHit,
209
+ bool& bOutReachedFilterLimit)
210
+ {
211
+ bOutRejectedHit = false;
212
+ bOutReachedFilterLimit = false;
213
+ FCollisionQueryParams Query(SCENE_QUERY_STAT(MCPSnapInstancesToSurface), bTraceComplex);
214
+ Query.bReturnFaceIndex = bTraceComplex;
215
+ Query.AddIgnoredActor(SourceActor);
216
+
217
+ // A channel trace stops at its closest blocking hit. Iteratively ignore a
218
+ // rejected blocker so a requested landscape or named surface behind it can
219
+ // still be reached without executing an unbounded trace loop.
220
+ for (int32 Attempt = 0; Attempt < MaxFilteredSurfaceHits; ++Attempt)
221
+ {
222
+ FHitResult Candidate;
223
+ if (!World->LineTraceSingleByChannel(Candidate, Start, End, Channel, Query))
224
+ {
225
+ return false;
226
+ }
227
+ if (HitMatchesSurface(Candidate, RequiredClass, RequiredLabels))
228
+ {
229
+ OutHit = Candidate;
230
+ return true;
231
+ }
232
+
233
+ bOutRejectedHit = true;
234
+ if (AActor* RejectedActor = Candidate.GetActor())
235
+ {
236
+ Query.AddIgnoredActor(RejectedActor);
237
+ }
238
+ else if (UPrimitiveComponent* RejectedComponent = Candidate.GetComponent())
239
+ {
240
+ Query.AddIgnoredComponent(RejectedComponent);
241
+ }
242
+ else
243
+ {
244
+ return false;
245
+ }
246
+ }
247
+
248
+ bOutReachedFilterLimit = true;
249
+ return false;
250
+ }
251
+
252
+ struct FProjectionRun
253
+ {
254
+ int32 StartIndex = INDEX_NONE;
255
+ TArray<FTransform> Before;
256
+ TArray<FTransform> After;
257
+ };
258
+
259
+ TArray<FProjectionRun> BuildProjectionRuns(const FProjectionPlan& Plan)
260
+ {
261
+ TArray<FProjectionRun> Runs;
262
+ for (const FProjectionPlanEntry& Entry : Plan.Entries)
263
+ {
264
+ if (!Entry.bHit) continue;
265
+ if (Runs.IsEmpty() || Entry.InstanceIndex != Runs.Last().StartIndex + Runs.Last().After.Num())
266
+ {
267
+ FProjectionRun& NewRun = Runs.AddDefaulted_GetRef();
268
+ NewRun.StartIndex = Entry.InstanceIndex;
269
+ }
270
+ Runs.Last().Before.Add(Entry.Before);
271
+ Runs.Last().After.Add(Entry.After);
272
+ }
273
+ return Runs;
274
+ }
275
+
276
+ TSharedPtr<FJsonObject> MakeProjectionSample(const FProjectionPlanEntry& Entry)
277
+ {
278
+ TSharedPtr<FJsonObject> Sample = MakeShared<FJsonObject>();
279
+ Sample->SetNumberField(TEXT("index"), Entry.InstanceIndex);
280
+ Sample->SetBoolField(TEXT("hit"), Entry.bHit);
281
+ Sample->SetObjectField(TEXT("from"), MCPVec3ToJsonObject(Entry.Before.GetLocation()));
282
+ if (Entry.bHit)
283
+ {
284
+ Sample->SetObjectField(TEXT("to"), MCPVec3ToJsonObject(Entry.After.GetLocation()));
285
+ Sample->SetStringField(TEXT("hitActorLabel"), Entry.HitActorLabel);
286
+ Sample->SetStringField(TEXT("hitActorClass"), Entry.HitActorClass);
287
+ }
288
+ else
289
+ {
290
+ Sample->SetStringField(TEXT("missReason"), Entry.MissReason);
291
+ }
292
+ return Sample;
293
+ }
294
+ }
295
+
296
+ TSharedPtr<FJsonValue> UEMCPInstanceProjection::SnapInstancesToSurfaceInWorld(
297
+ UWorld* World,
298
+ const TSharedPtr<FJsonObject>& Params)
299
+ {
300
+ using namespace UEMCPInstanceProjection;
301
+ if (!World) return MCPError(TEXT("World is not available"));
302
+ if (!Params) return MCPError(TEXT("params must be an object"));
303
+
304
+ FString ActorLabel;
305
+ if (TSharedPtr<FJsonValue> Error = RequireStringAlt(Params, TEXT("actorLabel"), TEXT("actorPath"), ActorLabel)) return Error;
306
+ TSharedPtr<FJsonValue> ActorErr;
307
+ AActor* Actor = MCPResolveActor(World, Params, ActorErr);
308
+ if (!Actor) return ActorErr;
309
+ ActorLabel = Actor->GetActorLabel();
310
+
311
+ FString ComponentName;
312
+ if (Params->HasField(TEXT("componentName"))
313
+ && !Params->TryGetStringField(TEXT("componentName"), ComponentName))
314
+ {
315
+ return MCPError(TEXT("componentName must be a string"));
316
+ }
317
+ FString ComponentError;
318
+ UInstancedStaticMeshComponent* ISMC = ResolveProjectionComponent(
319
+ Actor, ComponentName, ComponentError);
320
+ if (!ISMC) return MCPError(ComponentError);
321
+
322
+ const int32 InstanceCount = ISMC->GetInstanceCount();
323
+ if (InstanceCount <= 0) return MCPError(FString::Printf(TEXT("Component '%s' has no instances"), *ISMC->GetName()));
324
+
325
+ int32 MaxInstances = DefaultMaxInstances;
326
+ if (Params->HasField(TEXT("maxInstances")))
327
+ {
328
+ double RawMaxInstances = 0.0;
329
+ if (!Params->TryGetNumberField(TEXT("maxInstances"), RawMaxInstances)
330
+ || !FMath::IsFinite(RawMaxInstances)
331
+ || RawMaxInstances != FMath::RoundToDouble(RawMaxInstances))
332
+ {
333
+ return MCPError(TEXT("maxInstances must be an integer"));
334
+ }
335
+ if (RawMaxInstances < 1.0 || RawMaxInstances > HardMaxInstances)
336
+ {
337
+ return MCPError(FString::Printf(
338
+ TEXT("maxInstances must be between 1 and %d (default %d)"),
339
+ HardMaxInstances, DefaultMaxInstances));
340
+ }
341
+ MaxInstances = static_cast<int32>(RawMaxInstances);
342
+ }
343
+
344
+ TArray<int32> Indices;
345
+ if (TSharedPtr<FJsonValue> Error = ParseInstanceIndices(
346
+ Params, InstanceCount, MaxInstances, Indices))
347
+ {
348
+ return Error;
349
+ }
350
+ if (Indices.Num() > MaxInstances)
351
+ {
352
+ return MCPError(FString::Printf(
353
+ TEXT("Refusing to project %d instances because maxInstances is %d. Pass a bounded instanceIndices subset or explicitly raise maxInstances up to %d."),
354
+ Indices.Num(), MaxInstances, HardMaxInstances));
355
+ }
356
+
357
+ FVector Direction(0.0, 0.0, -1.0);
358
+ if (Params->HasField(TEXT("direction")))
359
+ {
360
+ if (TSharedPtr<FJsonValue> Error = RequireVec3(Params, TEXT("direction"), Direction)) return Error;
361
+ }
362
+ if (Direction.ContainsNaN() || !Direction.Normalize())
363
+ {
364
+ return MCPError(TEXT("direction must be a finite, non-zero vector"));
365
+ }
366
+ double TraceStartOffset = 1000.0;
367
+ double TraceDistance = 200000.0;
368
+ double SurfaceOffset = 0.0;
369
+ if (Params->HasField(TEXT("traceStartOffset"))
370
+ && !Params->TryGetNumberField(TEXT("traceStartOffset"), TraceStartOffset))
371
+ {
372
+ return MCPError(TEXT("traceStartOffset must be a number"));
373
+ }
374
+ if (Params->HasField(TEXT("traceDistance"))
375
+ && !Params->TryGetNumberField(TEXT("traceDistance"), TraceDistance))
376
+ {
377
+ return MCPError(TEXT("traceDistance must be a number"));
378
+ }
379
+ if (Params->HasField(TEXT("surfaceOffset"))
380
+ && !Params->TryGetNumberField(TEXT("surfaceOffset"), SurfaceOffset))
381
+ {
382
+ return MCPError(TEXT("surfaceOffset must be a number"));
383
+ }
384
+ if (!FMath::IsFinite(TraceStartOffset) || TraceStartOffset < 0.0)
385
+ {
386
+ return MCPError(TEXT("traceStartOffset must be a finite number greater than or equal to zero"));
387
+ }
388
+ if (!FMath::IsFinite(TraceDistance) || TraceDistance <= 0.0)
389
+ {
390
+ return MCPError(TEXT("traceDistance must be a finite number greater than zero"));
391
+ }
392
+ if (!FMath::IsFinite(SurfaceOffset)) return MCPError(TEXT("surfaceOffset must be finite"));
393
+
394
+ EMissPolicy MissPolicy = EMissPolicy::Error;
395
+ FString MissPolicyText = TEXT("error");
396
+ if (Params->HasField(TEXT("onMiss"))
397
+ && !Params->TryGetStringField(TEXT("onMiss"), MissPolicyText))
398
+ {
399
+ return MCPError(TEXT("onMiss must be a string"));
400
+ }
401
+ if (!ParseMissPolicy(MissPolicyText, MissPolicy))
402
+ {
403
+ return MCPError(FString::Printf(TEXT("Unknown onMiss policy '%s'; expected 'error' or 'skip'"), *MissPolicyText));
404
+ }
405
+
406
+ UClass* SurfaceClass = nullptr;
407
+ FString SurfaceClassSpec;
408
+ if (Params->HasField(TEXT("surfaceActorClass"))
409
+ && !Params->TryGetStringField(TEXT("surfaceActorClass"), SurfaceClassSpec))
410
+ {
411
+ return MCPError(TEXT("surfaceActorClass must be a string"));
412
+ }
413
+ if (!SurfaceClassSpec.IsEmpty())
414
+ {
415
+ SurfaceClass = MCPResolveClassOfType(SurfaceClassSpec, AActor::StaticClass());
416
+ if (!SurfaceClass) return MCPClassNotFoundError(SurfaceClassSpec, TEXT("surfaceActorClass"));
417
+ }
418
+
419
+ TSet<FString> SurfaceActorLabels;
420
+ const TArray<TSharedPtr<FJsonValue>>* LabelValues = nullptr;
421
+ if (Params->HasField(TEXT("surfaceActorLabels"))
422
+ && (!Params->TryGetArrayField(TEXT("surfaceActorLabels"), LabelValues) || !LabelValues))
423
+ {
424
+ return MCPError(TEXT("surfaceActorLabels must be an array of non-empty strings"));
425
+ }
426
+ if (LabelValues && LabelValues->IsEmpty())
427
+ {
428
+ return MCPError(TEXT("surfaceActorLabels must not be empty when provided; omit it for no label filter"));
429
+ }
430
+ if (LabelValues)
431
+ {
432
+ for (int32 Position = 0; Position < LabelValues->Num(); ++Position)
433
+ {
434
+ FString Label;
435
+ if (!(*LabelValues)[Position].IsValid() || !(*LabelValues)[Position]->TryGetString(Label))
436
+ {
437
+ return MCPError(FString::Printf(TEXT("surfaceActorLabels[%d] must be a non-empty string"), Position));
438
+ }
439
+ Label = Label.TrimStartAndEnd();
440
+ if (Label.IsEmpty())
441
+ {
442
+ return MCPError(FString::Printf(TEXT("surfaceActorLabels[%d] must be a non-empty string"), Position));
443
+ }
444
+ SurfaceActorLabels.Add(Label);
445
+ }
446
+ }
447
+
448
+ ECollisionChannel Channel = ECC_Visibility;
449
+ FString ChannelName;
450
+ FString RequestedChannel = TEXT("Visibility");
451
+ if (Params->HasField(TEXT("channel"))
452
+ && !Params->TryGetStringField(TEXT("channel"), RequestedChannel))
453
+ {
454
+ return MCPError(TEXT("channel must be a string"));
455
+ }
456
+ if (!ResolveProjectionTraceChannel(RequestedChannel, Channel, ChannelName))
457
+ {
458
+ return MCPError(FString::Printf(TEXT("Unknown collision channel '%s'"), *RequestedChannel));
459
+ }
460
+ bool bTraceComplex = false;
461
+ bool bDryRun = true;
462
+ if (Params->HasField(TEXT("traceComplex"))
463
+ && !Params->TryGetBoolField(TEXT("traceComplex"), bTraceComplex))
464
+ {
465
+ return MCPError(TEXT("traceComplex must be a boolean"));
466
+ }
467
+ if (Params->HasField(TEXT("dryRun"))
468
+ && !Params->TryGetBoolField(TEXT("dryRun"), bDryRun))
469
+ {
470
+ return MCPError(TEXT("dryRun must be a boolean"));
471
+ }
472
+
473
+ FProjectionPlan Plan;
474
+ Plan.Entries.Reserve(Indices.Num());
475
+ for (const int32 Index : Indices)
476
+ {
477
+ FProjectionPlanEntry Entry;
478
+ Entry.InstanceIndex = Index;
479
+ if (!ISMC->GetInstanceTransform(Index, Entry.Before, true))
480
+ {
481
+ return MCPError(FString::Printf(TEXT("Failed to read instance transform at index %d"), Index));
482
+ }
483
+ if (Entry.Before.ContainsNaN())
484
+ {
485
+ return MCPError(FString::Printf(TEXT("Instance transform at index %d is not finite"), Index));
486
+ }
487
+
488
+ const FVector Start = Entry.Before.GetLocation() - Direction * TraceStartOffset;
489
+ const FVector End = Start + Direction * (TraceStartOffset + TraceDistance);
490
+ if (Start.ContainsNaN() || End.ContainsNaN())
491
+ {
492
+ return MCPError(FString::Printf(
493
+ TEXT("Trace bounds overflowed for instance %d; reduce traceStartOffset or traceDistance"),
494
+ Index));
495
+ }
496
+ FHitResult AcceptedHit;
497
+ bool bRejectedHit = false;
498
+ bool bReachedFilterLimit = false;
499
+ if (!FindMatchingSurfaceHit(
500
+ World,
501
+ Start,
502
+ End,
503
+ Channel,
504
+ bTraceComplex,
505
+ Actor,
506
+ SurfaceClass,
507
+ SurfaceActorLabels,
508
+ AcceptedHit,
509
+ bRejectedHit,
510
+ bReachedFilterLimit))
511
+ {
512
+ Entry.MissReason = bReachedFilterLimit
513
+ ? TEXT("surface_filter_limit_reached")
514
+ : (bRejectedHit ? TEXT("no_hit_matched_surface_filter") : TEXT("trace_missed"));
515
+ ++Plan.MissCount;
516
+ }
517
+ else
518
+ {
519
+ Entry.bHit = true;
520
+ Entry.After = Entry.Before;
521
+ Entry.After.SetLocation(ApplySurfaceOffset(AcceptedHit.ImpactPoint, AcceptedHit.ImpactNormal, SurfaceOffset));
522
+ if (Entry.After.ContainsNaN())
523
+ {
524
+ return MCPError(FString::Printf(
525
+ TEXT("Projected transform overflowed for instance %d; reduce surfaceOffset"),
526
+ Index));
527
+ }
528
+ if (AActor* HitActor = AcceptedHit.GetActor())
529
+ {
530
+ Entry.HitActorLabel = HitActor->GetActorLabel();
531
+ Entry.HitActorClass = HitActor->GetClass()->GetPathName();
532
+ }
533
+ ++Plan.HitCount;
534
+ Plan.MinDistance = FMath::Min(Plan.MinDistance, static_cast<double>(AcceptedHit.Distance));
535
+ Plan.MaxDistance = FMath::Max(Plan.MaxDistance, static_cast<double>(AcceptedHit.Distance));
536
+ }
537
+ Plan.Entries.Add(MoveTemp(Entry));
538
+ }
539
+
540
+ TArray<TSharedPtr<FJsonValue>> Samples;
541
+ const int32 SampleCount = FMath::Min(Plan.Entries.Num(), MaxReportedSamples);
542
+ Samples.Reserve(SampleCount);
543
+ for (int32 Position = 0; Position < SampleCount; ++Position)
544
+ {
545
+ Samples.Add(MakeShared<FJsonValueObject>(MakeProjectionSample(Plan.Entries[Position])));
546
+ }
547
+
548
+ if (MissPolicy == EMissPolicy::Error && Plan.MissCount > 0)
549
+ {
550
+ auto Result = MCPSuccess();
551
+ Result->SetBoolField(TEXT("success"), false);
552
+ Result->SetStringField(TEXT("error"), FString::Printf(
553
+ TEXT("Preflight found %d misses; onMiss='error' prevents any mutation."), Plan.MissCount));
554
+ Result->SetBoolField(TEXT("dryRun"), bDryRun);
555
+ Result->SetBoolField(TEXT("preflightPassed"), false);
556
+ Result->SetBoolField(TEXT("mutationPerformed"), false);
557
+ Result->SetNumberField(TEXT("requestedCount"), Plan.Entries.Num());
558
+ Result->SetNumberField(TEXT("hitCount"), Plan.HitCount);
559
+ Result->SetNumberField(TEXT("missCount"), Plan.MissCount);
560
+ Result->SetNumberField(TEXT("sampleCount"), Samples.Num());
561
+ Result->SetBoolField(TEXT("samplesTruncated"), Plan.Entries.Num() > Samples.Num());
562
+ Result->SetArrayField(TEXT("samples"), Samples);
563
+ return MCPResult(Result);
564
+ }
565
+
566
+ bool bMutationPerformed = false;
567
+ if (!bDryRun && Plan.HitCount > 0)
568
+ {
569
+ const bool bShouldActuallyTransact = GEditor && World->WorldType == EWorldType::Editor;
570
+ FScopedTransaction Transaction(
571
+ NSLOCTEXT("UEMCP", "SnapInstancesToSurface", "Snap Instanced Meshes To Surface"),
572
+ bShouldActuallyTransact);
573
+ UPackage* Package = ISMC->GetOutermost();
574
+ const bool bPackageWasDirty = Package && Package->IsDirty();
575
+ Actor->Modify();
576
+ ISMC->Modify();
577
+ const TArray<FProjectionRun> Runs = BuildProjectionRuns(Plan);
578
+ int32 AppliedRunCount = 0;
579
+ for (const FProjectionRun& Run : Runs)
580
+ {
581
+ if (!ISMC->BatchUpdateInstancesTransforms(
582
+ Run.StartIndex,
583
+ Run.After,
584
+ true,
585
+ false,
586
+ true))
587
+ {
588
+ // Treat a false batch result as potentially partial. Restore the
589
+ // current run as well as every previously accepted run.
590
+ bool bRollbackSucceeded = ISMC->BatchUpdateInstancesTransforms(
591
+ Run.StartIndex,
592
+ Run.Before,
593
+ true,
594
+ false,
595
+ true);
596
+ for (int32 RollbackIndex = AppliedRunCount - 1; RollbackIndex >= 0; --RollbackIndex)
597
+ {
598
+ const FProjectionRun& AppliedRun = Runs[RollbackIndex];
599
+ bRollbackSucceeded &= ISMC->BatchUpdateInstancesTransforms(
600
+ AppliedRun.StartIndex,
601
+ AppliedRun.Before,
602
+ true,
603
+ false,
604
+ true);
605
+ }
606
+ ISMC->MarkRenderStateDirty();
607
+ if (Package && !bPackageWasDirty) Package->ClearDirtyFlag();
608
+ Transaction.Cancel();
609
+ return MCPError(FString::Printf(
610
+ TEXT("Unexpected apply failure at instance %d after a successful preflight; rollback %s"),
611
+ Run.StartIndex,
612
+ bRollbackSucceeded ? TEXT("succeeded") : TEXT("failed")));
613
+ }
614
+ ++AppliedRunCount;
615
+ }
616
+ ISMC->MarkRenderStateDirty();
617
+ // PKG_Transient no longer exists in UE 5.8's EPackageFlags. Transience is
618
+ // an object flag, and the engine's own transient package is a distinct
619
+ // object, so test both rather than a package flag that was removed.
620
+ if (Package && Package != GetTransientPackage() && !Package->HasAnyFlags(RF_Transient))
621
+ {
622
+ ISMC->MarkPackageDirty();
623
+ }
624
+ bMutationPerformed = true;
625
+ }
626
+
627
+ auto Result = MCPSuccess();
628
+ Result->SetBoolField(TEXT("dryRun"), bDryRun);
629
+ Result->SetBoolField(TEXT("preflightPassed"), true);
630
+ Result->SetBoolField(TEXT("mutationPerformed"), bMutationPerformed);
631
+ Result->SetStringField(TEXT("actorLabel"), ActorLabel);
632
+ Result->SetStringField(TEXT("actorPath"), Actor->GetPathName());
633
+ Result->SetStringField(TEXT("componentName"), ISMC->GetName());
634
+ Result->SetStringField(TEXT("componentClass"), ISMC->GetClass()->GetPathName());
635
+ Result->SetStringField(TEXT("channel"), ChannelName);
636
+ Result->SetStringField(TEXT("onMiss"), MissPolicy == EMissPolicy::Error ? TEXT("error") : TEXT("skip"));
637
+ Result->SetNumberField(TEXT("requestedCount"), Plan.Entries.Num());
638
+ Result->SetNumberField(TEXT("hitCount"), Plan.HitCount);
639
+ Result->SetNumberField(TEXT("missCount"), Plan.MissCount);
640
+ Result->SetNumberField(TEXT("updatedCount"), bMutationPerformed ? Plan.HitCount : 0);
641
+ Result->SetNumberField(TEXT("sampleCount"), Samples.Num());
642
+ Result->SetBoolField(TEXT("samplesTruncated"), Plan.Entries.Num() > Samples.Num());
643
+ Result->SetArrayField(TEXT("samples"), Samples);
644
+ if (Plan.HitCount > 0)
645
+ {
646
+ Result->SetNumberField(TEXT("minHitDistance"), Plan.MinDistance);
647
+ Result->SetNumberField(TEXT("maxHitDistance"), Plan.MaxDistance);
648
+ }
649
+ if (bMutationPerformed)
650
+ {
651
+ MCPSetUpdated(Result);
652
+ Result->SetStringField(TEXT("dirtyPackage"), ISMC->GetOutermost()->GetName());
653
+ Result->SetStringField(TEXT("note"), TEXT("Changes are dirty in memory and were not saved."));
654
+ }
655
+ else if (bDryRun)
656
+ {
657
+ Result->SetStringField(TEXT("note"), TEXT("Dry run only; re-call with dryRun=false to apply the preflighted projection."));
658
+ }
659
+ return MCPResult(Result);
660
+ }
661
+
662
+ TSharedPtr<FJsonValue> FLevelHandlers::SnapInstancesToSurface(const TSharedPtr<FJsonObject>& Params)
663
+ {
664
+ REQUIRE_EDITOR_WORLD(World);
665
+ return UEMCPInstanceProjection::SnapInstancesToSurfaceInWorld(World, Params);
666
+ }