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,453 @@
1
+ // Coverage for the DataTable row write path and the JSON property setter it
2
+ // runs on, for the three data-loss bugs they carried (#928, #929, #935).
3
+ //
4
+ // run_automation_tests dispatches every EditorContext/EngineFilter test in the
5
+ // process when it is called without a filter, against whatever project the
6
+ // bridge is attached to, so nothing here may touch a real asset. The DataTable
7
+ // under test is built in the transient package and lives for the length of one
8
+ // test; the property cases run on stack structs and on temporary property
9
+ // buffers, and read only the reflection data of engine types.
10
+
11
+ #if WITH_DEV_AUTOMATION_TESTS
12
+
13
+ #include "HandlerRegistry.h"
14
+ #include "HandlerUtils.h"
15
+ #include "HandlerJsonProperty.h"
16
+ #include "Handlers/AssetHandlers.h"
17
+ #include "Dom/JsonObject.h"
18
+ #include "Dom/JsonValue.h"
19
+ #include "Engine/AssetManagerTypes.h"
20
+ #include "Engine/DataTable.h"
21
+ #include "GameFramework/Actor.h"
22
+ #include "GameFramework/DefaultPawn.h"
23
+ #include "GameFramework/GameModeBase.h"
24
+ #include "Misc/AutomationTest.h"
25
+ #include "Misc/Guid.h"
26
+ #include "UObject/PerPlatformProperties.h"
27
+ #include "UObject/UnrealType.h"
28
+
29
+ namespace
30
+ {
31
+ // FPerPlatformInt is the row struct throughout: it is a plain engine USTRUCT
32
+ // with exactly the shape issue #929 is about. "Default" is an int32 with an
33
+ // explicit constructor default, and "PerPlatform" is a TMap with none, so a
34
+ // row rebuilt from struct defaults keeps the first and loses the second.
35
+ const TCHAR* const DataTableRowWriteScalarField = TEXT("Default");
36
+ const TCHAR* const DataTableRowWriteMapField = TEXT("PerPlatform");
37
+
38
+ /** A DataTable in the transient package, keyed on FPerPlatformInt rows. */
39
+ UDataTable* MakeTransientPerPlatformTable()
40
+ {
41
+ const FName TableName(*FString::Printf(TEXT("DT_UEMCP_RowWrite_%s"), *FGuid::NewGuid().ToString(EGuidFormats::Digits)));
42
+ UDataTable* Table = NewObject<UDataTable>(GetTransientPackage(), TableName);
43
+ if (!Table) return nullptr;
44
+ Table->RowStruct = FPerPlatformInt::StaticStruct();
45
+ return Table;
46
+ }
47
+
48
+ /** Add one row, built by hand so the test never depends on the write path it
49
+ * is about to exercise. */
50
+ void AddPerPlatformRow(UDataTable* Table, const TCHAR* RowName, int32 Default, const TMap<FName, int32>& PerPlatform)
51
+ {
52
+ FPerPlatformInt Row;
53
+ Row.Default = Default;
54
+ #if WITH_EDITORONLY_DATA
55
+ Row.PerPlatform = PerPlatform;
56
+ #else
57
+ (void)PerPlatform;
58
+ #endif
59
+ Table->AddRow(FName(RowName), *reinterpret_cast<FTableRowBase*>(&Row));
60
+ }
61
+
62
+ /** The row memory the table owns, read straight out of the row map so nothing
63
+ * in the read path can paper over a bad write. */
64
+ const FPerPlatformInt* FindPerPlatformRow(const UDataTable* Table, const TCHAR* RowName)
65
+ {
66
+ uint8* const* Found = Table->GetRowMap().Find(FName(RowName));
67
+ return (Found && *Found) ? reinterpret_cast<const FPerPlatformInt*>(*Found) : nullptr;
68
+ }
69
+
70
+ /** Params for asset(set_datatable_cell) against a transient table. */
71
+ TSharedPtr<FJsonObject> MakeCellWriteParams(
72
+ const UDataTable* Table,
73
+ const TCHAR* RowName,
74
+ const TCHAR* FieldName,
75
+ const TSharedPtr<FJsonValue>& Value)
76
+ {
77
+ TSharedPtr<FJsonObject> Params = MakeShared<FJsonObject>();
78
+ Params->SetStringField(TEXT("assetPath"), Table->GetPathName());
79
+ Params->SetStringField(TEXT("rowName"), RowName);
80
+ Params->SetStringField(TEXT("fieldName"), FieldName);
81
+ Params->SetField(TEXT("value"), Value);
82
+ return Params;
83
+ }
84
+
85
+ bool ResponseSucceeded(const TSharedPtr<FJsonValue>& Response, FString& OutError)
86
+ {
87
+ if (!Response.IsValid() || Response->Type != EJson::Object)
88
+ {
89
+ OutError = TEXT("handler returned no JSON object");
90
+ return false;
91
+ }
92
+ const TSharedPtr<FJsonObject> Obj = Response->AsObject();
93
+ bool bSuccess = false;
94
+ Obj->TryGetBoolField(TEXT("success"), bSuccess);
95
+ if (!bSuccess)
96
+ {
97
+ Obj->TryGetStringField(TEXT("error"), OutError);
98
+ }
99
+ return bSuccess;
100
+ }
101
+ }
102
+
103
+ // ─────────────────────────────────────────────────────────────────────────────
104
+ // #929: one named field changes, everything else keeps the bytes it had.
105
+ // ─────────────────────────────────────────────────────────────────────────────
106
+
107
+ IMPLEMENT_SIMPLE_AUTOMATION_TEST(
108
+ FDataTableSingleFieldWritePreservesRowTest,
109
+ "UE.MCP.Asset.DataTable.SingleFieldWritePreservesTheRest",
110
+ EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
111
+
112
+ bool FDataTableSingleFieldWritePreservesRowTest::RunTest(const FString& Parameters)
113
+ {
114
+ #if !WITH_EDITORONLY_DATA
115
+ AddInfo(TEXT("FPerPlatformInt::PerPlatform is editor-only data; nothing to assert here."));
116
+ return true;
117
+ #else
118
+ // The table lives in the transient package, which is not a registered
119
+ // asset, so the handler's save step declines and says so. That is the
120
+ // expected cost of keeping the test off a real asset.
121
+ AddExpectedError(TEXT("SaveLoadedAsset failed"), EAutomationExpectedErrorFlags::Contains, 0);
122
+
123
+ UDataTable* Table = MakeTransientPerPlatformTable();
124
+ if (!TestNotNull(TEXT("transient DataTable was created"), Table)) return false;
125
+ FGCRootScope TableRoot(Table);
126
+
127
+ TMap<FName, int32> FirstRowOverrides;
128
+ FirstRowOverrides.Add(TEXT("Windows"), 41);
129
+ FirstRowOverrides.Add(TEXT("Mac"), 42);
130
+ AddPerPlatformRow(Table, TEXT("RowA"), 1, FirstRowOverrides);
131
+
132
+ TMap<FName, int32> SecondRowOverrides;
133
+ SecondRowOverrides.Add(TEXT("Windows"), 43);
134
+ AddPerPlatformRow(Table, TEXT("RowB"), 2, SecondRowOverrides);
135
+
136
+ FMCPHandlerRegistry Registry;
137
+ FAssetHandlers::RegisterHandlers(Registry);
138
+ TestTrue(TEXT("set_datatable_cell is registered"), Registry.HasHandler(TEXT("set_datatable_cell")));
139
+
140
+ // Stated up front so a resolution failure reads as one, rather than as a
141
+ // pile of downstream assertions about a row that was never reached.
142
+ if (!TestTrue(
143
+ TEXT("the handler resolves the transient table by path"),
144
+ MCPLoadAssetObject(Table->GetPathName()) == Table))
145
+ {
146
+ return false;
147
+ }
148
+
149
+ FString Error;
150
+ const TSharedPtr<FJsonValue> Response = Registry.ExecuteHandler(
151
+ TEXT("set_datatable_cell"),
152
+ MakeCellWriteParams(Table, TEXT("RowA"), DataTableRowWriteScalarField, MakeShared<FJsonValueNumber>(7)));
153
+ const bool bWritten = ResponseSucceeded(Response, Error);
154
+ if (!TestTrue(FString::Printf(TEXT("cell write succeeded (%s)"), *Error), bWritten))
155
+ {
156
+ return false;
157
+ }
158
+
159
+ const FPerPlatformInt* RowA = FindPerPlatformRow(Table, TEXT("RowA"));
160
+ if (!TestNotNull(TEXT("the edited row still exists"), RowA)) return false;
161
+
162
+ TestEqual(TEXT("the named field took the new value"), RowA->Default, 7);
163
+
164
+ // The assertion the issue is about. PerPlatform declares no default, so a
165
+ // row rebuilt from the struct's defaults comes back empty.
166
+ TestEqual(TEXT("the untouched TMap kept every entry"), RowA->PerPlatform.Num(), 2);
167
+ if (const int32* Windows = RowA->PerPlatform.Find(TEXT("Windows")))
168
+ {
169
+ TestEqual(TEXT("the untouched TMap kept its first value"), *Windows, 41);
170
+ }
171
+ else
172
+ {
173
+ AddError(TEXT("the untouched TMap lost its 'Windows' entry"));
174
+ }
175
+ if (const int32* Mac = RowA->PerPlatform.Find(TEXT("Mac")))
176
+ {
177
+ TestEqual(TEXT("the untouched TMap kept its second value"), *Mac, 42);
178
+ }
179
+ else
180
+ {
181
+ AddError(TEXT("the untouched TMap lost its 'Mac' entry"));
182
+ }
183
+
184
+ // A write to one row must not reach any other row.
185
+ const FPerPlatformInt* RowB = FindPerPlatformRow(Table, TEXT("RowB"));
186
+ if (!TestNotNull(TEXT("the row that was not named still exists"), RowB)) return false;
187
+ TestEqual(TEXT("the other row kept its scalar"), RowB->Default, 2);
188
+ TestEqual(TEXT("the other row kept its TMap"), RowB->PerPlatform.Num(), 1);
189
+
190
+ TestEqual(TEXT("no row was added or dropped"), Table->GetRowMap().Num(), 2);
191
+ return true;
192
+ #endif
193
+ }
194
+
195
+ // ─────────────────────────────────────────────────────────────────────────────
196
+ // #935: a rejected write leaves the row exactly as it was found.
197
+ // ─────────────────────────────────────────────────────────────────────────────
198
+
199
+ IMPLEMENT_SIMPLE_AUTOMATION_TEST(
200
+ FDataTableRejectedWriteLeavesRowIntactTest,
201
+ "UE.MCP.Asset.DataTable.RejectedWriteLeavesRowIntact",
202
+ EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
203
+
204
+ bool FDataTableRejectedWriteLeavesRowIntactTest::RunTest(const FString& Parameters)
205
+ {
206
+ #if !WITH_EDITORONLY_DATA
207
+ AddInfo(TEXT("FPerPlatformInt::PerPlatform is editor-only data; nothing to assert here."));
208
+ return true;
209
+ #else
210
+ UDataTable* Table = MakeTransientPerPlatformTable();
211
+ if (!TestNotNull(TEXT("transient DataTable was created"), Table)) return false;
212
+ FGCRootScope TableRoot(Table);
213
+
214
+ TMap<FName, int32> Overrides;
215
+ Overrides.Add(TEXT("Windows"), 41);
216
+ AddPerPlatformRow(Table, TEXT("RowA"), 1, Overrides);
217
+
218
+ FMCPHandlerRegistry Registry;
219
+ FAssetHandlers::RegisterHandlers(Registry);
220
+
221
+ if (!TestTrue(
222
+ TEXT("the handler resolves the transient table by path"),
223
+ MCPLoadAssetObject(Table->GetPathName()) == Table))
224
+ {
225
+ return false;
226
+ }
227
+
228
+ // A field the row struct does not have. Nothing may be written, and no
229
+ // save may be attempted, so this test expects no log errors at all.
230
+ FString Error;
231
+ const TSharedPtr<FJsonValue> Response = Registry.ExecuteHandler(
232
+ TEXT("set_datatable_cell"),
233
+ MakeCellWriteParams(Table, TEXT("RowA"), TEXT("NoSuchField"), MakeShared<FJsonValueNumber>(7)));
234
+ TestFalse(TEXT("a write to an unknown field fails"), ResponseSucceeded(Response, Error));
235
+ TestTrue(TEXT("the error names the field"), Error.Contains(TEXT("NoSuchField")));
236
+
237
+ const FPerPlatformInt* RowA = FindPerPlatformRow(Table, TEXT("RowA"));
238
+ if (!TestNotNull(TEXT("the row survived the rejected write"), RowA)) return false;
239
+ TestEqual(TEXT("the scalar is untouched"), RowA->Default, 1);
240
+ TestEqual(TEXT("the TMap is untouched"), RowA->PerPlatform.Num(), 1);
241
+ return true;
242
+ #endif
243
+ }
244
+
245
+ // ─────────────────────────────────────────────────────────────────────────────
246
+ // #935: reference kinds are classified most-derived first, so the branch that
247
+ // knows how to resolve a class is the one that runs for a class field.
248
+ // ─────────────────────────────────────────────────────────────────────────────
249
+
250
+ IMPLEMENT_SIMPLE_AUTOMATION_TEST(
251
+ FJsonPropertyReferenceKindTest,
252
+ "UE.MCP.Property.ReferenceKindClassification",
253
+ EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
254
+
255
+ bool FJsonPropertyReferenceKindTest::RunTest(const FString& Parameters)
256
+ {
257
+ using MCPJsonProperty::ERefKind;
258
+ using MCPJsonProperty::ClassifyReference;
259
+
260
+ UScriptStruct* TypeInfo = FPrimaryAssetTypeInfo::StaticStruct();
261
+
262
+ FProperty* SoftClassProp = TypeInfo->FindPropertyByName(TEXT("AssetBaseClass"));
263
+ FProperty* HardClassProp = TypeInfo->FindPropertyByName(TEXT("AssetBaseClassLoaded"));
264
+ FProperty* NameProp = TypeInfo->FindPropertyByName(TEXT("PrimaryAssetType"));
265
+ FProperty* ObjectProp = AActor::StaticClass()->FindPropertyByName(TEXT("RootComponent"));
266
+ FProperty* SubclassOfProp = AGameModeBase::StaticClass()->FindPropertyByName(TEXT("DefaultPawnClass"));
267
+
268
+ if (!TestNotNull(TEXT("FPrimaryAssetTypeInfo::AssetBaseClass exists"), SoftClassProp)) return false;
269
+ if (!TestNotNull(TEXT("FPrimaryAssetTypeInfo::AssetBaseClassLoaded exists"), HardClassProp)) return false;
270
+ if (!TestNotNull(TEXT("FPrimaryAssetTypeInfo::PrimaryAssetType exists"), NameProp)) return false;
271
+ if (!TestNotNull(TEXT("AActor::RootComponent exists"), ObjectProp)) return false;
272
+ if (!TestNotNull(TEXT("AGameModeBase::DefaultPawnClass exists"), SubclassOfProp)) return false;
273
+
274
+ // The whole point: FClassProperty derives from FObjectProperty and
275
+ // FSoftClassProperty from FSoftObjectProperty, so a chain that tested the
276
+ // base first answered "Object" and "SoftObject" for these two.
277
+ TestTrue(TEXT("a UClass* field classifies as a class"), ClassifyReference(HardClassProp) == ERefKind::Class);
278
+ TestTrue(TEXT("a TSubclassOf<> field classifies as a class"), ClassifyReference(SubclassOfProp) == ERefKind::Class);
279
+ TestTrue(TEXT("a TSoftClassPtr<> field classifies as a soft class"), ClassifyReference(SoftClassProp) == ERefKind::SoftClass);
280
+ TestTrue(TEXT("an object pointer classifies as an object"), ClassifyReference(ObjectProp) == ERefKind::Object);
281
+ TestTrue(TEXT("a name is not a reference"), ClassifyReference(NameProp) == ERefKind::NotAReference);
282
+ TestTrue(TEXT("a null property is not a reference"), ClassifyReference(nullptr) == ERefKind::NotAReference);
283
+
284
+ // A native class path resolves as written, and a class-typed field takes it.
285
+ UClass* Resolved = MCPJsonProperty::ResolveClassPath(TEXT("/Script/Engine.DefaultPawn"));
286
+ TestTrue(TEXT("a native class path resolves without a suffix"), Resolved == ADefaultPawn::StaticClass());
287
+
288
+ FDefaultConstructedPropertyElement HardClassValue(SubclassOfProp);
289
+ FString SetError;
290
+ const bool bSet = MCPJsonProperty::SetJsonOnProperty(
291
+ SubclassOfProp,
292
+ HardClassValue.GetObjAddress(),
293
+ MakeShared<FJsonValueString>(TEXT("/Script/Engine.DefaultPawn")),
294
+ SetError);
295
+ if (TestTrue(FString::Printf(TEXT("a native class path is accepted (%s)"), *SetError), bSet))
296
+ {
297
+ UObject* Stored = CastField<FClassProperty>(SubclassOfProp)->GetObjectPropertyValue(HardClassValue.GetObjAddress());
298
+ TestTrue(TEXT("the class field stores the class that was named"), Stored == ADefaultPawn::StaticClass());
299
+ }
300
+ return true;
301
+ }
302
+
303
+ // ─────────────────────────────────────────────────────────────────────────────
304
+ // #928: a native class path is stored as written, with no Blueprint suffix.
305
+ // ─────────────────────────────────────────────────────────────────────────────
306
+
307
+ IMPLEMENT_SIMPLE_AUTOMATION_TEST(
308
+ FSoftClassPathSuffixTest,
309
+ "UE.MCP.Property.SoftClassPathKeepsNativePath",
310
+ EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
311
+
312
+ bool FSoftClassPathSuffixTest::RunTest(const FString& Parameters)
313
+ {
314
+ FProperty* SoftClassProp = FPrimaryAssetTypeInfo::StaticStruct()->FindPropertyByName(TEXT("AssetBaseClass"));
315
+ if (!TestNotNull(TEXT("FPrimaryAssetTypeInfo::AssetBaseClass exists"), SoftClassProp)) return false;
316
+
317
+ FDefaultConstructedPropertyElement SoftClassValue(SoftClassProp);
318
+ const TSharedPtr<FJsonValue> Requested = MakeShared<FJsonValueString>(TEXT("/Script/Engine.DefaultPawn"));
319
+
320
+ FString SetError;
321
+ const bool bSet = MCPJsonProperty::SetJsonOnProperty(
322
+ SoftClassProp, SoftClassValue.GetObjAddress(), Requested, SetError);
323
+ if (!TestTrue(FString::Printf(TEXT("the soft class path is accepted (%s)"), *SetError), bSet)) return false;
324
+
325
+ const FString Stored =
326
+ CastField<FSoftClassProperty>(SoftClassProp)->GetPropertyValue(SoftClassValue.GetObjAddress()).ToString();
327
+ TestEqual(TEXT("the native path is stored verbatim"), Stored, FString(TEXT("/Script/Engine.DefaultPawn")));
328
+ TestFalse(TEXT("no Blueprint suffix was appended"), Stored.EndsWith(TEXT("_C")));
329
+
330
+ // Readback verification agrees, and notices when the stored value is not
331
+ // the one that was asked for.
332
+ FString Detail;
333
+ TestTrue(
334
+ TEXT("verification accepts the value that was stored"),
335
+ MCPJsonProperty::VerifyJsonOnProperty(SoftClassProp, SoftClassValue.GetObjAddress(), Requested, Detail));
336
+
337
+ CastField<FSoftClassProperty>(SoftClassProp)->SetPropertyValue(
338
+ SoftClassValue.GetObjAddress(), FSoftObjectPtr(FSoftObjectPath(TEXT("/Script/Engine.DefaultPawn_C"))));
339
+ Detail.Reset();
340
+ TestFalse(
341
+ TEXT("verification rejects a path that was rewritten"),
342
+ MCPJsonProperty::VerifyJsonOnProperty(SoftClassProp, SoftClassValue.GetObjAddress(), Requested, Detail));
343
+ TestTrue(TEXT("the mismatch reports what was stored"), Detail.Contains(TEXT("_C")));
344
+ return true;
345
+ }
346
+
347
+ // ─────────────────────────────────────────────────────────────────────────────
348
+ // The path comparison the readback verification depends on.
349
+ // ─────────────────────────────────────────────────────────────────────────────
350
+
351
+ IMPLEMENT_SIMPLE_AUTOMATION_TEST(
352
+ FAssetPathCompareTest,
353
+ "UE.MCP.Property.AssetPathComparison",
354
+ EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
355
+
356
+ bool FAssetPathCompareTest::RunTest(const FString& Parameters)
357
+ {
358
+ using MCPJsonProperty::NormalizeAssetPathForCompare;
359
+
360
+ // A Blueprint asset path, its long form, and its generated class all name
361
+ // the same thing.
362
+ const FString Expected(TEXT("/Game/Foo/BP_Thing"));
363
+ TestEqual(TEXT("short form"), NormalizeAssetPathForCompare(TEXT("/Game/Foo/BP_Thing")), Expected);
364
+ TestEqual(TEXT("long form"), NormalizeAssetPathForCompare(TEXT("/Game/Foo/BP_Thing.BP_Thing")), Expected);
365
+ TestEqual(TEXT("generated class"), NormalizeAssetPathForCompare(TEXT("/Game/Foo/BP_Thing.BP_Thing_C")), Expected);
366
+
367
+ // A native class path keeps its object name: the package leaf and the
368
+ // object name are different, so there is nothing to collapse.
369
+ TestEqual(
370
+ TEXT("native class path"),
371
+ NormalizeAssetPathForCompare(TEXT("/Script/Engine.DefaultPawn")),
372
+ FString(TEXT("/Script/Engine.DefaultPawn")));
373
+
374
+ // #928 in one line: the corrupted form of that path stays a different
375
+ // path, so the readback check can see it.
376
+ TestNotEqual(
377
+ TEXT("a suffixed native class path is not the same path"),
378
+ NormalizeAssetPathForCompare(TEXT("/Script/Engine.DefaultPawn_C")),
379
+ NormalizeAssetPathForCompare(TEXT("/Script/Engine.DefaultPawn")));
380
+
381
+ // An asset whose own name ends in _C still compares equal to itself.
382
+ TestEqual(
383
+ TEXT("an asset named with a _C suffix"),
384
+ NormalizeAssetPathForCompare(TEXT("/Game/Foo_C.Foo_C_C")),
385
+ NormalizeAssetPathForCompare(TEXT("/Game/Foo_C")));
386
+
387
+ // Two different assets do not.
388
+ TestNotEqual(
389
+ TEXT("different assets stay different"),
390
+ NormalizeAssetPathForCompare(TEXT("/Game/Foo/BP_Thing")),
391
+ NormalizeAssetPathForCompare(TEXT("/Game/Foo/BP_Other")));
392
+
393
+ // A subobject path is compared whole.
394
+ TestEqual(
395
+ TEXT("subobject path"),
396
+ NormalizeAssetPathForCompare(TEXT("/Game/Foo/BP_Thing.BP_Thing_C:Comp")),
397
+ FString(TEXT("/Game/Foo/BP_Thing.BP_Thing_C:Comp")));
398
+ return true;
399
+ }
400
+
401
+ // ─────────────────────────────────────────────────────────────────────────────
402
+ // #935: readback verification notices a container that did not store what it
403
+ // was handed, which is the shape the DataTable writes report on.
404
+ // ─────────────────────────────────────────────────────────────────────────────
405
+
406
+ IMPLEMENT_SIMPLE_AUTOMATION_TEST(
407
+ FReadbackVerificationTest,
408
+ "UE.MCP.Property.ReadbackVerification",
409
+ EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
410
+
411
+ bool FReadbackVerificationTest::RunTest(const FString& Parameters)
412
+ {
413
+ #if !WITH_EDITORONLY_DATA
414
+ AddInfo(TEXT("FPerPlatformInt::PerPlatform is editor-only data; nothing to assert here."));
415
+ return true;
416
+ #else
417
+ FProperty* MapProp = FPerPlatformInt::StaticStruct()->FindPropertyByName(DataTableRowWriteMapField);
418
+ if (!TestNotNull(TEXT("FPerPlatformInt::PerPlatform exists"), MapProp)) return false;
419
+
420
+ TSharedPtr<FJsonObject> MapJson = MakeShared<FJsonObject>();
421
+ MapJson->SetNumberField(TEXT("Windows"), 41);
422
+ MapJson->SetNumberField(TEXT("Mac"), 42);
423
+ const TSharedPtr<FJsonValue> Requested = MakeShared<FJsonValueObject>(MapJson);
424
+
425
+ FPerPlatformInt Value;
426
+ void* MapAddr = MapProp->ContainerPtrToValuePtr<void>(&Value);
427
+
428
+ FString SetError;
429
+ if (!TestTrue(
430
+ FString::Printf(TEXT("the map is written (%s)"), *SetError),
431
+ MCPJsonProperty::SetJsonOnProperty(MapProp, MapAddr, Requested, SetError)))
432
+ {
433
+ return false;
434
+ }
435
+
436
+ FString Detail;
437
+ TestTrue(
438
+ TEXT("verification accepts a map that stored every pair"),
439
+ MCPJsonProperty::VerifyJsonOnProperty(MapProp, MapAddr, Requested, Detail));
440
+
441
+ // Drop an entry behind the setter's back: this is what a write that
442
+ // reported success and stored less than it was handed looks like.
443
+ Value.PerPlatform.Remove(TEXT("Mac"));
444
+ Detail.Reset();
445
+ TestFalse(
446
+ TEXT("verification rejects a map that lost a pair"),
447
+ MCPJsonProperty::VerifyJsonOnProperty(MapProp, MapAddr, Requested, Detail));
448
+ TestTrue(TEXT("the mismatch says what was asked for"), Detail.Contains(TEXT("Windows")));
449
+ return true;
450
+ #endif
451
+ }
452
+
453
+ #endif // WITH_DEV_AUTOMATION_TESTS
@@ -0,0 +1,223 @@
1
+ #if WITH_DEV_AUTOMATION_TESTS
2
+
3
+ #include "HandlerFunctionCall.h"
4
+
5
+ #include "Engine/BrushBuilder.h"
6
+ #include "Kismet/KismetStringLibrary.h"
7
+ #include "Misc/AutomationTest.h"
8
+ #include "UObject/UnrealType.h"
9
+
10
+ /**
11
+ * #885: a UFUNCTION returning a container has to arrive as a container.
12
+ *
13
+ * Export text renders a TArray<FString> return as an empty string, so an
14
+ * array-returning accessor could not be read through the bridge at all. These
15
+ * assert the marshalling directly rather than through a socket, because the
16
+ * defect is in what the frame is turned into, not in how it is transported.
17
+ */
18
+ IMPLEMENT_SIMPLE_AUTOMATION_TEST(
19
+ FEditorFunctionCallContainerReturnTest,
20
+ "UE.MCP.Editor.FunctionCall.ContainerReturnMarshalling",
21
+ EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
22
+
23
+ bool FEditorFunctionCallContainerReturnTest::RunTest(const FString& Parameters)
24
+ {
25
+ // A real static UFUNCTION returning TArray<FString>, called the way
26
+ // invoke_static_function calls one: a frame, ProcessEvent on the CDO, then
27
+ // the outputs read back out of the frame.
28
+ UClass* LibClass = UKismetStringLibrary::StaticClass();
29
+ UFunction* Func = LibClass->FindFunctionByName(FName(TEXT("ParseIntoArray")));
30
+ if (!TestNotNull(TEXT("UKismetStringLibrary::ParseIntoArray is reflected"), Func))
31
+ {
32
+ return false;
33
+ }
34
+
35
+ TArray<uint8> Frame;
36
+ Frame.SetNumZeroed(Func->ParmsSize);
37
+ for (TFieldIterator<FProperty> It(Func); It && (It->PropertyFlags & CPF_Parm); ++It)
38
+ {
39
+ It->InitializeValue_InContainer(Frame.GetData());
40
+ }
41
+
42
+ if (FStrProperty* SourceProp = CastField<FStrProperty>(Func->FindPropertyByName(FName(TEXT("SourceString")))))
43
+ {
44
+ SourceProp->SetPropertyValue_InContainer(Frame.GetData(), TEXT("alpha,beta,gamma"));
45
+ }
46
+ if (FStrProperty* DelimProp = CastField<FStrProperty>(Func->FindPropertyByName(FName(TEXT("Delimiter")))))
47
+ {
48
+ DelimProp->SetPropertyValue_InContainer(Frame.GetData(), TEXT(","));
49
+ }
50
+ if (FBoolProperty* CullProp = CastField<FBoolProperty>(Func->FindPropertyByName(FName(TEXT("CullEmptyStrings")))))
51
+ {
52
+ CullProp->SetPropertyValue_InContainer(Frame.GetData(), true);
53
+ }
54
+
55
+ UObject* CDO = LibClass->GetDefaultObject();
56
+ CDO->ProcessEvent(Func, Frame.GetData());
57
+
58
+ TSharedPtr<FJsonObject> OutVals = MakeShared<FJsonObject>();
59
+ MCPFunctionCall::WriteOutputs(OutVals, Func, Frame.GetData(), CDO);
60
+ MCPFunctionCall::DestroyFrame(Func, Frame.GetData());
61
+
62
+ const TSharedPtr<FJsonValue> Returned = OutVals->TryGetField(TEXT("ReturnValue"));
63
+ if (!TestTrue(TEXT("the return value is present"), Returned.IsValid()))
64
+ {
65
+ return false;
66
+ }
67
+ // The whole bug: this used to be EJson::String, and empty at that.
68
+ if (!TestEqual(TEXT("a TArray<FString> return is a JSON array"), (int32)Returned->Type, (int32)EJson::Array))
69
+ {
70
+ return false;
71
+ }
72
+ const TArray<TSharedPtr<FJsonValue>>& Items = Returned->AsArray();
73
+ TestEqual(TEXT("every element survived"), Items.Num(), 3);
74
+ if (Items.Num() == 3)
75
+ {
76
+ TestEqual(TEXT("element 0"), Items[0]->AsString(), FString(TEXT("alpha")));
77
+ TestEqual(TEXT("element 1"), Items[1]->AsString(), FString(TEXT("beta")));
78
+ TestEqual(TEXT("element 2"), Items[2]->AsString(), FString(TEXT("gamma")));
79
+ }
80
+
81
+ return true;
82
+ }
83
+
84
+ /**
85
+ * The other half of #885: nothing but containers moved. Every existing reader
86
+ * of returnValues parses export text, so a scalar or a struct has to keep
87
+ * arriving as a JSON string.
88
+ */
89
+ IMPLEMENT_SIMPLE_AUTOMATION_TEST(
90
+ FEditorFunctionCallScalarReturnTest,
91
+ "UE.MCP.Editor.FunctionCall.ScalarReturnStaysExportText",
92
+ EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
93
+
94
+ bool FEditorFunctionCallScalarReturnTest::RunTest(const FString& Parameters)
95
+ {
96
+ FBuilderPoly Poly;
97
+ Poly.Direction = 7;
98
+ Poly.ItemName = TEXT("Face");
99
+
100
+ UScriptStruct* Struct = FBuilderPoly::StaticStruct();
101
+ FProperty* DirectionProp = Struct->FindPropertyByName(FName(TEXT("Direction")));
102
+ FProperty* NameProp = Struct->FindPropertyByName(FName(TEXT("ItemName")));
103
+ if (!TestNotNull(TEXT("FBuilderPoly::Direction is reflected"), DirectionProp)) return false;
104
+ if (!TestNotNull(TEXT("FBuilderPoly::ItemName is reflected"), NameProp)) return false;
105
+
106
+ const TSharedPtr<FJsonValue> DirectionJson =
107
+ MCPFunctionCall::OutputToJson(DirectionProp, DirectionProp->ContainerPtrToValuePtr<void>(&Poly), nullptr);
108
+ TestEqual(TEXT("an int return stays a JSON string"), (int32)DirectionJson->Type, (int32)EJson::String);
109
+ TestEqual(TEXT("an int return keeps its export text"), DirectionJson->AsString(), FString(TEXT("7")));
110
+
111
+ const TSharedPtr<FJsonValue> NameJson =
112
+ MCPFunctionCall::OutputToJson(NameProp, NameProp->ContainerPtrToValuePtr<void>(&Poly), nullptr);
113
+ TestEqual(TEXT("an FName return stays a JSON string"), (int32)NameJson->Type, (int32)EJson::String);
114
+ TestEqual(TEXT("an FName return keeps its value"), NameJson->AsString(), FString(TEXT("Face")));
115
+
116
+ return true;
117
+ }
118
+
119
+ /** #885: container elements are typed, so a TArray<int32> reads as numbers. */
120
+ IMPLEMENT_SIMPLE_AUTOMATION_TEST(
121
+ FEditorFunctionCallNumericArrayTest,
122
+ "UE.MCP.Editor.FunctionCall.NumericArrayElementsAreNumbers",
123
+ EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
124
+
125
+ bool FEditorFunctionCallNumericArrayTest::RunTest(const FString& Parameters)
126
+ {
127
+ FBuilderPoly Poly;
128
+ Poly.VertexIndices = { 3, 5, 8 };
129
+
130
+ FArrayProperty* ArrayProp =
131
+ CastField<FArrayProperty>(FBuilderPoly::StaticStruct()->FindPropertyByName(FName(TEXT("VertexIndices"))));
132
+ if (!TestNotNull(TEXT("FBuilderPoly::VertexIndices is a reflected TArray<int32>"), ArrayProp)) return false;
133
+
134
+ const TSharedPtr<FJsonValue> Json =
135
+ MCPFunctionCall::OutputToJson(ArrayProp, ArrayProp->ContainerPtrToValuePtr<void>(&Poly), nullptr);
136
+ if (!TestEqual(TEXT("a TArray<int32> is a JSON array"), (int32)Json->Type, (int32)EJson::Array)) return false;
137
+
138
+ const TArray<TSharedPtr<FJsonValue>>& Items = Json->AsArray();
139
+ TestEqual(TEXT("every element survived"), Items.Num(), 3);
140
+ if (Items.Num() == 3)
141
+ {
142
+ TestEqual(TEXT("elements are numbers, not strings"), (int32)Items[0]->Type, (int32)EJson::Number);
143
+ TestEqual(TEXT("element 0"), (int32)Items[0]->AsNumber(), 3);
144
+ TestEqual(TEXT("element 1"), (int32)Items[1]->AsNumber(), 5);
145
+ TestEqual(TEXT("element 2"), (int32)Items[2]->AsNumber(), 8);
146
+ }
147
+
148
+ // An empty container is an empty array, not the empty string the defect
149
+ // reported: those two read very differently to a caller.
150
+ FBuilderPoly Empty;
151
+ const TSharedPtr<FJsonValue> EmptyJson =
152
+ MCPFunctionCall::OutputToJson(ArrayProp, ArrayProp->ContainerPtrToValuePtr<void>(&Empty), nullptr);
153
+ TestEqual(TEXT("an empty TArray is still a JSON array"), (int32)EmptyJson->Type, (int32)EJson::Array);
154
+ TestEqual(TEXT("an empty TArray has no elements"), EmptyJson->AsArray().Num(), 0);
155
+
156
+ return true;
157
+ }
158
+
159
+ /**
160
+ * #969: a path may carry literal arguments, e.g. GetTallyWeight(overclock).
161
+ * The parser has to keep a dot inside an argument list out of the path split,
162
+ * or a float literal would silently become two path segments.
163
+ */
164
+ IMPLEMENT_SIMPLE_AUTOMATION_TEST(
165
+ FEditorRuntimeValuePathParseTest,
166
+ "UE.MCP.Editor.FunctionCall.RuntimeValuePathParsing",
167
+ EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
168
+
169
+ bool FEditorRuntimeValuePathParseTest::RunTest(const FString& Parameters)
170
+ {
171
+ TArray<FString> Segments;
172
+
173
+ MCPFunctionCall::SplitPathSegments(TEXT("PowerConnector.GetRequired"), Segments);
174
+ TestEqual(TEXT("a plain dotted path splits as before"), Segments.Num(), 2);
175
+
176
+ MCPFunctionCall::SplitPathSegments(TEXT("Tally.GetMirroredTallyWeight(overclock)"), Segments);
177
+ TestEqual(TEXT("an argument list does not add a segment"), Segments.Num(), 2);
178
+ if (Segments.Num() == 2)
179
+ {
180
+ TestEqual(TEXT("the call segment stays whole"), Segments[1], FString(TEXT("GetMirroredTallyWeight(overclock)")));
181
+ }
182
+
183
+ MCPFunctionCall::SplitPathSegments(TEXT("GetWeightAt(1.5)"), Segments);
184
+ TestEqual(TEXT("a float literal's dot is not a path separator"), Segments.Num(), 1);
185
+
186
+ FString Name;
187
+ TArray<FString> Args;
188
+ bool bHasArgList = false;
189
+ FString Error;
190
+
191
+ TestTrue(TEXT("a bare name parses"), MCPFunctionCall::ParseCallSegment(TEXT("GetRequired"), Name, Args, bHasArgList, Error));
192
+ TestEqual(TEXT("a bare name keeps its name"), Name, FString(TEXT("GetRequired")));
193
+ TestFalse(TEXT("a bare name has no argument list"), bHasArgList);
194
+ TestEqual(TEXT("a bare name has no arguments"), Args.Num(), 0);
195
+
196
+ TestTrue(TEXT("an empty list parses"), MCPFunctionCall::ParseCallSegment(TEXT("GetRequired()"), Name, Args, bHasArgList, Error));
197
+ TestTrue(TEXT("an empty list is still an argument list"), bHasArgList);
198
+ TestEqual(TEXT("an empty list has no arguments"), Args.Num(), 0);
199
+
200
+ TestTrue(TEXT("a keyed accessor parses"),
201
+ MCPFunctionCall::ParseCallSegment(TEXT("GetMirroredTallyWeight(overclock)"), Name, Args, bHasArgList, Error));
202
+ TestEqual(TEXT("the function name is separated"), Name, FString(TEXT("GetMirroredTallyWeight")));
203
+ TestEqual(TEXT("one argument was read"), Args.Num(), 1);
204
+ if (Args.Num() == 1) TestEqual(TEXT("the literal is unquoted"), Args[0], FString(TEXT("overclock")));
205
+
206
+ TestTrue(TEXT("multiple arguments parse"),
207
+ MCPFunctionCall::ParseCallSegment(TEXT("GetAt(\"a, b\", 2, true)"), Name, Args, bHasArgList, Error));
208
+ TestEqual(TEXT("a comma inside quotes does not split"), Args.Num(), 3);
209
+ if (Args.Num() == 3)
210
+ {
211
+ TestEqual(TEXT("the quoted literal keeps its comma"), Args[0], FString(TEXT("a, b")));
212
+ TestEqual(TEXT("the numeric literal"), Args[1], FString(TEXT("2")));
213
+ TestEqual(TEXT("the bool literal"), Args[2], FString(TEXT("true")));
214
+ }
215
+
216
+ TestFalse(TEXT("an unbalanced list is refused"),
217
+ MCPFunctionCall::ParseCallSegment(TEXT("GetAt(overclock"), Name, Args, bHasArgList, Error));
218
+ TestTrue(TEXT("an unbalanced list explains itself"), !Error.IsEmpty());
219
+
220
+ return true;
221
+ }
222
+
223
+ #endif