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
@@ -5,9 +5,12 @@
5
5
  #include "Dom/JsonObject.h"
6
6
  #include "Serialization/JsonReader.h"
7
7
  #include "Serialization/JsonSerializer.h"
8
+ #include "Serialization/JsonWriter.h"
9
+ #include "Policies/CondensedJsonPrintPolicy.h"
8
10
  #include "UObject/UnrealType.h"
9
11
  #include "UObject/PropertyPortFlags.h"
10
12
  #include "UObject/SoftObjectPtr.h"
13
+ #include "StructUtils/InstancedStruct.h"
11
14
  #include "GameplayTagContainer.h"
12
15
  #include "GameplayTagsManager.h"
13
16
  #include "Engine/Blueprint.h"
@@ -23,6 +26,87 @@ namespace MCPJsonProperty
23
26
  {
24
27
  inline bool SetJsonOnProperty(FProperty* Prop, void* ValueAddr, const TSharedPtr<FJsonValue>& InValue, FString& OutError);
25
28
 
29
+ // The reference-property kinds, as one closed answer per property.
30
+ //
31
+ // The reflection types nest: FClassProperty derives from FObjectProperty,
32
+ // and FSoftClassProperty from FSoftObjectProperty; FWeakObjectProperty and
33
+ // FLazyObjectProperty are siblings of both under FObjectPropertyBase.
34
+ // CastField<Base>() succeeds on a derived instance, so a chain that tests
35
+ // the base first swallows the derived kind and leaves the derived branch
36
+ // unreachable. That is exactly how a TSubclassOf<> field came to be
37
+ // resolved by the generic asset loader instead of the class loader: the
38
+ // class branch sat below the object branch and never ran (#935).
39
+ //
40
+ // Classifying once and switching on the answer is used here in preference
41
+ // to simply reordering the branches. Ordering is invisible at the point of
42
+ // use, is not checkable by a test, and one tidy-up edit that moves a block
43
+ // silently reinstates the bug. A wrong answer here is a single function to
44
+ // read and a single function to test, and no later edit to the call sites
45
+ // can bring the dead branch back.
46
+ enum class ERefKind : uint8
47
+ {
48
+ NotAReference,
49
+ SoftClass,
50
+ SoftObject,
51
+ Class,
52
+ Object,
53
+ WeakObject,
54
+ LazyObject,
55
+ Interface,
56
+ };
57
+
58
+ inline ERefKind ClassifyReference(const FProperty* Prop)
59
+ {
60
+ if (!Prop) return ERefKind::NotAReference;
61
+ // Most derived first: each test is reached only once every
62
+ // more-derived test above it has failed.
63
+ if (Prop->IsA<FSoftClassProperty>()) return ERefKind::SoftClass;
64
+ if (Prop->IsA<FSoftObjectProperty>()) return ERefKind::SoftObject;
65
+ if (Prop->IsA<FClassProperty>()) return ERefKind::Class;
66
+ if (Prop->IsA<FObjectProperty>()) return ERefKind::Object;
67
+ if (Prop->IsA<FWeakObjectProperty>()) return ERefKind::WeakObject;
68
+ if (Prop->IsA<FLazyObjectProperty>()) return ERefKind::LazyObject;
69
+ if (Prop->IsA<FInterfaceProperty>()) return ERefKind::Interface;
70
+ return ERefKind::NotAReference;
71
+ }
72
+
73
+ // Resolve a class path the way a caller means it, without inventing a
74
+ // suffix the path did not ask for.
75
+ //
76
+ // #489: a caller naming a Blueprint commonly passes the asset path
77
+ // (/Game/Foo/BP_GameMode.BP_GameMode) for a class-typed field, and the
78
+ // generated class is that path plus "_C".
79
+ // #928: the suffix used to be appended unconditionally on the soft-class
80
+ // path, so a native /Script/Module.ClassName reference was stored as
81
+ // /Script/Module.ClassName_C, which names no class at all, and the write
82
+ // still reported success. The suffix is now only ever used when the path
83
+ // as written does not name a class and the suffixed form does.
84
+ inline UClass* ResolveClassPath(const FString& Path)
85
+ {
86
+ if (Path.IsEmpty()) return nullptr;
87
+
88
+ if (UClass* Direct = LoadClass<UObject>(nullptr, *Path))
89
+ {
90
+ return Direct;
91
+ }
92
+ // A native class lives at its /Script/ path and never carries "_C".
93
+ if (!Path.StartsWith(TEXT("/Script/")) && !Path.EndsWith(TEXT("_C")))
94
+ {
95
+ const FString WithSuffix = Path + TEXT("_C");
96
+ if (UClass* Generated = LoadClass<UObject>(nullptr, *WithSuffix))
97
+ {
98
+ return Generated;
99
+ }
100
+ }
101
+ // Last ditch: load the asset as a UBlueprint and take its generated
102
+ // class. Covers paths written without the .Name object suffix.
103
+ if (UBlueprint* BP = Cast<UBlueprint>(StaticLoadObject(UBlueprint::StaticClass(), nullptr, *Path)))
104
+ {
105
+ return BP->GeneratedClass;
106
+ }
107
+ return nullptr;
108
+ }
109
+
26
110
  // One (key, value) write for a TMap, in whichever JSON shape the caller
27
111
  // used. `KeyJson` is null for the object shape, where the field name is the
28
112
  // key text.
@@ -117,32 +201,33 @@ namespace MCPJsonProperty
117
201
  // to ImportText and surfaced "asset not found: None".
118
202
  if (Value->Type == EJson::Null)
119
203
  {
120
- if (FObjectProperty* OP = CastField<FObjectProperty>(Prop))
121
- {
122
- OP->SetObjectPropertyValue(ValueAddr, nullptr);
123
- return true;
124
- }
125
- if (FSoftObjectProperty* SOP = CastField<FSoftObjectProperty>(Prop))
204
+ // One dispatch on the classified kind: FClassProperty used to sit
205
+ // below FObjectProperty here and never be reached (#935). Clearing
206
+ // happens to mean the same thing for both, so this branch was not
207
+ // the one that corrupted data, but leaving the same inverted chain
208
+ // in place invites the next reader to copy it.
209
+ switch (ClassifyReference(Prop))
126
210
  {
127
- SOP->SetPropertyValue(ValueAddr, FSoftObjectPtr());
211
+ case ERefKind::SoftClass:
212
+ case ERefKind::SoftObject:
213
+ CastFieldChecked<FSoftObjectProperty>(Prop)->SetPropertyValue(ValueAddr, FSoftObjectPtr());
128
214
  return true;
129
- }
130
- if (FWeakObjectProperty* WOP = CastField<FWeakObjectProperty>(Prop))
131
- {
132
- WOP->SetObjectPropertyValue(ValueAddr, nullptr);
215
+ case ERefKind::Class:
216
+ case ERefKind::Object:
217
+ CastFieldChecked<FObjectProperty>(Prop)->SetObjectPropertyValue(ValueAddr, nullptr);
133
218
  return true;
134
- }
135
- if (FClassProperty* CP = CastField<FClassProperty>(Prop))
136
- {
137
- CP->SetObjectPropertyValue(ValueAddr, nullptr);
219
+ case ERefKind::WeakObject:
220
+ CastFieldChecked<FWeakObjectProperty>(Prop)->SetObjectPropertyValue(ValueAddr, nullptr);
138
221
  return true;
139
- }
140
- if (FInterfaceProperty* IP = CastField<FInterfaceProperty>(Prop))
222
+ case ERefKind::Interface:
141
223
  {
142
224
  FScriptInterface Empty;
143
- IP->SetPropertyValue(ValueAddr, Empty);
225
+ CastFieldChecked<FInterfaceProperty>(Prop)->SetPropertyValue(ValueAddr, Empty);
144
226
  return true;
145
227
  }
228
+ default:
229
+ break;
230
+ }
146
231
  OutError = FString::Printf(TEXT("property '%s' is not an object/class/interface reference; null value not allowed"), *Prop->GetName());
147
232
  return false;
148
233
  }
@@ -351,84 +436,81 @@ namespace MCPJsonProperty
351
436
  }
352
437
  }
353
438
 
354
- // Hard UObject ref - accept asset path
355
- if (FObjectProperty* ObjProp = CastField<FObjectProperty>(Prop))
439
+ // Object, class and soft references, all addressed by path. One
440
+ // dispatch on the classified kind (see ClassifyReference), so the
441
+ // class kinds are handled by the class-aware code rather than being
442
+ // swallowed by their base's branch (#935). A value that is not a
443
+ // string falls out of the switch to the ImportText fallback below,
444
+ // exactly as it did when these were separate `if` blocks.
445
+ switch (ClassifyReference(Prop))
446
+ {
447
+ case ERefKind::Class:
356
448
  {
357
449
  FString Path;
358
450
  if (Value->TryGetString(Path) && !Path.IsEmpty())
359
451
  {
360
- UObject* Loaded = StaticLoadObject(ObjProp->PropertyClass, nullptr, *Path);
361
- if (!Loaded) { OutError = FString::Printf(TEXT("asset not found: %s"), *Path); return false; }
362
- ObjProp->SetObjectPropertyValue(ValueAddr, Loaded);
452
+ UClass* Loaded = ResolveClassPath(Path);
453
+ if (!Loaded) { OutError = FString::Printf(TEXT("class not found: %s"), *Path); return false; }
454
+ CastFieldChecked<FClassProperty>(Prop)->SetObjectPropertyValue(ValueAddr, Loaded);
363
455
  return true;
364
456
  }
457
+ break;
365
458
  }
366
-
367
- // Hard UClass ref - accept class path
368
- if (FClassProperty* ClassProp = CastField<FClassProperty>(Prop))
459
+ case ERefKind::Object:
369
460
  {
370
461
  FString Path;
371
462
  if (Value->TryGetString(Path) && !Path.IsEmpty())
372
463
  {
373
- UClass* Loaded = LoadClass<UObject>(nullptr, *Path);
374
- if (!Loaded)
375
- {
376
- // #489: callers commonly pass a Blueprint asset path
377
- // (/Game/Foo/BP_GameMode.BP_GameMode) for a TSubclassOf
378
- // field. Retry with the generated-class suffix.
379
- if (!Path.EndsWith(TEXT("_C")))
380
- {
381
- const FString WithSuffix = Path + TEXT("_C");
382
- Loaded = LoadClass<UObject>(nullptr, *WithSuffix);
383
- }
384
- }
385
- if (!Loaded)
386
- {
387
- // Last-ditch: load the asset as UBlueprint and grab its
388
- // GeneratedClass. Covers paths that omit the .Name suffix.
389
- if (UBlueprint* BP = Cast<UBlueprint>(StaticLoadObject(UBlueprint::StaticClass(), nullptr, *Path)))
390
- {
391
- Loaded = BP->GeneratedClass;
392
- }
393
- }
394
- if (!Loaded) { OutError = FString::Printf(TEXT("class not found: %s"), *Path); return false; }
395
- ClassProp->SetObjectPropertyValue(ValueAddr, Loaded);
464
+ FObjectProperty* ObjProp = CastFieldChecked<FObjectProperty>(Prop);
465
+ UObject* Loaded = StaticLoadObject(ObjProp->PropertyClass, nullptr, *Path);
466
+ if (!Loaded) { OutError = FString::Printf(TEXT("asset not found: %s"), *Path); return false; }
467
+ ObjProp->SetObjectPropertyValue(ValueAddr, Loaded);
396
468
  return true;
397
469
  }
470
+ break;
398
471
  }
399
-
400
- // Soft class ref - accept class path string, same Blueprint suffix tolerance.
401
- if (FSoftClassProperty* SoftClassProp = CastField<FSoftClassProperty>(Prop))
472
+ case ERefKind::SoftClass:
402
473
  {
403
474
  FString Path;
404
475
  if (Value->TryGetString(Path))
405
476
  {
477
+ FSoftClassProperty* SoftClassProp = CastFieldChecked<FSoftClassProperty>(Prop);
406
478
  if (Path.IsEmpty())
407
479
  {
408
480
  SoftClassProp->SetPropertyValue(ValueAddr, FSoftObjectPtr());
409
481
  return true;
410
482
  }
411
- if (!Path.EndsWith(TEXT("_C")))
483
+ // #928: store the path that actually names a class. Only a
484
+ // Blueprint-generated class carries the "_C" suffix, and it is
485
+ // added only when the path as written names nothing and the
486
+ // suffixed form does. A path that resolves to neither is
487
+ // stored verbatim: a soft reference to an asset that does not
488
+ // exist yet is legitimate, and silently rewriting it was how a
489
+ // native /Script/ reference got corrupted.
490
+ if (UClass* Resolved = ResolveClassPath(Path))
412
491
  {
413
- Path += TEXT("_C");
492
+ SoftClassProp->SetPropertyValue(ValueAddr, FSoftObjectPtr(FSoftObjectPath(Resolved)));
493
+ return true;
414
494
  }
415
- FSoftObjectPath PathObj(Path);
416
- SoftClassProp->SetPropertyValue(ValueAddr, FSoftObjectPtr(PathObj));
495
+ SoftClassProp->SetPropertyValue(ValueAddr, FSoftObjectPtr(FSoftObjectPath(Path)));
417
496
  return true;
418
497
  }
498
+ break;
419
499
  }
420
-
421
- // Soft object ref - accept path string
422
- if (FSoftObjectProperty* SoftObjProp = CastField<FSoftObjectProperty>(Prop))
500
+ case ERefKind::SoftObject:
423
501
  {
424
502
  FString Path;
425
503
  if (Value->TryGetString(Path))
426
504
  {
427
505
  FSoftObjectPath PathObj(Path);
428
506
  FSoftObjectPtr Ptr(PathObj);
429
- SoftObjProp->SetPropertyValue(ValueAddr, Ptr);
507
+ CastFieldChecked<FSoftObjectProperty>(Prop)->SetPropertyValue(ValueAddr, Ptr);
430
508
  return true;
431
509
  }
510
+ break;
511
+ }
512
+ default:
513
+ break;
432
514
  }
433
515
 
434
516
  // Enum / Byte-with-enum: accept friendly aliases ("center", "Center"),
@@ -552,6 +634,208 @@ namespace MCPJsonProperty
552
634
  return bOk;
553
635
  }
554
636
 
637
+ // ── Readback verification ────────────────────────────────────────────────
638
+ //
639
+ // #935: a write that did not take must not come back as a success. The
640
+ // setter returns true the moment an assignment executes, and nothing used
641
+ // to look at what was actually stored afterwards, so a reference that
642
+ // resolved to the wrong thing, a container that quietly emptied, or a
643
+ // value that never reached the row the caller named all reported
644
+ // success: true with the broken asset saved to disk.
645
+
646
+ /** A compact, readable rendering of a JSON value for an error message. */
647
+ inline FString DescribeJsonValue(const TSharedPtr<FJsonValue>& Value)
648
+ {
649
+ if (!Value.IsValid()) return TEXT("(none)");
650
+ switch (Value->Type)
651
+ {
652
+ case EJson::Null: return TEXT("null");
653
+ case EJson::Boolean: return Value->AsBool() ? TEXT("true") : TEXT("false");
654
+ case EJson::Number: return FString::SanitizeFloat(Value->AsNumber());
655
+ case EJson::String: return Value->AsString();
656
+ default:
657
+ break;
658
+ }
659
+ FString Out;
660
+ using FCondensedWriter = TJsonWriter<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>;
661
+ const TSharedRef<FCondensedWriter> Writer =
662
+ TJsonWriterFactory<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>::Create(&Out);
663
+ if (Value->Type == EJson::Object)
664
+ {
665
+ FJsonSerializer::Serialize(Value->AsObject().ToSharedRef(), Writer);
666
+ }
667
+ else
668
+ {
669
+ FJsonSerializer::Serialize(Value->AsArray(), Writer);
670
+ }
671
+ return Out;
672
+ }
673
+
674
+ /** True when two independent applications of one request must produce
675
+ * values that compare Identical.
676
+ *
677
+ * Two kinds of value are not in that class, and both would report a
678
+ * mismatch that is not one:
679
+ *
680
+ * FText identity is per instance. Two imports of the same source string
681
+ * carry different keys and are never Identical.
682
+ *
683
+ * An instanced reference is a fresh subobject on every import, so the two
684
+ * applications point at two different objects by design.
685
+ *
686
+ * Anything unrecognised, and anything past the depth bound, answers
687
+ * "no", so the conservative path is the one that skips the comparison
688
+ * rather than the one that fails a write that was fine. */
689
+ inline bool PropertyIsComparableByIdentity(const FProperty* Prop, int32 Depth = 0)
690
+ {
691
+ if (!Prop || Depth > 12) return false;
692
+ if (Prop->HasAnyPropertyFlags(CPF_InstancedReference | CPF_ContainsInstancedReference)) return false;
693
+ if (Prop->IsA<FTextProperty>()) return false;
694
+
695
+ if (const FArrayProperty* ArrProp = CastField<FArrayProperty>(Prop))
696
+ {
697
+ return PropertyIsComparableByIdentity(ArrProp->Inner, Depth + 1);
698
+ }
699
+ if (const FSetProperty* SetProp = CastField<FSetProperty>(Prop))
700
+ {
701
+ return PropertyIsComparableByIdentity(SetProp->ElementProp, Depth + 1);
702
+ }
703
+ if (const FMapProperty* MapProp = CastField<FMapProperty>(Prop))
704
+ {
705
+ return PropertyIsComparableByIdentity(MapProp->KeyProp, Depth + 1)
706
+ && PropertyIsComparableByIdentity(MapProp->ValueProp, Depth + 1);
707
+ }
708
+ if (const FStructProperty* StructProp = CastField<FStructProperty>(Prop))
709
+ {
710
+ for (TFieldIterator<FProperty> It(StructProp->Struct); It; ++It)
711
+ {
712
+ if (!PropertyIsComparableByIdentity(*It, Depth + 1)) return false;
713
+ }
714
+ }
715
+ return true;
716
+ }
717
+
718
+ /** The comparable form of an asset path.
719
+ *
720
+ * Two normalisations, and only the two the resolver is allowed to apply:
721
+ * the "Pkg.Obj" long form of "Pkg" that the content browser writes as
722
+ * "Pkg", and the Blueprint generated class "Pkg.Obj_C" of that same
723
+ * asset. Both are recognised by the object name repeating the package
724
+ * leaf, which is what makes them the same asset; a "_C" that does not
725
+ * produce that repetition is part of the name and is left alone, so
726
+ * /Script/Engine.Character and /Script/Engine.Character_C stay different
727
+ * paths, and an asset genuinely called Foo_C still compares equal to
728
+ * itself. A subobject path is compared whole. */
729
+ inline FString NormalizeAssetPathForCompare(const FString& InPath)
730
+ {
731
+ FString Path = InPath.TrimStartAndEnd();
732
+ if (Path.IsEmpty() || Path.Contains(TEXT(":"))) return Path;
733
+
734
+ int32 DotPos = INDEX_NONE;
735
+ if (!Path.FindLastChar(TEXT('.'), DotPos)) return Path;
736
+
737
+ const FString Package = Path.Left(DotPos);
738
+ const FString ObjectName = Path.Mid(DotPos + 1);
739
+
740
+ int32 SlashPos = INDEX_NONE;
741
+ const FString LeafName = Package.FindLastChar(TEXT('/'), SlashPos)
742
+ ? Package.Mid(SlashPos + 1)
743
+ : Package;
744
+
745
+ if (LeafName.Equals(ObjectName, ESearchCase::IgnoreCase)) return Package;
746
+ if (ObjectName.EndsWith(TEXT("_C")) &&
747
+ LeafName.Equals(ObjectName.LeftChop(2), ESearchCase::IgnoreCase))
748
+ {
749
+ return Package;
750
+ }
751
+ return Path;
752
+ }
753
+
754
+ /** Read the stored value back and answer whether it is what was asked for.
755
+ *
756
+ * Three checks, because they fail on different things.
757
+ *
758
+ * A soft reference is never resolved at write time, so its stored text is
759
+ * whatever the setter chose to write. Comparing that text against the
760
+ * requested path is the only thing that catches a path the setter
761
+ * rewrote, which is exactly how a native /Script/ class reference used to
762
+ * end up with a "_C" suffix appended (#928).
763
+ *
764
+ * A hard reference that was asked for by path must not have come out
765
+ * null. Comparing its path is not useful, because a redirector legally
766
+ * answers a different path than the one requested.
767
+ *
768
+ * Everything else is compared by identity against an independent buffer
769
+ * the same request was applied to. That is what catches a value that
770
+ * never reached the destination: a container that stored fewer entries
771
+ * than it was handed, a row copy that dropped the field, a reference
772
+ * cleared by a later step. Values whose two applications are not
773
+ * required to be identical, text and instanced references, are exempt
774
+ * rather than failed.
775
+ *
776
+ * On a mismatch OutDetail carries the requested and the stored value. */
777
+ inline bool VerifyJsonOnProperty(
778
+ FProperty* Prop,
779
+ const void* ValueAddr,
780
+ const TSharedPtr<FJsonValue>& Requested,
781
+ FString& OutDetail)
782
+ {
783
+ if (!Prop || !ValueAddr || !Requested.IsValid()) return true;
784
+
785
+ FString StoredText;
786
+ Prop->ExportTextItem_Direct(StoredText, ValueAddr, nullptr, nullptr, PPF_None);
787
+
788
+ FString RequestedPath;
789
+ const bool bRequestedPath = Requested->TryGetString(RequestedPath) && !RequestedPath.IsEmpty();
790
+ const ERefKind Kind = ClassifyReference(Prop);
791
+
792
+ if (bRequestedPath && (Kind == ERefKind::SoftClass || Kind == ERefKind::SoftObject))
793
+ {
794
+ const FString Stored = CastFieldChecked<FSoftObjectProperty>(Prop)->GetPropertyValue(ValueAddr).ToString();
795
+ if (!NormalizeAssetPathForCompare(Stored).Equals(
796
+ NormalizeAssetPathForCompare(RequestedPath), ESearchCase::IgnoreCase))
797
+ {
798
+ OutDetail = FString::Printf(TEXT("requested '%s', stored '%s'"), *RequestedPath, *Stored);
799
+ return false;
800
+ }
801
+ return true;
802
+ }
803
+
804
+ if (bRequestedPath && (Kind == ERefKind::Class || Kind == ERefKind::Object))
805
+ {
806
+ if (CastFieldChecked<FObjectProperty>(Prop)->GetObjectPropertyValue(ValueAddr) == nullptr)
807
+ {
808
+ OutDetail = FString::Printf(TEXT("requested '%s', stored nothing"), *RequestedPath);
809
+ return false;
810
+ }
811
+ return true;
812
+ }
813
+
814
+ if (!PropertyIsComparableByIdentity(Prop)) return true;
815
+
816
+ FDefaultConstructedPropertyElement Reference(Prop);
817
+ FString Ignored;
818
+ if (!SetJsonOnProperty(Prop, Reference.GetObjAddress(), Requested, Ignored))
819
+ {
820
+ // The same request applied cleanly a moment ago; if it does not
821
+ // apply now the stored value cannot be trusted either.
822
+ OutDetail = FString::Printf(
823
+ TEXT("requested %s, stored '%s' (the request no longer applies: %s)"),
824
+ *DescribeJsonValue(Requested), *StoredText, *Ignored);
825
+ return false;
826
+ }
827
+ if (!Prop->Identical(ValueAddr, Reference.GetObjAddress(), PPF_None))
828
+ {
829
+ FString ReferenceText;
830
+ Prop->ExportTextItem_Direct(ReferenceText, Reference.GetObjAddress(), nullptr, nullptr, PPF_None);
831
+ OutDetail = FString::Printf(
832
+ TEXT("requested %s (which resolves to '%s'), stored '%s'"),
833
+ *DescribeJsonValue(Requested), *ReferenceText, *StoredText);
834
+ return false;
835
+ }
836
+ return true;
837
+ }
838
+
555
839
  // Resolve a dotted property path that may index arrays ("Traits[2]") and
556
840
  // follow object references (instanced subobjects), e.g.
557
841
  // "Config.Traits[1].Params.RepresentationActorManagementClass" on a
@@ -571,12 +855,13 @@ namespace MCPJsonProperty
571
855
  if (Parts.Num() == 0) { OutError = TEXT("empty property name"); return false; }
572
856
 
573
857
  void* Container = Root;
574
- UStruct* ContainerStruct = Root->GetClass();
858
+ const UStruct* ContainerStruct = Root->GetClass();
575
859
  UObject* Owner = Root;
576
860
 
577
861
  for (int32 i = 0; i < Parts.Num(); ++i)
578
862
  {
579
863
  FString Token = Parts[i];
864
+ const FString PathToken = Token;
580
865
  int32 Index = INDEX_NONE;
581
866
  int32 BracketPos;
582
867
  if (Token.FindChar(TEXT('['), BracketPos))
@@ -595,12 +880,36 @@ namespace MCPJsonProperty
595
880
 
596
881
  if (Index != INDEX_NONE)
597
882
  {
598
- FArrayProperty* ArrProp = CastField<FArrayProperty>(Prop);
599
- if (!ArrProp) { OutError = FString::Printf(TEXT("'%s' is not an array but was indexed [%d]"), *Token, Index); return false; }
600
- FScriptArrayHelper H(ArrProp, ValueAddr);
601
- if (Index < 0 || Index >= H.Num()) { OutError = FString::Printf(TEXT("index %d out of range on '%s' (num=%d)"), Index, *Token, H.Num()); return false; }
602
- Prop = ArrProp->Inner;
603
- ValueAddr = H.GetRawPtr(Index);
883
+ if (FArrayProperty* ArrProp = CastField<FArrayProperty>(Prop))
884
+ {
885
+ FScriptArrayHelper H(ArrProp, ValueAddr);
886
+ if (Index < 0 || Index >= H.Num()) { OutError = FString::Printf(TEXT("index %d out of range on '%s' (num=%d)"), Index, *Token, H.Num()); return false; }
887
+ Prop = ArrProp->Inner;
888
+ ValueAddr = H.GetRawPtr(Index);
889
+ }
890
+ // #927: a UPROPERTY declared as a C-style fixed array, e.g.
891
+ // `int32 Foo[3]`, is ONE FProperty with ArrayDim == 3 rather than
892
+ // an FArrayProperty. Without this branch every indexed write to
893
+ // one landed on element 0, or was refused as "not an array",
894
+ // which is how RecastNavMesh's three navmesh generation tiers
895
+ // became unreachable: only the Low tier could be read or written
896
+ // while the engine generated from Default and High.
897
+ else if (Prop->ArrayDim > 1)
898
+ {
899
+ if (Index < 0 || Index >= Prop->ArrayDim)
900
+ {
901
+ OutError = FString::Printf(TEXT("index %d out of range on fixed array '%s' (ArrayDim=%d)"), Index, *Token, Prop->ArrayDim);
902
+ return false;
903
+ }
904
+ // Re-address from the container so the element offset is the
905
+ // engine's own, rather than adding a hand-computed stride.
906
+ ValueAddr = Prop->ContainerPtrToValuePtr<void>(Container, Index);
907
+ }
908
+ else
909
+ {
910
+ OutError = FString::Printf(TEXT("'%s' is not an array but was indexed [%d]"), *Token, Index);
911
+ return false;
912
+ }
604
913
  }
605
914
 
606
915
  if (i == Parts.Num() - 1)
@@ -614,8 +923,25 @@ namespace MCPJsonProperty
614
923
  // Descend for the next token.
615
924
  if (FStructProperty* SP = CastField<FStructProperty>(Prop))
616
925
  {
617
- Container = ValueAddr;
618
- ContainerStruct = SP->Struct;
926
+ if (SP->Struct == FInstancedStruct::StaticStruct())
927
+ {
928
+ FInstancedStruct* InstancedStruct = static_cast<FInstancedStruct*>(ValueAddr);
929
+ const UScriptStruct* PayloadStruct = InstancedStruct->GetScriptStruct();
930
+ void* PayloadMemory = InstancedStruct->GetMutableMemory();
931
+ if (!PayloadStruct || !PayloadMemory)
932
+ {
933
+ OutError = FString::Printf(TEXT("'%s' FInstancedStruct payload is empty - cannot descend"), *PathToken);
934
+ return false;
935
+ }
936
+
937
+ Container = PayloadMemory;
938
+ ContainerStruct = PayloadStruct;
939
+ }
940
+ else
941
+ {
942
+ Container = ValueAddr;
943
+ ContainerStruct = SP->Struct;
944
+ }
619
945
  }
620
946
  else if (FObjectProperty* OP = CastField<FObjectProperty>(Prop))
621
947
  {