@alpsckr/unitycli 0.4.2 → 0.4.4

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 (420) hide show
  1. package/README.md +5 -5
  2. package/contracts/command-contract.json +109 -217
  3. package/contracts/package-files.json +374 -294
  4. package/contracts/stable-session/direct-call-semantics.json +8 -2
  5. package/contracts/tool-completion-contract.json +148 -672
  6. package/dist/bridge/admission.d.ts +17 -1
  7. package/dist/bridge/admission.js +2 -0
  8. package/dist/bridge/admission.js.map +1 -1
  9. package/dist/bridge/domains/asset.js +3 -1
  10. package/dist/bridge/domains/asset.js.map +1 -1
  11. package/dist/bridge/domains/camera.js +39 -7
  12. package/dist/bridge/domains/camera.js.map +1 -1
  13. package/dist/bridge/domains/console.js +2 -1
  14. package/dist/bridge/domains/console.js.map +1 -1
  15. package/dist/bridge/domains/doctor.d.ts +3 -0
  16. package/dist/bridge/domains/doctor.js +8 -0
  17. package/dist/bridge/domains/doctor.js.map +1 -0
  18. package/dist/bridge/domains/editor.js +0 -4
  19. package/dist/bridge/domains/editor.js.map +1 -1
  20. package/dist/bridge/domains/game-object.js +4 -12
  21. package/dist/bridge/domains/game-object.js.map +1 -1
  22. package/dist/bridge/domains/graphics.js +1 -16
  23. package/dist/bridge/domains/graphics.js.map +1 -1
  24. package/dist/bridge/domains/object.js +3 -0
  25. package/dist/bridge/domains/object.js.map +1 -1
  26. package/dist/bridge/domains/physics.js +1 -2
  27. package/dist/bridge/domains/physics.js.map +1 -1
  28. package/dist/bridge/domains/profiler.js +18 -7
  29. package/dist/bridge/domains/profiler.js.map +1 -1
  30. package/dist/bridge/domains/project-tool.js +4 -0
  31. package/dist/bridge/domains/project-tool.js.map +1 -1
  32. package/dist/bridge/domains/reflection.js +10 -2
  33. package/dist/bridge/domains/reflection.js.map +1 -1
  34. package/dist/bridge/domains/runtime-tool.js +3 -0
  35. package/dist/bridge/domains/runtime-tool.js.map +1 -1
  36. package/dist/bridge/domains/scene.js +3 -8
  37. package/dist/bridge/domains/scene.js.map +1 -1
  38. package/dist/bridge/domains/screenshot.js +30 -6
  39. package/dist/bridge/domains/screenshot.js.map +1 -1
  40. package/dist/bridge/domains/ui.js +3 -1
  41. package/dist/bridge/domains/ui.js.map +1 -1
  42. package/dist/bridge/index.d.ts +5 -5
  43. package/dist/bridge/index.js +6 -9
  44. package/dist/bridge/index.js.map +1 -1
  45. package/dist/bridge/registry.js +3 -5
  46. package/dist/bridge/registry.js.map +1 -1
  47. package/dist/bridge/session-supervisor.d.ts +6 -3
  48. package/dist/bridge/session-supervisor.js +122 -34
  49. package/dist/bridge/session-supervisor.js.map +1 -1
  50. package/dist/bridge/types.d.ts +6 -5
  51. package/dist/bridge/types.js +16 -2
  52. package/dist/bridge/types.js.map +1 -1
  53. package/dist/catalog-protocol.d.ts +64 -0
  54. package/dist/catalog-protocol.js +460 -0
  55. package/dist/catalog-protocol.js.map +1 -0
  56. package/dist/cli-commands/command.js +84 -61
  57. package/dist/cli-commands/command.js.map +1 -1
  58. package/dist/cli-commands/extensions.js +70 -50
  59. package/dist/cli-commands/extensions.js.map +1 -1
  60. package/dist/cli-commands/instances.js +1 -1
  61. package/dist/cli-commands/instances.js.map +1 -1
  62. package/dist/cli-process-owners.d.ts +6 -0
  63. package/dist/cli-process-owners.js +27 -0
  64. package/dist/cli-process-owners.js.map +1 -0
  65. package/dist/cli.d.ts +1 -1
  66. package/dist/cli.js +20 -5
  67. package/dist/cli.js.map +1 -1
  68. package/dist/contracts/command-contract.js +14 -4
  69. package/dist/contracts/command-contract.js.map +1 -1
  70. package/dist/doctor.d.ts +4 -6
  71. package/dist/doctor.js +429 -398
  72. package/dist/doctor.js.map +1 -1
  73. package/dist/editor-blocker-observer.d.ts +2 -1
  74. package/dist/editor-blocker-observer.js +40 -13
  75. package/dist/editor-blocker-observer.js.map +1 -1
  76. package/dist/extension-availability.d.ts +27 -9
  77. package/dist/extension-availability.js +96 -56
  78. package/dist/extension-availability.js.map +1 -1
  79. package/dist/extensions/descriptor-loader.d.ts +2 -2
  80. package/dist/extensions/descriptor-loader.js +3 -4
  81. package/dist/extensions/descriptor-loader.js.map +1 -1
  82. package/dist/extensions/index.d.ts +1 -1
  83. package/dist/extensions/index.js +1 -1
  84. package/dist/extensions/index.js.map +1 -1
  85. package/dist/extensions/installer.d.ts +24 -7
  86. package/dist/extensions/installer.js +142 -66
  87. package/dist/extensions/installer.js.map +1 -1
  88. package/dist/extensions/registry.js +3 -5
  89. package/dist/extensions/registry.js.map +1 -1
  90. package/dist/extensions/state-contract.d.ts +11 -0
  91. package/dist/extensions/state-contract.js +50 -0
  92. package/dist/extensions/state-contract.js.map +1 -0
  93. package/dist/instances.d.ts +13 -1
  94. package/dist/instances.js +26 -0
  95. package/dist/instances.js.map +1 -1
  96. package/dist/package-manager.d.ts +0 -24
  97. package/dist/package-manager.js +5 -58
  98. package/dist/package-manager.js.map +1 -1
  99. package/dist/project-tool-discovery.d.ts +5 -2
  100. package/dist/project-tool-discovery.js +22 -18
  101. package/dist/project-tool-discovery.js.map +1 -1
  102. package/dist/runtime-code.js +1 -1
  103. package/dist/runtime-code.js.map +1 -1
  104. package/dist/tool-name.d.ts +4 -0
  105. package/dist/tool-name.js +15 -0
  106. package/dist/tool-name.js.map +1 -0
  107. package/dist/tools/.generated-completion-registry.d.ts +2 -0
  108. package/dist/tools/.generated-completion-registry.js +763 -325
  109. package/dist/tools/.generated-completion-registry.js.map +1 -1
  110. package/dist/tools/.generated-registry.d.ts +6 -11
  111. package/dist/tools/.generated-registry.js +6 -11
  112. package/dist/tools/.generated-registry.js.map +1 -1
  113. package/dist/tools/.generated-schemas.js +11 -16
  114. package/dist/tools/.generated-schemas.js.map +1 -1
  115. package/dist/tools/asset/refresh-status.d.ts +3 -0
  116. package/dist/tools/asset/refresh-status.js +12 -0
  117. package/dist/tools/asset/refresh-status.js.map +1 -0
  118. package/dist/tools/asset/refresh.js +7 -3
  119. package/dist/tools/asset/refresh.js.map +1 -1
  120. package/dist/tools/build/player-status.d.ts +3 -0
  121. package/dist/tools/build/player-status.js +12 -0
  122. package/dist/tools/build/player-status.js.map +1 -0
  123. package/dist/tools/build/run.js +1 -1
  124. package/dist/tools/build/run.js.map +1 -1
  125. package/dist/tools/catalog-control.d.ts +22 -0
  126. package/dist/tools/catalog-control.js +335 -0
  127. package/dist/tools/catalog-control.js.map +1 -0
  128. package/dist/tools/catalog.d.ts +7 -9
  129. package/dist/tools/catalog.js +66 -45
  130. package/dist/tools/catalog.js.map +1 -1
  131. package/dist/tools/commands.d.ts +1 -1
  132. package/dist/tools/commands.js +44 -11
  133. package/dist/tools/commands.js.map +1 -1
  134. package/dist/tools/define.js +3 -2
  135. package/dist/tools/define.js.map +1 -1
  136. package/dist/tools/dispatcher.d.ts +53 -6
  137. package/dist/tools/dispatcher.js +277 -108
  138. package/dist/tools/dispatcher.js.map +1 -1
  139. package/dist/tools/editor/compile.js +1 -1
  140. package/dist/tools/editor/compile.js.map +1 -1
  141. package/dist/tools/editor/play-mode-status.d.ts +3 -0
  142. package/dist/tools/editor/play-mode-status.js +12 -0
  143. package/dist/tools/editor/play-mode-status.js.map +1 -0
  144. package/dist/tools/editor/readiness.d.ts +0 -1
  145. package/dist/tools/editor/readiness.js +0 -1
  146. package/dist/tools/editor/readiness.js.map +1 -1
  147. package/dist/tools/editor/refresh.d.ts +7 -3
  148. package/dist/tools/editor/refresh.js +20 -15
  149. package/dist/tools/editor/refresh.js.map +1 -1
  150. package/dist/tools/editor/state.d.ts +2 -2
  151. package/dist/tools/editor/state.js +13 -12
  152. package/dist/tools/editor/state.js.map +1 -1
  153. package/dist/tools/metadata/derived.js +1 -7
  154. package/dist/tools/metadata/derived.js.map +1 -1
  155. package/dist/tools/metadata/status.js +1 -12
  156. package/dist/tools/metadata/status.js.map +1 -1
  157. package/dist/tools/metadata/types.d.ts +0 -2
  158. package/dist/tools/navmesh/live.js +1 -1
  159. package/dist/tools/navmesh/live.js.map +1 -1
  160. package/dist/tools/occlusion/live.js +1 -1
  161. package/dist/tools/occlusion/live.js.map +1 -1
  162. package/dist/tools/package/deploy.js +2 -2
  163. package/dist/tools/package/deploy.js.map +1 -1
  164. package/dist/tools/package/embed.js +1 -1
  165. package/dist/tools/package/embed.js.map +1 -1
  166. package/dist/tools/package/install-status.d.ts +3 -0
  167. package/dist/tools/package/install-status.js +12 -0
  168. package/dist/tools/package/install-status.js.map +1 -0
  169. package/dist/tools/package/install.js +3 -3
  170. package/dist/tools/package/install.js.map +1 -1
  171. package/dist/tools/package/live-resolve.js +45 -12
  172. package/dist/tools/package/live-resolve.js.map +1 -1
  173. package/dist/tools/package/registry-clear.js.map +1 -1
  174. package/dist/tools/package/registry-set.js.map +1 -1
  175. package/dist/tools/package/remove-status.d.ts +3 -0
  176. package/dist/tools/package/remove-status.js +12 -0
  177. package/dist/tools/package/remove-status.js.map +1 -0
  178. package/dist/tools/package/remove.js +3 -3
  179. package/dist/tools/package/remove.js.map +1 -1
  180. package/dist/tools/package/resolve.js +3 -3
  181. package/dist/tools/package/resolve.js.map +1 -1
  182. package/dist/tools/package/restore.js +2 -2
  183. package/dist/tools/package/restore.js.map +1 -1
  184. package/dist/tools/package/scoped-registry-add.js.map +1 -1
  185. package/dist/tools/status/support.d.ts +13 -0
  186. package/dist/tools/status/support.js +28 -0
  187. package/dist/tools/status/support.js.map +1 -0
  188. package/dist/tools/test/cancel.js +2 -2
  189. package/dist/tools/test/cancel.js.map +1 -1
  190. package/dist/tools/test/run-status.d.ts +3 -0
  191. package/dist/tools/test/run-status.js +12 -0
  192. package/dist/tools/test/run-status.js.map +1 -0
  193. package/dist/tools/test/run.js +7 -4
  194. package/dist/tools/test/run.js.map +1 -1
  195. package/dist/tools/tools/group-summaries.js +10 -14
  196. package/dist/tools/tools/group-summaries.js.map +1 -1
  197. package/docs/architecture.md +4 -4
  198. package/docs/availability-differences.md +4 -4
  199. package/docs/command-execution.md +4 -3
  200. package/docs/error-codes.md +13 -5
  201. package/docs/extensions.md +1 -7
  202. package/docs/risk-and-side-effects.md +4 -3
  203. package/docs/safety-boundaries.md +3 -1
  204. package/docs/target-and-instance.md +1 -1
  205. package/docs/tool-discovery.md +17 -10
  206. package/docs/tool-execution.md +18 -7
  207. package/extensions/cinemachine/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
  208. package/extensions/hdrp/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
  209. package/extensions/probuilder/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
  210. package/extensions/urp/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
  211. package/extensions/vfx-graph/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
  212. package/package.json +2 -4
  213. package/skills/unitycli/SKILL.md +35 -42
  214. package/skills/unitycli/manifest.json +1 -1
  215. package/skills/unitycli/references/operation-protocol.md +3 -3
  216. package/unitypkg/Editor/AssemblyInfo.cs +5 -0
  217. package/unitypkg/Editor/Bridge/BridgeCommandRegistry.cs +49 -14
  218. package/unitypkg/Editor/Bridge/BridgeHttpServer.cs +9 -35
  219. package/unitypkg/Editor/Bridge/BridgeQueueState.cs +672 -0
  220. package/unitypkg/Editor/Bridge/BridgeWorkQueue.cs +970 -120
  221. package/unitypkg/Editor/BridgeStartupStateMachine.cs +59 -1
  222. package/unitypkg/Editor/Commands/ProjectToolCommands.cs +4 -0
  223. package/unitypkg/Editor/Commands/RuntimeToolCommands.cs +3 -0
  224. package/unitypkg/Editor/Generated/UnityCliToolCompletionRegistry.g.cs +379 -14
  225. package/unitypkg/Editor/Infrastructure/CodeExecSupport.cs +28 -13
  226. package/unitypkg/Editor/Infrastructure/RegistryOwnershipResolver.cs +132 -105
  227. package/unitypkg/Editor/Infrastructure/UnityCliActiveOperation.cs +138 -29
  228. package/unitypkg/Editor/Infrastructure/UnityCliAdmission.cs +1856 -237
  229. package/unitypkg/Editor/Infrastructure/UnityCliDoctorDiagnostics.cs +220 -0
  230. package/unitypkg/Editor/Infrastructure/UnityCliDurableStatusCoordinator.cs +118 -0
  231. package/unitypkg/Editor/Infrastructure/UnityCliEditorStateSnapshot.cs +2 -1
  232. package/unitypkg/Editor/Infrastructure/UnityCliHierarchyPager.cs +203 -0
  233. package/unitypkg/Editor/Infrastructure/UnityCliInstanceCleanup.cs +2 -1
  234. package/unitypkg/Editor/Infrastructure/UnityCliInstanceRegistry.cs +201 -63
  235. package/unitypkg/Editor/Infrastructure/UnityCliJson.cs +2 -3
  236. package/unitypkg/Editor/Infrastructure/UnityCliLiveBuildCoordinator.cs +289 -46
  237. package/unitypkg/Editor/Infrastructure/UnityCliLiveRefreshCoordinator.cs +87 -53
  238. package/unitypkg/Editor/Infrastructure/UnityCliNavMeshBakeCoordinator.cs +4 -4
  239. package/unitypkg/Editor/Infrastructure/UnityCliObjectRef.cs +128 -25
  240. package/unitypkg/Editor/Infrastructure/UnityCliObjectSerializer.cs +435 -327
  241. package/unitypkg/Editor/Infrastructure/UnityCliOcclusionBakeCoordinator.cs +4 -4
  242. package/unitypkg/Editor/Infrastructure/UnityCliOperationCoordinator.cs +351 -53
  243. package/unitypkg/Editor/Infrastructure/UnityCliPackageResolveCoordinator.cs +118 -18
  244. package/unitypkg/Editor/Infrastructure/UnityCliPackageResolveState.cs +20 -1
  245. package/unitypkg/Editor/Infrastructure/UnityCliReadGeneration.cs +97 -0
  246. package/unitypkg/Editor/Infrastructure/UnityCliReflection.cs +644 -133
  247. package/unitypkg/Editor/Infrastructure/UnityCliReloadOperation.cs +128 -41
  248. package/unitypkg/Editor/Infrastructure/UnityCliRuntimeExecutor.cs +14 -84
  249. package/unitypkg/Editor/Infrastructure/UnityCliSerializer.cs +220 -0
  250. package/unitypkg/Editor/Infrastructure/UnityCliSessionMutationLease.cs +69 -29
  251. package/unitypkg/Editor/Infrastructure/UnityCliTestRunCoordinator.cs +440 -54
  252. package/unitypkg/Editor/Infrastructure/UnityCliWaiter.cs +128 -35
  253. package/unitypkg/Editor/ProjectTools/IUnityCliTool.cs +9 -1
  254. package/unitypkg/Editor/ProjectTools/OfficialAttributeToolRegistry.cs +32 -31
  255. package/unitypkg/Editor/ProjectTools/ProjectToolRegistry.cs +607 -458
  256. package/unitypkg/Editor/ProjectTools/ProjectToolSchemaValidator.cs +35 -7
  257. package/unitypkg/Editor/ProjectTools/RegisteredTool.cs +501 -60
  258. package/unitypkg/Editor/ProjectTools/UnityCliProjectToolContext.cs +21 -0
  259. package/unitypkg/Editor/ProjectTools/UnityCliSchemaDeriver.cs +36 -7
  260. package/unitypkg/Editor/ProjectTools/UnityCliToolName.cs +30 -0
  261. package/unitypkg/Editor/Properties/AssemblyInfo.cs +3 -0
  262. package/unitypkg/Editor/Tools/asset/get-data.cs +16 -7
  263. package/unitypkg/Editor/Tools/build/BuildSceneStateTool.cs +23 -3
  264. package/unitypkg/Editor/Tools/build/BuildToolDtos.cs +2 -1
  265. package/unitypkg/Editor/Tools/build/scene/add.cs +6 -3
  266. package/unitypkg/Editor/Tools/build/scene/disable.cs +1 -1
  267. package/unitypkg/Editor/Tools/build/scene/enable.cs +1 -1
  268. package/unitypkg/Editor/Tools/build/scene/list.cs +1 -1
  269. package/unitypkg/Editor/Tools/build/scene/remove.cs +13 -3
  270. package/unitypkg/Editor/Tools/camera/CameraToolSupport.cs +38 -38
  271. package/unitypkg/Editor/Tools/camera/multiview.cs +13 -6
  272. package/unitypkg/Editor/Tools/camera/screenshot.cs +14 -7
  273. package/unitypkg/Editor/Tools/component/get.cs +16 -9
  274. package/unitypkg/Editor/Tools/component/modify.cs +6 -4
  275. package/unitypkg/Editor/Tools/console/clear.cs +7 -6
  276. package/unitypkg/Editor/Tools/console/get.cs +48 -16
  277. package/unitypkg/Editor/Tools/editor/EditorToolSupport.cs +143 -28
  278. package/unitypkg/Editor/Tools/editor/menu-items.cs +1 -1
  279. package/unitypkg/Editor/Tools/editor/play.cs +2 -11
  280. package/unitypkg/Editor/Tools/editor/selection.cs +1 -1
  281. package/unitypkg/Editor/Tools/editor/stop.cs +2 -11
  282. package/unitypkg/Editor/Tools/editor/windows.cs +1 -1
  283. package/unitypkg/Editor/Tools/go/GameObjectToolSupport.cs +54 -4
  284. package/unitypkg/Editor/Tools/go/delete.cs +1 -1
  285. package/unitypkg/Editor/Tools/go/find.cs +167 -70
  286. package/unitypkg/Editor/Tools/graphics/GraphicsToolSupport.cs +25 -0
  287. package/unitypkg/Editor/Tools/graphics/light-probe-group.cs +9 -3
  288. package/unitypkg/Editor/Tools/graphics/pipeline-info.cs +1 -1
  289. package/unitypkg/Editor/Tools/graphics/pipeline-settings.cs +1 -1
  290. package/unitypkg/Editor/Tools/graphics/quality.cs +2 -2
  291. package/unitypkg/Editor/Tools/graphics/reflection-probe.cs +3 -3
  292. package/unitypkg/Editor/Tools/graphics/stats.cs +1 -1
  293. package/unitypkg/Editor/Tools/object/ObjectToolSupport.cs +4 -4
  294. package/unitypkg/Editor/Tools/object/get-data.cs +9 -7
  295. package/unitypkg/Editor/Tools/object/modify.cs +7 -5
  296. package/unitypkg/Editor/Tools/physics/raycast.cs +24 -6
  297. package/unitypkg/Editor/Tools/prefab/stage.cs +1 -1
  298. package/unitypkg/Editor/Tools/profiler/ProfilerFrameDebuggerSupport.cs +94 -21
  299. package/unitypkg/Editor/Tools/profiler/ProfilerMemorySnapshotSupport.cs +397 -10
  300. package/unitypkg/Editor/Tools/profiler/ProfilerToolSupport.cs +18 -4
  301. package/unitypkg/Editor/Tools/profiler/frame-debugger/enable.cs +1 -1
  302. package/unitypkg/Editor/Tools/profiler/frame-debugger/events.cs +88 -87
  303. package/unitypkg/Editor/Tools/profiler/hierarchy.cs +278 -112
  304. package/unitypkg/Editor/Tools/profiler/memory-snapshot/compare.cs +9 -5
  305. package/unitypkg/Editor/Tools/profiler/memory-snapshot/list.cs +37 -35
  306. package/unitypkg/Editor/Tools/profiler/memory-snapshot/take.cs +99 -97
  307. package/unitypkg/Editor/Tools/project/info.cs +1 -1
  308. package/unitypkg/Editor/Tools/project/layers.cs +1 -1
  309. package/unitypkg/Editor/Tools/project/tags.cs +1 -1
  310. package/unitypkg/Editor/Tools/reflection/method/call.cs +9 -7
  311. package/unitypkg/Editor/Tools/reflection/method/find.cs +27 -37
  312. package/unitypkg/Editor/Tools/reflection/type/schema.cs +24 -28
  313. package/unitypkg/Editor/Tools/scene/gameobject/component.cs +8 -5
  314. package/unitypkg/Editor/Tools/scene/gameobject/components.cs +10 -7
  315. package/unitypkg/Editor/Tools/scene/gameobject-support.cs +104 -8
  316. package/unitypkg/Editor/Tools/scene/gameobject.cs +37 -16
  317. package/unitypkg/Editor/Tools/scene/hierarchy.cs +146 -142
  318. package/unitypkg/Editor/Tools/screenshot/EditorElementCaptureToolSupport.cs +77 -25
  319. package/unitypkg/Editor/Tools/screenshot/ScreenshotToolSupport.cs +315 -95
  320. package/unitypkg/Editor/Tools/screenshot/editor-element.cs +7 -5
  321. package/unitypkg/Editor/Tools/screenshot/game-view.cs +35 -21
  322. package/unitypkg/Editor/Tools/screenshot/isolated-object.cs +29 -23
  323. package/unitypkg/Editor/Tools/screenshot/orbit.cs +37 -25
  324. package/unitypkg/Editor/Tools/screenshot/scene-view.cs +25 -18
  325. package/unitypkg/Editor/Tools/screenshot/screen.cs +177 -17
  326. package/unitypkg/Editor/Tools/screenshot/surround.cs +83 -79
  327. package/unitypkg/Editor/Tools/ui/render-preview.cs +16 -11
  328. package/unitypkg/Editor/UnityCliBridge.cs +97 -34
  329. package/unitypkg/Runtime/Infrastructure/UnityCliRuntimeBridge.cs +753 -122
  330. package/unitypkg/Runtime/Properties/AssemblyInfo.cs +5 -0
  331. package/unitypkg/Runtime/UnityCli.Runtime.asmdef +1 -1
  332. package/unitypkg/Shared/AssemblyInfo.cs +4 -0
  333. package/unitypkg/Shared/UnityCliAdmissionPool.cs +265 -75
  334. package/unitypkg/Shared/UnityCliBridgeQueuePolicy.cs +410 -0
  335. package/unitypkg/Shared/UnityCliBridgeWorkItemState.cs +47 -0
  336. package/unitypkg/Shared/UnityCliExceptionProjection.cs +151 -0
  337. package/unitypkg/Shared/UnityCliQueuePublication.cs +30 -0
  338. package/unitypkg/Shared/UnityCliRequestBudget.cs +352 -0
  339. package/unitypkg/Shared/UnityCliRuntimeRequestPublication.cs +107 -0
  340. package/unitypkg/Tests/Editor/UnityCli.Editor.Tests.asmdef +2 -2
  341. package/unitypkg/Tests/Editor/UnityCliAdmissionCoordinatorIntegrationTests.cs +1329 -0
  342. package/unitypkg/Tests/Editor/UnityCliAdmissionPoolTests.cs +311 -114
  343. package/unitypkg/Tests/Editor/UnityCliAdmissionRepairStateTests.cs +94 -0
  344. package/unitypkg/Tests/Editor/UnityCliBridgeAdmissionIntegrationTests.cs +1229 -0
  345. package/unitypkg/Tests/Editor/UnityCliBridgeQueuePolicyTests.cs +412 -0
  346. package/unitypkg/Tests/Editor/UnityCliContinuationIntegrationTests.cs +292 -0
  347. package/unitypkg/Tests/Editor/UnityCliDiagnosticBoundaryTests.cs +120 -0
  348. package/unitypkg/Tests/Editor/UnityCliDurableStatusTests.cs +84 -0
  349. package/unitypkg/Tests/Editor/UnityCliExceptionProjectionTests.cs +72 -0
  350. package/unitypkg/Tests/Editor/UnityCliInstancePublicationRetryTests.cs +180 -97
  351. package/unitypkg/Tests/Editor/UnityCliMemoryReflectionIntegrationTests.cs +434 -0
  352. package/unitypkg/Tests/Editor/UnityCliRequestBudgetTests.cs +615 -0
  353. package/unitypkg/Tests/Editor/UnityCliRuntimeBridgeIntegrationTests.cs +520 -0
  354. package/unitypkg/Tests/Editor/UnityCliToolCatalogTests.cs +562 -0
  355. package/unitypkg/Tests/Runtime/UnityCli.Runtime.Tests.asmdef +23 -0
  356. package/unitypkg/Tests/Runtime/UnityCliRuntimeCatalogValidationTests.cs +29 -0
  357. package/unitypkg/package.json +1 -1
  358. package/contracts/static-capability-catalog.json +0 -48226
  359. package/dist/bridge/domains/diagnose.d.ts +0 -2
  360. package/dist/bridge/domains/diagnose.js +0 -11
  361. package/dist/bridge/domains/diagnose.js.map +0 -1
  362. package/dist/bridge/domains/extension.d.ts +0 -8
  363. package/dist/bridge/domains/extension.js +0 -20
  364. package/dist/bridge/domains/extension.js.map +0 -1
  365. package/dist/capabilities.d.ts +0 -5
  366. package/dist/capabilities.js +0 -84
  367. package/dist/capabilities.js.map +0 -1
  368. package/dist/static-capability-catalog.d.ts +0 -13
  369. package/dist/static-capability-catalog.js +0 -41
  370. package/dist/static-capability-catalog.js.map +0 -1
  371. package/dist/tools/diagnose/capabilities.d.ts +0 -13
  372. package/dist/tools/diagnose/capabilities.js +0 -34
  373. package/dist/tools/diagnose/capabilities.js.map +0 -1
  374. package/dist/tools/package/info.d.ts +0 -19
  375. package/dist/tools/package/info.js +0 -27
  376. package/dist/tools/package/info.js.map +0 -1
  377. package/dist/tools/package/list.d.ts +0 -27
  378. package/dist/tools/package/list.js +0 -21
  379. package/dist/tools/package/list.js.map +0 -1
  380. package/dist/tools/package/registry-get.d.ts +0 -10
  381. package/dist/tools/package/registry-get.js +0 -22
  382. package/dist/tools/package/registry-get.js.map +0 -1
  383. package/dist/tools/package/scoped-registry-list.d.ts +0 -14
  384. package/dist/tools/package/scoped-registry-list.js +0 -22
  385. package/dist/tools/package/scoped-registry-list.js.map +0 -1
  386. package/dist/tools/package/search.d.ts +0 -17
  387. package/dist/tools/package/search.js +0 -39
  388. package/dist/tools/package/search.js.map +0 -1
  389. package/dist/tools/package/status.d.ts +0 -21
  390. package/dist/tools/package/status.js +0 -37
  391. package/dist/tools/package/status.js.map +0 -1
  392. package/dist/tools/tools/describe.d.ts +0 -27
  393. package/dist/tools/tools/describe.js +0 -35
  394. package/dist/tools/tools/describe.js.map +0 -1
  395. package/dist/tools/tools/group-list.d.ts +0 -16
  396. package/dist/tools/tools/group-list.js +0 -38
  397. package/dist/tools/tools/group-list.js.map +0 -1
  398. package/dist/tools/tools/group.d.ts +0 -13
  399. package/dist/tools/tools/group.js +0 -34
  400. package/dist/tools/tools/group.js.map +0 -1
  401. package/dist/tools/tools/pagination.d.ts +0 -17
  402. package/dist/tools/tools/pagination.js +0 -28
  403. package/dist/tools/tools/pagination.js.map +0 -1
  404. package/dist/tools/tools/search.d.ts +0 -15
  405. package/dist/tools/tools/search.js +0 -42
  406. package/dist/tools/tools/search.js.map +0 -1
  407. package/unitypkg/Editor/Tools/diagnose/preflight.cs +0 -269
  408. package/unitypkg/Editor/Tools/editor/console.cs +0 -26
  409. package/unitypkg/Editor/Tools/extension/status.cs +0 -87
  410. package/unitypkg/Editor/Tools/go/inspect.cs +0 -49
  411. package/unitypkg/Editor/Tools/graphics/pipeline-quality.cs +0 -24
  412. package/unitypkg/Editor/Tools/graphics/probe-positions.cs +0 -58
  413. package/unitypkg/Editor/Tools/graphics/settings.cs +0 -104
  414. package/unitypkg/Editor/Tools/graphics/stats-counters.cs +0 -28
  415. package/unitypkg/Editor/Tools/graphics/stats-memory.cs +0 -21
  416. package/unitypkg/Editor/Tools/instances/list.cs +0 -71
  417. package/unitypkg/Editor/Tools/physics/raycast-all.cs +0 -67
  418. package/unitypkg/Editor/Tools/scene/build-settings.cs +0 -69
  419. package/unitypkg/Editor/Tools/scene/data.cs +0 -125
  420. package/unitypkg/Editor/Tools/tests/list.cs +0 -98
@@ -0,0 +1,615 @@
1
+ using System;
2
+ using System.Collections;
3
+ using System.Collections.Generic;
4
+ using System.IO;
5
+ using System.Linq;
6
+ using System.Reflection;
7
+ using System.Text;
8
+ using System.Threading;
9
+ using NUnit.Framework;
10
+ using UnityEditor.SceneManagement;
11
+ using UnityEngine;
12
+ using UnityEngine.SceneManagement;
13
+
14
+ namespace UnityCli.Tests
15
+ {
16
+ public sealed class UnityCliRequestBudgetTests
17
+ {
18
+ [Test]
19
+ public void SparseHundredThousandConsoleShapeStopsBeforeMaterialization()
20
+ {
21
+ var clock = new ManualClock();
22
+ var budget = Budget(clock, scans: 1000);
23
+ var matches = 0;
24
+ for (var index = 99999; index >= 0; index--)
25
+ {
26
+ if (!budget.TryConsumeScannedRecord()) break;
27
+ if (index % 10000 == 0) matches++;
28
+ }
29
+ Assert.That(budget.Usage().ScannedRecords, Is.EqualTo(1000));
30
+ Assert.That(matches, Is.EqualTo(0));
31
+ Assert.That(budget.ExhaustedDimension, Is.EqualTo("scannedRecords"));
32
+ }
33
+
34
+ [Test]
35
+ public void ZeroNegativeAndOverMaximumBudgetValuesFailBeforeReads()
36
+ {
37
+ Assert.Throws<ArgumentOutOfRangeException>(() => new UnityCliRequestBudget(new UnityCliReadBudgetInput { MaxScannedRecords = 0 }, 1000, CancellationToken.None, new ManualClock()));
38
+ Assert.Throws<ArgumentOutOfRangeException>(() => new UnityCliRequestBudget(new UnityCliReadBudgetInput { MaxNodes = -1 }, 1000, CancellationToken.None, new ManualClock()));
39
+ Assert.Throws<ArgumentOutOfRangeException>(() => new UnityCliRequestBudget(new UnityCliReadBudgetInput { MaxGetters = UnityCliRequestBudget.AbsoluteMaxGetters + 1 }, 1000, CancellationToken.None, new ManualClock()));
40
+ Assert.Throws<ArgumentOutOfRangeException>(() => new UnityCliRequestBudget(new UnityCliReadBudgetInput { MaxReflectionCalls = UnityCliRequestBudget.AbsoluteMaxReflectionCalls + 1 }, 1000, CancellationToken.None, new ManualClock()));
41
+ Assert.Throws<ArgumentOutOfRangeException>(() => new UnityCliRequestBudget(new UnityCliReadBudgetInput { MaxMainThreadMilliseconds = UnityCliRequestBudget.AbsoluteMaxMainThreadMilliseconds + 1 }, 1000, CancellationToken.None, new ManualClock()));
42
+ Assert.Throws<ArgumentOutOfRangeException>(() => new UnityCliRequestBudget(new UnityCliReadBudgetInput { MaxSerializedUtf8Bytes = UnityCliRequestBudget.AbsoluteMaxSerializedUtf8Bytes + 1 }, 1000, CancellationToken.None, new ManualClock()));
43
+ }
44
+
45
+ [Test]
46
+ public void SerializedUtf8BudgetCountsEncodedBytesAndStructureReservations()
47
+ {
48
+ var budget = new UnityCliRequestBudget(new UnityCliReadBudgetInput { MaxSerializedUtf8Bytes = 8 }, 1000, CancellationToken.None, new ManualClock());
49
+ Assert.That(budget.TryReserveSerializedUtf8("é"), Is.True);
50
+ Assert.That(budget.TryReserveSerializedUtf8Bytes(6), Is.True);
51
+ Assert.That(budget.TryReserveSerializedUtf8("x"), Is.False);
52
+ Assert.That(budget.ExhaustedDimension, Is.EqualTo("serializedUtf8Bytes"));
53
+ Assert.That(budget.Usage().EstimatedSerializedUtf8Bytes, Is.EqualTo(8));
54
+ Assert.That(budget.Usage().ActualSerializedUtf8Bytes, Is.Zero);
55
+ }
56
+
57
+ [Test]
58
+ public void ManualMonotonicClockAndCancellationShareEveryCounter()
59
+ {
60
+ var clock = new ManualClock();
61
+ var cancellation = new CancellationTokenSource();
62
+ var budget = new UnityCliRequestBudget(new UnityCliReadBudgetInput { MaxMainThreadMilliseconds = 5 }, 1000, cancellation.Token, clock);
63
+ Assert.That(budget.TryConsumeNode(), Is.True);
64
+ clock.Advance(5);
65
+ Assert.That(budget.TryConsumeGetter(), Is.False);
66
+ Assert.That(budget.ExhaustedDimension, Is.EqualTo("mainThreadMilliseconds"));
67
+
68
+ var cancelled = new UnityCliRequestBudget(null, 1000, cancellation.Token, clock);
69
+ cancellation.Cancel();
70
+ Assert.Throws<OperationCanceledException>(() => cancelled.TryConsumeReflectionCall());
71
+ }
72
+
73
+ [Test]
74
+ public void CyclicDeepWideEnumerableAndMultiplePathsUseOneSerializerBudget()
75
+ {
76
+ var cycle = new Node { Name = "root" };
77
+ cycle.Next = cycle;
78
+ var cycleJson = UnityCliSerializer.Serialize(UnityCliBridge.ObjectSerializer.Serialize(cycle,
79
+ new UnityCliBridge.ObjectSerializer.Options { MaxDepth = 16, MaxArrayItems = 1000 }, Budget(new ManualClock(), nodes: 32)));
80
+ StringAssert.Contains("cycle-detected", cycleJson);
81
+
82
+ var enumerable = new CountingEnumerable(100000);
83
+ var enumerableBudget = Budget(new ManualClock(), scans: 17, nodes: 64);
84
+ var enumerableJson = UnityCliSerializer.Serialize(UnityCliBridge.ObjectSerializer.Serialize(enumerable,
85
+ new UnityCliBridge.ObjectSerializer.Options { MaxDepth = 4, MaxArrayItems = 1000 }, enumerableBudget));
86
+ Assert.That(enumerable.MoveNextCount, Is.EqualTo(17));
87
+ StringAssert.Contains("non-resumable-enumerable", enumerableJson);
88
+ StringAssert.DoesNotContain("nextPath", enumerableJson);
89
+
90
+ var multi = new GetterFixture();
91
+ var pathBudget = Budget(new ManualClock(), getters: 1, reflections: 32, nodes: 32);
92
+ var pathJson = UnityCliSerializer.Serialize(UnityCliBridge.ObjectSerializer.Serialize(multi,
93
+ new UnityCliBridge.ObjectSerializer.Options { Paths = new List<string> { "First", "Second" }, MaxDepth = 2 }, pathBudget));
94
+ Assert.That(multi.GetterCount, Is.EqualTo(1));
95
+ StringAssert.Contains("request-budget-exhausted", pathJson);
96
+ }
97
+
98
+ [Test]
99
+ public void OversizedAndRepeatedCapturePlansFailBeforeTextureAllocation()
100
+ {
101
+ var requestBudget = Budget(new ManualClock());
102
+ Assert.Throws<UnityCliBridge.BridgeCommandException>(() => ScreenshotToolSupport.PrepareCaptureBudget(8192, 8192, 1, false, null, requestBudget));
103
+ var captures = ScreenshotToolSupport.PrepareCaptureBudget(512, 512, 2, false, null, Budget(new ManualClock()));
104
+ captures.BeginCapture(512, 512);
105
+ captures.BeginCapture(512, 512);
106
+ Assert.Throws<UnityCliBridge.BridgeCommandException>(() => captures.BeginCapture(512, 512));
107
+ Assert.That(captures.Plan.PeakTemporaryBytes, Is.LessThanOrEqualTo(ScreenshotToolSupport.DefaultMaxTemporaryBytes));
108
+ }
109
+
110
+ [Test]
111
+ public void HighBranchProfilerAndLargeSceneOrPrefabShapesStopAtGlobalNodeBudget()
112
+ {
113
+ var profiler = SyntheticTree.Wide(depth: 2, width: 100);
114
+ var profilerBudget = Budget(new ManualClock(), scans: 250, nodes: 200);
115
+ Assert.That(Traverse(profiler, profilerBudget), Is.EqualTo(200));
116
+ Assert.That(profilerBudget.ExhaustedDimension, Is.EqualTo("nodes"));
117
+
118
+ var scene = SyntheticTree.Wide(depth: 4, width: 12);
119
+ var sceneBudget = Budget(new ManualClock(), scans: 101, nodes: 101);
120
+ Assert.That(Traverse(scene, sceneBudget), Is.EqualTo(101));
121
+ Assert.That(sceneBudget.Usage().ScannedRecords, Is.EqualTo(101));
122
+ }
123
+
124
+ [Test]
125
+ public void ProfilerHierarchyTraversesUnity2022FlatPreorderWithoutChildAt()
126
+ {
127
+ var budget = Budget(new ManualClock(), scans: 64, nodes: 64, getters: 64, reflections: 128);
128
+ using (var accessor = ProfilerHierarchyTool.RawHierarchyAccessor.InitializeForTests(new Unity2022RawFrameDataViewProbe(), budget))
129
+ {
130
+ var stack = new List<ProfilerHierarchyTool.TraversalFrame>
131
+ {
132
+ new ProfilerHierarchyTool.TraversalFrame { ParentId = 0, NextSample = 1, Depth = 0 }
133
+ };
134
+ var items = new List<ProfilerHierarchyTool.HierarchyItem>();
135
+ ProfilerHierarchyTool.HierarchyItem item;
136
+ while (ProfilerHierarchyTool.TryNext(accessor, stack, 8, budget, out item)) items.Add(item);
137
+
138
+ CollectionAssert.AreEqual(new long[] { 1, 2, 3, 4 }, items.Select(value => value.Id).ToArray());
139
+ CollectionAssert.AreEqual(new long[] { 0, 1, 0, 3 }, items.Select(value => value.ParentId).ToArray());
140
+ CollectionAssert.AreEqual(new long[] { 0, 1, 0, 1 }, items.Select(value => value.Depth).ToArray());
141
+ CollectionAssert.AreEqual(new long[] { 1, 0, 1, 0 }, items.Select(value => value.ChildCount).ToArray());
142
+ Assert.That(accessor.SubtreeEndExclusive(0, budget), Is.EqualTo(5));
143
+ Assert.That(stack, Is.Empty);
144
+ }
145
+ }
146
+
147
+ [Test]
148
+ public void ContinuationBindsScopeGenerationAndRawPosition()
149
+ {
150
+ var token = UnityCliContinuationToken.Create("console.get", "generation-a", "99999:99000:sentinel");
151
+ string position;
152
+ Assert.That(UnityCliContinuationToken.TryParse(token, "console.get", "generation-a", out position), Is.True);
153
+ Assert.That(position, Is.EqualTo("99999:99000:sentinel"));
154
+ Assert.That(UnityCliContinuationToken.TryParse(token, "console.get", "generation-b", out position), Is.False);
155
+ Assert.That(UnityCliContinuationToken.TryParse(token, "scene.hierarchy", "generation-a", out position), Is.False);
156
+ }
157
+
158
+ [Test]
159
+ public void SnapshotRetentionKeepsLatestHundredAndHonorsBytePressure()
160
+ {
161
+ var sizes = Enumerable.Repeat(10L, 101).ToArray();
162
+ var byCount = UnityCliRetentionPlanner.RetainedIndices(sizes, 100, 10000);
163
+ Assert.That(byCount.Count, Is.EqualTo(100));
164
+ Assert.That(byCount.Contains(100), Is.False);
165
+ var byBytes = UnityCliRetentionPlanner.RetainedIndices(sizes, 100, 35);
166
+ CollectionAssert.AreEqual(new[] { 0, 1, 2 }, byBytes.OrderBy(value => value).ToArray());
167
+ var contiguousNewest = UnityCliRetentionPlanner.RetainedIndices(new[] { 20L, 40L, 1L }, 100, 35);
168
+ CollectionAssert.AreEqual(new[] { 0 }, contiguousNewest.OrderBy(value => value).ToArray());
169
+ }
170
+
171
+ [Test]
172
+ public void BroadReflectionReturnsStableContinuationWithinCallBudget()
173
+ {
174
+ var budget = Budget(new ManualClock(), scans: 8, nodes: 64, reflections: 128);
175
+ var page = UnityCliBridge.ReflectionInvoker.FindMethods("System.String", null,
176
+ BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static, 100, null, budget);
177
+ Assert.That(page.Methods.Count, Is.LessThanOrEqualTo(8));
178
+ Assert.That(page.Truncated, Is.True);
179
+ Assert.That(page.NextCursor, Is.Not.Null.And.Not.Empty);
180
+ string position;
181
+ Assert.That(UnityCliContinuationToken.TryParse(page.NextCursor, "reflection.method.find", page.Generation, out position), Is.True);
182
+ }
183
+
184
+ [Test]
185
+ public void CallbackPublicationOwnsFactoryOnceAndRejectsLateCallbacks()
186
+ {
187
+ var abandoned = new UnityCliCallbackPublication<string>();
188
+ var lateFactoryCalls = 0;
189
+ Assert.That(abandoned.TryAbandon(), Is.True);
190
+ Assert.That(abandoned.TryComplete(() => { lateFactoryCalls++; return "late"; }), Is.False);
191
+ Assert.That(lateFactoryCalls, Is.Zero);
192
+
193
+ var completed = new UnityCliCallbackPublication<string>();
194
+ Assert.That(completed.TryComplete(() => "published"), Is.True);
195
+ Assert.That(completed.TryFail(new InvalidOperationException("late")), Is.False);
196
+ bool isCompleted;
197
+ bool isFailed;
198
+ string value;
199
+ Exception error;
200
+ completed.Read(out isCompleted, out isFailed, out value, out error);
201
+ Assert.That(isCompleted, Is.True);
202
+ Assert.That(isFailed, Is.False);
203
+ Assert.That(value, Is.EqualTo("published"));
204
+ Assert.That(error, Is.Null);
205
+
206
+ var faulted = new UnityCliCallbackPublication<string>();
207
+ Assert.That(faulted.TryComplete(() => { throw new IOException("write fault"); }), Is.True);
208
+ faulted.Read(out isCompleted, out isFailed, out value, out error);
209
+ Assert.That(isCompleted, Is.False);
210
+ Assert.That(isFailed, Is.True);
211
+ Assert.That(error, Is.TypeOf<IOException>());
212
+ }
213
+
214
+ [Test]
215
+ public void RealScenePagerAndComponentGetterFailClosedBeforeBatchMaterialization()
216
+ {
217
+ var scene = EditorSceneManager.NewPreviewScene();
218
+ try
219
+ {
220
+ var first = NewRoot(scene, "issue-042-first");
221
+ var second = NewRoot(scene, "issue-042-second");
222
+ var third = NewRoot(scene, "issue-042-third");
223
+ first.AddComponent<BoxCollider>();
224
+ first.AddComponent<SphereCollider>();
225
+
226
+ var rootBudget = Budget(new ManualClock(), scans: 2);
227
+ Assert.Throws<UnityCliRequestBudgetExceededException>(() => UnityCliBridge.ObjectRefResolver.BoundedSceneRoots(scene, rootBudget));
228
+ Assert.That(rootBudget.Usage().ScannedRecords, Is.Zero);
229
+
230
+ var componentBudget = Budget(new ManualClock(), scans: 2);
231
+ Assert.Throws<UnityCliRequestBudgetExceededException>(() => UnityCliBridge.ObjectRefResolver.BoundedComponents(first, componentBudget));
232
+ Assert.That(componentBudget.Usage().ScannedRecords, Is.Zero);
233
+
234
+ var pagerBudget = Budget(new ManualClock(), scans: 32, nodes: 32, getters: 128);
235
+ var traversal = new UnityCliHierarchyPager.Traversal(scene, null, 8, pagerBudget);
236
+ Assert.That(traversal.First(pagerBudget), Is.SameAs(first));
237
+ Assert.That(traversal.Next(first, pagerBudget), Is.SameAs(second));
238
+ var generation = UnityCliReadGeneration.Hierarchy(scene);
239
+ var cursor = UnityCliHierarchyPager.Cursor("issue-042", generation, "scene", third, pagerBudget);
240
+ string phase;
241
+ Assert.That(UnityCliHierarchyPager.ResolveCursor(cursor, "issue-042", generation, pagerBudget, out phase), Is.SameAs(third));
242
+ Assert.That(phase, Is.EqualTo("scene"));
243
+
244
+ var handler = new UnityCliBridge.SceneGameObjectComponentsTool();
245
+ Assert.Throws<UnityCliRequestBudgetExceededException>(() => handler.ExecuteAsync(
246
+ new UnityCliBridge.SceneGameObjectComponentsTool.Request
247
+ {
248
+ Id = first.GetInstanceID(),
249
+ IncludeProperties = false,
250
+ Budget = new UnityCliReadBudgetInput { MaxScannedRecords = 2 }
251
+ },
252
+ new UnityCliProjectToolContext { RequestId = "issue-042-over-budget", TimeoutMs = 30000, CancellationToken = CancellationToken.None }).GetAwaiter().GetResult());
253
+ var handlerResult = handler.ExecuteAsync(
254
+ new UnityCliBridge.SceneGameObjectComponentsTool.Request { Id = first.GetInstanceID(), IncludeProperties = false },
255
+ new UnityCliProjectToolContext { RequestId = "issue-042", TimeoutMs = 30000, CancellationToken = CancellationToken.None }).GetAwaiter().GetResult();
256
+ Assert.That(handlerResult.Count, Is.EqualTo(3));
257
+ Assert.That(handlerResult.Components.Count, Is.EqualTo(3));
258
+ }
259
+ finally
260
+ {
261
+ Assert.That(EditorSceneManager.ClosePreviewScene(scene), Is.True);
262
+ Assert.That(scene.IsValid(), Is.False);
263
+ }
264
+ }
265
+
266
+ [Test]
267
+ public void DictionaryAndEnumTruncationAreExplicitlyNonResumable()
268
+ {
269
+ var dictionary = new Dictionary<string, int> { { "alpha", 1 }, { "beta", 2 } };
270
+ var dictionaryJson = UnityCliSerializer.Serialize(UnityCliBridge.ObjectSerializer.Serialize(
271
+ dictionary,
272
+ new UnityCliBridge.ObjectSerializer.Options { MaxDepth = 4, MaxArrayItems = 1 },
273
+ Budget(new ManualClock(), scans: 32, nodes: 32, getters: 32, reflections: 32)));
274
+ StringAssert.Contains("non-resumable-dictionary-truncation", dictionaryJson);
275
+ StringAssert.DoesNotContain("nextPath", dictionaryJson);
276
+
277
+ var enumPage = UnityCliBridge.TypeSchemaGenerator.Generate(
278
+ typeof(Issue042Enum).FullName,
279
+ 4,
280
+ 1,
281
+ 16,
282
+ 16,
283
+ null,
284
+ Budget(new ManualClock(), scans: 4096, nodes: 64, getters: 64, reflections: 4096));
285
+ var schema = (Dictionary<string, object>)enumPage.Data["schema"];
286
+ Assert.That(schema["continuationSupported"], Is.False);
287
+ Assert.That(schema["truncationReason"], Is.EqualTo("non-resumable-enum-truncation"));
288
+ Assert.That(enumPage.NextCursor, Is.Null);
289
+ }
290
+
291
+ [Test]
292
+ public void ConsoleCoreFailsOnZeroProgressWithoutRescanningUnboundedInput()
293
+ {
294
+ var adapter = new EmptyConsoleSnapshot(100000);
295
+ var budget = Budget(new ManualClock(), scans: 3, nodes: 32, getters: 32, reflections: 32);
296
+ Assert.Throws<UnityCliRequestBudgetExceededException>(() => EditorToolSupport.ReadConsolePage("error", null, 10, budget, adapter));
297
+ Assert.That(adapter.ReadCount, Is.EqualTo(3));
298
+ Assert.That(budget.ExhaustedDimension, Is.EqualTo("scannedRecords"));
299
+ }
300
+
301
+ [Test]
302
+ public void WideReflectionSnapshotsAndInvocationPrebudgetFailBeforeEffect()
303
+ {
304
+ Assert.Throws<UnityCliRequestBudgetExceededException>(() => UnityCliReflectionSnapshot.Methods(
305
+ typeof(string),
306
+ BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static,
307
+ Budget(new ManualClock(), scans: 64, nodes: 64, getters: 64, reflections: 64),
308
+ 1));
309
+ Assert.Throws<UnityCliRequestBudgetExceededException>(() => UnityCliReflectionSnapshot.Interfaces(
310
+ typeof(Dictionary<string, string>),
311
+ Budget(new ManualClock(), scans: 64, nodes: 64, getters: 64, reflections: 64),
312
+ 1));
313
+ Assert.Throws<UnityCliRequestBudgetExceededException>(() => UnityCliReflectionSnapshot.GenericArguments(
314
+ typeof(Dictionary<string, List<Dictionary<int, string>>>),
315
+ Budget(new ManualClock(), scans: 64, nodes: 64, getters: 64, reflections: 64),
316
+ 1));
317
+ Assert.Throws<UnityCliRequestBudgetExceededException>(() => UnityCliBridge.TypeSchemaGenerator.Generate(
318
+ typeof(Dictionary<string, List<Dictionary<int, string>>>).AssemblyQualifiedName,
319
+ 8,
320
+ 64,
321
+ 64,
322
+ 64,
323
+ null,
324
+ Budget(new ManualClock(), scans: 128, nodes: 128, getters: 128, reflections: 128, serialized: 64)));
325
+
326
+ Issue042InvocationFixture.InvocationCount = 0;
327
+ var callBudget = Budget(new ManualClock(), scans: 128, nodes: 128, getters: 128, reflections: 128, serialized: 1024);
328
+ Assert.Throws<UnityCliRequestBudgetExceededException>(() => UnityCliBridge.ReflectionInvoker.PrepareCall(
329
+ typeof(Issue042InvocationFixture).FullName,
330
+ "Invoke",
331
+ new List<object> { 1L, 2L, 3L },
332
+ null,
333
+ null,
334
+ BindingFlags.Public | BindingFlags.Static,
335
+ callBudget));
336
+ Assert.That(Issue042InvocationFixture.InvocationCount, Is.Zero);
337
+ }
338
+
339
+ [Test]
340
+ public void TerminalSerializationReportsExactUtf8AndUsesManualDeadline()
341
+ {
342
+ var clock = new ManualClock();
343
+ var budget = Budget(clock);
344
+ var data = new Dictionary<string, object>
345
+ {
346
+ { "value", "é" },
347
+ { "budget", budget.Usage() }
348
+ };
349
+ var json = UnityCliBridge.SerializeTerminalSuccess("issue-042", data, budget, CancellationToken.None);
350
+ Assert.That(budget.Usage().ActualSerializedUtf8Bytes, Is.EqualTo(Encoding.UTF8.GetByteCount(json)));
351
+ StringAssert.Contains("actualSerializedUtf8Bytes", json);
352
+
353
+ var deadlineClock = new ManualClock();
354
+ var deadlineBudget = Budget(deadlineClock, milliseconds: 5);
355
+ deadlineClock.Advance(5);
356
+ Assert.Throws<UnityCliRequestBudgetExceededException>(() => UnityCliBridge.SerializeTerminalSuccess(
357
+ "issue-042-deadline", new { Value = "late" }, deadlineBudget, CancellationToken.None));
358
+ }
359
+
360
+ [Test]
361
+ public void MemoryRetentionLimitsAndOrphanCleanupOperateOnRealFiles()
362
+ {
363
+ var root = CreateTestRoot("memory");
364
+ try
365
+ {
366
+ ProfilerMemorySnapshotSupport.ResetTestOverrides();
367
+ ProfilerMemorySnapshotSupport.RootOverrideForTests = root;
368
+ string newest = null;
369
+ for (var index = 0; index < 101; index++)
370
+ {
371
+ var path = Path.Combine(root, "snapshot-" + index.ToString("D3") + ".snap");
372
+ File.WriteAllBytes(path, new[] { (byte)index });
373
+ File.SetLastWriteTimeUtc(path, new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(index));
374
+ newest = path;
375
+ }
376
+ ProfilerMemorySnapshotSupport.ApplyRetention(newest, Budget(new ManualClock(), scans: 10000, nodes: 4096, getters: 4096));
377
+ Assert.That(Directory.GetFiles(root, "*.snap", SearchOption.TopDirectoryOnly).Length, Is.EqualTo(ProfilerMemorySnapshotSupport.RetainedSnapshotCount));
378
+ Assert.That(File.Exists(newest), Is.True);
379
+ Assert.That(File.Exists(Path.Combine(root, "snapshot-000.snap")), Is.False);
380
+
381
+ CollectionAssert.AreEqual(
382
+ new[] { 0, 1 },
383
+ UnityCliRetentionPlanner.RetainedIndices(
384
+ new[] { ProfilerMemorySnapshotSupport.AbsoluteSnapshotBytes, ProfilerMemorySnapshotSupport.AbsoluteSnapshotBytes, 1L },
385
+ ProfilerMemorySnapshotSupport.RetainedSnapshotCount,
386
+ ProfilerMemorySnapshotSupport.RetainedSnapshotBytes).OrderBy(value => value).ToArray());
387
+ Assert.DoesNotThrow(() => ProfilerMemorySnapshotSupport.ValidateSnapshotLength(
388
+ ProfilerMemorySnapshotSupport.AbsoluteSnapshotBytes,
389
+ ProfilerMemorySnapshotSupport.AbsoluteSnapshotBytes));
390
+ Assert.Throws<UnityCliBridge.BridgeCommandException>(() => ProfilerMemorySnapshotSupport.ValidateSnapshotLength(
391
+ ProfilerMemorySnapshotSupport.AbsoluteSnapshotBytes + 1L,
392
+ ProfilerMemorySnapshotSupport.AbsoluteSnapshotBytes));
393
+
394
+ for (var index = 0; index < 10; index++)
395
+ File.WriteAllBytes(Path.Combine(root, ".pending-orphan-" + index.ToString("D2") + ".snap"), new byte[] { 1 });
396
+ Assert.That(ProfilerMemorySnapshotSupport.CleanupOrphanPass(), Is.True);
397
+ Assert.That(Directory.GetFiles(root, ".pending-*.snap", SearchOption.TopDirectoryOnly), Is.Empty);
398
+ }
399
+ finally
400
+ {
401
+ ProfilerMemorySnapshotSupport.ResetTestOverrides();
402
+ DeleteTestRoot(root);
403
+ }
404
+ }
405
+
406
+ [Test]
407
+ public void ScreenshotPendingPublicationIsUniqueAtomicAndFaultOwned()
408
+ {
409
+ var root = CreateTestRoot("screen");
410
+ try
411
+ {
412
+ var output = Path.Combine(root, "capture.png");
413
+ using (var first = new ScreenshotScreenTool.PendingCapturePublication(output))
414
+ using (var second = new ScreenshotScreenTool.PendingCapturePublication(output))
415
+ {
416
+ Assert.That(first.PendingPath, Is.Not.EqualTo(second.PendingPath));
417
+ File.WriteAllText(first.PendingPath, "first");
418
+ File.WriteAllText(second.PendingPath, "second");
419
+ first.Publish();
420
+ Assert.That(File.ReadAllText(output), Is.EqualTo("first"));
421
+ Assert.That(first.TryFault(), Is.False);
422
+ Assert.That(second.TryFault(), Is.True);
423
+ Assert.That(File.Exists(second.PendingPath), Is.False);
424
+ Assert.Throws<InvalidOperationException>(() => second.Publish());
425
+ Assert.That(File.ReadAllText(output), Is.EqualTo("first"));
426
+ }
427
+ string latePath;
428
+ using (var late = new ScreenshotScreenTool.PendingCapturePublication(output))
429
+ {
430
+ latePath = late.PendingPath;
431
+ late.BeginWrite();
432
+ Assert.That(late.TryFault(), Is.True);
433
+ }
434
+ File.WriteAllText(latePath, "late");
435
+ using (var sweep = new ScreenshotScreenTool.PendingCapturePublication(output))
436
+ {
437
+ Assert.That(File.Exists(latePath), Is.False);
438
+ Assert.That(sweep.TryFault(), Is.True);
439
+ }
440
+ Assert.That(Directory.GetFiles(root, "*.unitycli.pending", SearchOption.TopDirectoryOnly), Is.Empty);
441
+ }
442
+ finally
443
+ {
444
+ DeleteTestRoot(root);
445
+ }
446
+ }
447
+
448
+ [Test]
449
+ public void TextureAndRawViewOwnershipReleaseInitializationFailures()
450
+ {
451
+ Texture2D texture = null;
452
+ var destroyed = false;
453
+ Assert.Throws<InvalidOperationException>(() => ScreenshotToolSupport.CreateTextureWithOwnership(
454
+ () => texture = new Texture2D(2, 2, TextureFormat.RGB24, false),
455
+ value => { throw new InvalidOperationException("populate failed"); },
456
+ value => { destroyed = true; UnityEngine.Object.DestroyImmediate(value); }));
457
+ Assert.That(destroyed, Is.True);
458
+ Assert.That(texture == null, Is.True);
459
+
460
+ var rawView = new DisposableProbe();
461
+ Assert.Throws<InvalidOperationException>(() => ProfilerHierarchyTool.RawHierarchyAccessor.InitializeOwnedView<object>(
462
+ rawView,
463
+ value => { throw new InvalidOperationException("binding failed"); }));
464
+ Assert.That(rawView.Disposed, Is.True);
465
+ }
466
+
467
+ private static GameObject NewRoot(Scene scene, string name)
468
+ {
469
+ var gameObject = new GameObject(name);
470
+ SceneManager.MoveGameObjectToScene(gameObject, scene);
471
+ return gameObject;
472
+ }
473
+
474
+ private static string CreateTestRoot(string suffix)
475
+ {
476
+ var parent = Path.GetFullPath(Path.Combine("Library", "UnityCli", "tests"));
477
+ var root = Path.Combine(parent, "issue-042-" + suffix + "-" + Guid.NewGuid().ToString("N"));
478
+ Directory.CreateDirectory(root);
479
+ Assert.That(Directory.Exists(root), Is.True);
480
+ return root;
481
+ }
482
+
483
+ private static void DeleteTestRoot(string root)
484
+ {
485
+ var parent = Path.GetFullPath(Path.Combine("Library", "UnityCli", "tests")) + Path.DirectorySeparatorChar;
486
+ var canonical = Path.GetFullPath(root);
487
+ Assert.That(canonical.StartsWith(parent, StringComparison.OrdinalIgnoreCase), Is.True);
488
+ if (Directory.Exists(canonical)) Directory.Delete(canonical, true);
489
+ Assert.That(Directory.Exists(canonical), Is.False);
490
+ }
491
+
492
+ private static int Traverse(SyntheticTree root, UnityCliRequestBudget budget)
493
+ {
494
+ var count = 0;
495
+ var stack = new Stack<SyntheticTree>();
496
+ stack.Push(root);
497
+ while (stack.Count > 0)
498
+ {
499
+ if (!budget.TryConsumeScannedRecord() || !budget.TryConsumeNode()) break;
500
+ var current = stack.Pop();
501
+ count++;
502
+ for (var index = current.Children.Count - 1; index >= 0; index--) stack.Push(current.Children[index]);
503
+ }
504
+ return count;
505
+ }
506
+
507
+ private static UnityCliRequestBudget Budget(ManualClock clock, long scans = 10000, long nodes = 4096, long getters = 2048, long reflections = 4096, long serialized = 8 * 1024 * 1024, long milliseconds = 2000)
508
+ {
509
+ return new UnityCliRequestBudget(new UnityCliReadBudgetInput
510
+ {
511
+ MaxScannedRecords = scans,
512
+ MaxNodes = nodes,
513
+ MaxGetters = getters,
514
+ MaxReflectionCalls = reflections,
515
+ MaxMainThreadMilliseconds = milliseconds,
516
+ MaxSerializedUtf8Bytes = serialized
517
+ }, 30000, CancellationToken.None, clock);
518
+ }
519
+
520
+ private sealed class ManualClock : IUnityCliMonotonicClock
521
+ {
522
+ public long ElapsedMilliseconds { get; private set; }
523
+ public void Advance(long milliseconds) { ElapsedMilliseconds += milliseconds; }
524
+ }
525
+
526
+ private sealed class Node { public string Name { get; set; } public Node Next { get; set; } }
527
+
528
+ private sealed class GetterFixture
529
+ {
530
+ public int GetterCount;
531
+ public string First { get { GetterCount++; return "first"; } }
532
+ public string Second { get { GetterCount++; return "second"; } }
533
+ }
534
+
535
+ private sealed class DisposableProbe : IDisposable
536
+ {
537
+ internal bool Disposed { get; private set; }
538
+ public void Dispose() { Disposed = true; }
539
+ }
540
+
541
+ private sealed class Unity2022RawFrameDataViewProbe
542
+ {
543
+ private static readonly int[] DirectChildren = { 2, 1, 0, 1, 0 };
544
+ private static readonly int[] RecursiveChildren = { 4, 1, 0, 1, 0 };
545
+
546
+ public bool valid { get { return true; } }
547
+ public int sampleCount { get { return DirectChildren.Length; } }
548
+ public int frameIndex { get { return 17; } }
549
+ public int threadIndex { get { return 0; } }
550
+ public double frameStartTimeMs { get { return 100.0; } }
551
+ public float frameTimeMs { get { return 16.0f; } }
552
+ public int GetSampleChildrenCount(int sampleIndex) { return DirectChildren[sampleIndex]; }
553
+ public int GetSampleChildrenCountRecursive(int sampleIndex) { return RecursiveChildren[sampleIndex]; }
554
+ public string GetSampleName(int sampleIndex) { return "sample-" + sampleIndex.ToString(); }
555
+ public float GetSampleTimeMs(int sampleIndex) { return sampleIndex; }
556
+ }
557
+
558
+ private sealed class EmptyConsoleSnapshot : EditorToolSupport.IConsoleSnapshotAdapter
559
+ {
560
+ internal EmptyConsoleSnapshot(int count) { Count = count; }
561
+ public int Count { get; private set; }
562
+ internal int ReadCount { get; private set; }
563
+ public bool TryRead(int rawIndex, UnityCliRequestBudget budget, out ConsoleEntry result)
564
+ {
565
+ result = null;
566
+ if (!budget.TryConsumeScannedRecord()) return false;
567
+ ReadCount++;
568
+ result = new ConsoleEntry { Type = "log", Message = string.Empty, File = string.Empty };
569
+ return true;
570
+ }
571
+ }
572
+
573
+ private enum Issue042Enum { First, Second, Third }
574
+
575
+ private static class Issue042InvocationFixture
576
+ {
577
+ internal static int InvocationCount;
578
+ public static int Invoke(int first, int second, int third)
579
+ {
580
+ InvocationCount++;
581
+ return first + second + third;
582
+ }
583
+ }
584
+
585
+ private sealed class CountingEnumerable : IEnumerable
586
+ {
587
+ private readonly int count;
588
+ internal CountingEnumerable(int count) { this.count = count; }
589
+ internal int MoveNextCount { get; private set; }
590
+ public IEnumerator GetEnumerator() { return new Counter(this); }
591
+
592
+ private sealed class Counter : IEnumerator
593
+ {
594
+ private readonly CountingEnumerable owner;
595
+ private int index = -1;
596
+ internal Counter(CountingEnumerable owner) { this.owner = owner; }
597
+ public object Current { get { return index; } }
598
+ public bool MoveNext() { owner.MoveNextCount++; index++; return index < owner.count; }
599
+ public void Reset() { index = -1; }
600
+ }
601
+ }
602
+
603
+ private sealed class SyntheticTree
604
+ {
605
+ internal readonly List<SyntheticTree> Children = new List<SyntheticTree>();
606
+ internal static SyntheticTree Wide(int depth, int width)
607
+ {
608
+ var root = new SyntheticTree();
609
+ if (depth == 0) return root;
610
+ for (var index = 0; index < width; index++) root.Children.Add(Wide(depth - 1, width));
611
+ return root;
612
+ }
613
+ }
614
+ }
615
+ }