@alpsckr/unitycli 0.4.3 → 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 +11 -2
  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
@@ -1,6 +1,8 @@
1
1
  #if UNITY_EDITOR
2
2
  using System;
3
+ using System.Collections.Generic;
3
4
  using System.IO;
5
+ using System.Threading;
4
6
  using UnityEngine;
5
7
 
6
8
  namespace UnityCli
@@ -15,7 +17,9 @@ namespace UnityCli
15
17
  [UnityCliRequired] public string Path { get; set; }
16
18
  public bool Overwrite { get; set; }
17
19
  public bool InlineBase64 { get; set; }
18
- public int? SuperSize { get; set; }
20
+ [UnityCliRange(1L, 4L)] public int? SuperSize { get; set; }
21
+ public ScreenshotBudgetInput Limits { get; set; }
22
+ public UnityCliReadBudgetInput Budget { get; set; }
19
23
  }
20
24
 
21
25
  public sealed class Result : ScreenshotCaptureResult { }
@@ -24,7 +28,7 @@ namespace UnityCli
24
28
  {
25
29
  Name = "screenshot.screen",
26
30
  Summary = "Capture the final composited screen including screen-space UI to a PNG file.",
27
- Description = "Capture the final composited frame (including Screen Space Overlay Canvas UI) via ScreenCapture. Resolution follows the Game View size and cannot be set arbitrarily; capture is most reliable in Play Mode and the file is written at the end of the frame.",
31
+ Description = "Capture the final composited frame including screen-space UI. superSize is 1..4 and the resulting Game View dimensions must each be 1..8192 with at most 16777216 pixels. Before ScreenCapture, the plan must fit default ceilings of 33554432 total pixels, 128 MiB temporary memory and 64 MiB artifacts (public maxima 67108864, 256 MiB and 256 MiB). The file is produced at frame end; available in Edit and Play Mode but most reliable in Play Mode.",
28
32
  Group = "screenshot",
29
33
  DestructiveHint = true,
30
34
  SideEffects = new[] { "asset-write" },
@@ -42,39 +46,195 @@ namespace UnityCli
42
46
  throw new UnityCliBridge.BridgeCommandException("E_SCREENSHOT_FORMAT_UNSUPPORTED", "screenshot.screen only writes .png output.", "Pass a .png output path.");
43
47
  if (File.Exists(outputPath) && !request.Overwrite)
44
48
  throw new UnityCliBridge.BridgeCommandException("E_OUTPUT_EXISTS", "Screenshot output already exists: " + outputPath, "Pass overwrite=true or choose a different output path.");
49
+ var requestBudget = ctx.UseReadBudget(request.Budget);
50
+ var superSize = request.SuperSize ?? 1;
51
+ if (!requestBudget.TryConsumeGetter() || !requestBudget.TryConsumeGetter()) throw new UnityCliRequestBudgetExceededException(requestBudget.ExhaustedDimension);
52
+ var width = checked(Screen.width * superSize);
53
+ var height = checked(Screen.height * superSize);
54
+ var captureBudget = ScreenshotToolSupport.PrepareCaptureBudget(width, height, 1, false, request.Limits, requestBudget);
45
55
  ctx.CommitEffect();
46
56
  ScreenshotToolSupport.CreateScreenshotDirectory(outputPath);
47
- var superSize = Math.Max(1, request.SuperSize ?? 1);
48
-
49
- // ScreenCapture 抓最终合成画面(含屏幕空间 UI),在帧末异步落盘。
50
- ScreenCapture.CaptureScreenshot(outputPath, superSize);
51
-
52
- var written = await ctx.Wait.Until(() => File.Exists(outputPath) && new FileInfo(outputPath).Length > 0, TimeSpan.FromMilliseconds(Math.Max(1, ctx.TimeoutMs)), ct);
53
- if (!written)
54
- throw new UnityCliBridge.BridgeCommandException("E_EXECUTION_TIMEOUT", "ScreenCapture did not write the screenshot before the deadline.", "Retry with a larger timeout, or use screenshot.game-view for camera-only capture.");
57
+ captureBudget.BeginCapture(width, height);
58
+ ScreenshotArtifactProof artifact;
59
+ using (var publication = new PendingCapturePublication(outputPath))
60
+ {
61
+ // ScreenCapture 只写本请求的唯一 pending;旧 final 与并发请求都不能满足 fresh-write proof。
62
+ publication.BeginWrite();
63
+ ScreenCapture.CaptureScreenshot(publication.PendingPath, superSize);
64
+ var written = await ctx.Wait.Until(
65
+ () => File.Exists(publication.PendingPath) && new FileInfo(publication.PendingPath).Length > 0,
66
+ TimeSpan.FromMilliseconds(Math.Max(1, ctx.TimeoutMs)),
67
+ ct);
68
+ ctx.ReadBudget.ResumeAfterWait();
69
+ if (!written)
70
+ {
71
+ publication.TryFault();
72
+ throw new UnityCliBridge.BridgeCommandException("E_EXECUTION_TIMEOUT", "ScreenCapture did not write the screenshot before the deadline.", "Retry with a larger timeout, or use screenshot.game-view for camera-only capture.");
73
+ }
55
74
 
56
- var artifact = ScreenshotToolSupport.VerifyScreenshotArtifact(outputPath, "png");
57
- var (width, height) = ReadPngDimensions(outputPath);
75
+ captureBudget.ChargeArtifact(new FileInfo(publication.PendingPath).Length);
76
+ artifact = ScreenshotToolSupport.VerifyScreenshotArtifact(publication.PendingPath, "png", captureBudget);
77
+ publication.Publish();
78
+ artifact.Path = outputPath;
79
+ }
80
+ var dimensions = ReadPngDimensions(outputPath);
58
81
  var result = new Result
59
82
  {
60
83
  ScreenshotPath = outputPath, ArtifactPath = outputPath, Source = "screen",
61
- SizeBytes = artifact.SizeBytes, Sha256 = artifact.Sha256
84
+ SizeBytes = artifact.SizeBytes, Sha256 = artifact.Sha256, CapturePlan = captureBudget.Plan
62
85
  };
63
- if (request.InlineBase64)
86
+ if (request.InlineBase64 && artifact.SizeBytes <= 512 * 1024)
64
87
  {
65
88
  var bytes = File.ReadAllBytes(outputPath);
66
- ScreenshotToolSupport.MaybeAppendInline(result, bytes, (int)width, (int)height, "image/png", true);
89
+ ScreenshotToolSupport.MaybeAppendInline(result, bytes, (int)dimensions.width, (int)dimensions.height, "image/png", true, captureBudget);
67
90
  }
68
91
  return result;
69
92
  }
70
93
 
94
+ private static readonly object PublicationLock = new object();
95
+ private static readonly object PendingLock = new object();
96
+ private static readonly HashSet<string> LivePending = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
97
+ private static readonly HashSet<string> LateWriterPending = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
98
+ private const int PendingOwnerLimit = 8;
99
+ private const int MaximumObservedPendingFiles = 1024;
100
+
101
+ /// <summary>每次 capture 都有唯一同目录 pending;迟到 writer 的 owner 不复用,当前启动最多保留8个。</summary>
102
+ internal sealed class PendingCapturePublication : IDisposable
103
+ {
104
+ private readonly string outputPath;
105
+ private int terminal;
106
+ private int writerStarted;
107
+
108
+ internal PendingCapturePublication(string outputPath)
109
+ {
110
+ if (string.IsNullOrWhiteSpace(outputPath)) throw new ArgumentException("Output path is required.", "outputPath");
111
+ this.outputPath = outputPath;
112
+ SweepObservedPending(Path.GetDirectoryName(outputPath));
113
+ PendingPath = outputPath + "." + Guid.NewGuid().ToString("N") + ".unitycli.pending";
114
+ lock (PendingLock)
115
+ {
116
+ if (LivePending.Count + LateWriterPending.Count >= PendingOwnerLimit)
117
+ throw new UnityCliBridge.BridgeCommandException("E_SCREENSHOT_PENDING_SLOTS_BUSY", "All 8 screenshot pending owners are waiting for capture or late-writer cleanup.", "Wait for prior ScreenCapture writes to settle, then retry.");
118
+ LivePending.Add(PendingPath);
119
+ }
120
+ try { DeletePendingStrict(PendingPath); }
121
+ catch
122
+ {
123
+ lock (PendingLock) LivePending.Remove(PendingPath);
124
+ throw;
125
+ }
126
+ }
127
+
128
+ internal string PendingPath { get; private set; }
129
+
130
+ internal void BeginWrite()
131
+ {
132
+ if (Volatile.Read(ref terminal) != 0) throw new InvalidOperationException("Screenshot pending publication already reached a terminal state.");
133
+ Volatile.Write(ref writerStarted, 1);
134
+ }
135
+
136
+ internal void Publish()
137
+ {
138
+ if (Interlocked.CompareExchange(ref terminal, 1, 0) != 0)
139
+ throw new InvalidOperationException("Screenshot pending publication already reached a terminal state.");
140
+ try { PublishPending(PendingPath, outputPath); }
141
+ catch
142
+ {
143
+ DeletePendingStrict(PendingPath);
144
+ throw;
145
+ }
146
+ finally
147
+ {
148
+ lock (PendingLock) LivePending.Remove(PendingPath);
149
+ }
150
+ }
151
+
152
+ internal bool TryFault()
153
+ {
154
+ if (Interlocked.CompareExchange(ref terminal, 2, 0) != 0) return false;
155
+ DeletePendingStrict(PendingPath);
156
+ lock (PendingLock)
157
+ {
158
+ LivePending.Remove(PendingPath);
159
+ if (Volatile.Read(ref writerStarted) != 0) LateWriterPending.Add(PendingPath);
160
+ }
161
+ return true;
162
+ }
163
+
164
+ public void Dispose()
165
+ {
166
+ TryFault();
167
+ }
168
+ }
169
+
170
+ /// <summary>只观察最多1024个产品 pending;迟到文件出现后删除并释放对应 owner。</summary>
171
+ private static void SweepObservedPending(string directory)
172
+ {
173
+ if (string.IsNullOrWhiteSpace(directory) || !Directory.Exists(directory)) return;
174
+ var observed = 0;
175
+ foreach (var path in Directory.EnumerateFiles(directory, "*.unitycli.pending", SearchOption.TopDirectoryOnly))
176
+ {
177
+ observed++;
178
+ if (observed > MaximumObservedPendingFiles)
179
+ throw new UnityCliBridge.BridgeCommandException("E_SCREENSHOT_PENDING_SCAN_LIMIT", "Screenshot pending cleanup exceeded the 1024-file observation limit.", "Remove stale *.unitycli.pending files from the output directory and retry.");
180
+ var lateWriter = false;
181
+ var delete = false;
182
+ lock (PendingLock)
183
+ {
184
+ lateWriter = LateWriterPending.Contains(path);
185
+ delete = lateWriter || !LivePending.Contains(path);
186
+ }
187
+ if (!delete) continue;
188
+ DeletePendingStrict(path);
189
+ if (lateWriter)
190
+ {
191
+ lock (PendingLock) LateWriterPending.Remove(path);
192
+ }
193
+ }
194
+ }
195
+
196
+ /// <summary>同目录唯一 pending 在进程内串行原子 rename;失败时保留原 final。</summary>
197
+ internal static void PublishPending(string pendingPath, string outputPath)
198
+ {
199
+ lock (PublicationLock)
200
+ {
201
+ try
202
+ {
203
+ if (File.Exists(outputPath)) File.Replace(pendingPath, outputPath, null, true);
204
+ else File.Move(pendingPath, outputPath);
205
+ }
206
+ catch (Exception exception)
207
+ {
208
+ throw new UnityCliBridge.BridgeCommandException("E_SCREENSHOT_PUBLISH_FAILED", "Failed to atomically publish screenshot output: " + exception.Message, "Inspect output permissions and retry after the pending file is clean.");
209
+ }
210
+ }
211
+ }
212
+
213
+ /// <summary>pending删除失败必须显式终止,不能积累或被后续capture复用。</summary>
214
+ internal static void DeletePendingStrict(string pendingPath)
215
+ {
216
+ try
217
+ {
218
+ if (File.Exists(pendingPath)) File.Delete(pendingPath);
219
+ if (File.Exists(pendingPath)) throw new IOException("pending path still exists after deletion");
220
+ }
221
+ catch (Exception exception)
222
+ {
223
+ throw new UnityCliBridge.BridgeCommandException("E_SCREENSHOT_PENDING_CLEANUP_FAILED", "Failed to clean screenshot pending output: " + exception.Message, "Remove the reported pending file after checking its exact path, then retry.");
224
+ }
225
+ }
226
+
71
227
  // 从落盘 PNG 的 IHDR 读取实际宽高(大端),用于报告 Game View 实际分辨率。
72
228
  private static (long width, long height) ReadPngDimensions(string path)
73
229
  {
74
230
  try
75
231
  {
76
- var bytes = File.ReadAllBytes(path);
77
- if (bytes.Length >= 24 && bytes[0] == 0x89 && bytes[1] == 0x50 && bytes[2] == 0x4e && bytes[3] == 0x47)
232
+ var bytes = new byte[24];
233
+ using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
234
+ {
235
+ if (stream.Read(bytes, 0, bytes.Length) < bytes.Length) return (0, 0);
236
+ }
237
+ if (bytes[0] == 0x89 && bytes[1] == 0x50 && bytes[2] == 0x4e && bytes[3] == 0x47)
78
238
  {
79
239
  long width = ((long)bytes[16] << 24) | ((long)bytes[17] << 16) | ((long)bytes[18] << 8) | bytes[19];
80
240
  long height = ((long)bytes[20] << 24) | ((long)bytes[21] << 16) | ((long)bytes[22] << 8) | bytes[23];
@@ -6,9 +6,7 @@ using UnityEngine;
6
6
 
7
7
  namespace UnityCli
8
8
  {
9
- /// <summary>
10
- /// screenshot.surround — 6 角度环绕截图并生成 contact sheet。破坏性(asset-write)。
11
- /// </summary>
9
+ /// <summary>screenshot.surround — 六张逐次释放的捕获与一个 aggregate contact sheet。</summary>
12
10
  public class ScreenshotSurroundTool : IUnityCliTool<ScreenshotSurroundTool.Request, ScreenshotSurroundTool.Result>
13
11
  {
14
12
  public sealed class Request
@@ -16,12 +14,14 @@ namespace UnityCli
16
14
  [UnityCliRequired] public string Target { get; set; }
17
15
  public string OutputDir { get; set; } = "Assets/Screenshots/Surround";
18
16
  public string ContactSheetPath { get; set; }
19
- public int? Width { get; set; }
20
- public int? Height { get; set; }
17
+ [UnityCliRange(1L, 8192L)] public int? Width { get; set; }
18
+ [UnityCliRange(1L, 8192L)] public int? Height { get; set; }
21
19
  public float? Radius { get; set; }
22
20
  public float? Fov { get; set; }
23
21
  public bool Overwrite { get; set; }
24
22
  public bool InlineBase64 { get; set; }
23
+ public ScreenshotBudgetInput Limits { get; set; }
24
+ public UnityCliReadBudgetInput Budget { get; set; }
25
25
  }
26
26
 
27
27
  public sealed class Result
@@ -35,6 +35,7 @@ namespace UnityCli
35
35
  public long? ImageWidth { get; set; }
36
36
  public long? ImageHeight { get; set; }
37
37
  public string ImageMimeType { get; set; }
38
+ public ScreenshotCapturePlan CapturePlan { get; set; }
38
39
  }
39
40
 
40
41
  public sealed class SurroundShot
@@ -47,134 +48,137 @@ namespace UnityCli
47
48
  public ToolDescriptor Describe() => new()
48
49
  {
49
50
  Name = "screenshot.surround",
50
- Summary = "Capture six fixed-angle screenshots and compose a 3x2 contact sheet; dry-run supported.",
51
- Description = "Capture six fixed angles (front, back, left, right, top, bird_eye) around a GameObject and compose a 3x2 contact sheet PNG. Supports --dry-run to preview planned artifacts. Editor-only.",
52
- Group = "screenshot",
53
- DestructiveHint = true,
54
- SideEffects = new[] { "asset-write" },
55
- Target = "editor",
56
- PlayModeSupport = "editor-only",
57
- SupportsDryRun = true,
58
- DefaultTimeoutMs = 30000,
59
- MaxTimeoutMs = 600000,
60
- Examples = new[] { new ToolExample { Description = "Surround a target (dry-run)", Input = new Request { Target = "Player" }, Execution = new ToolExampleExecution { DryRun = true } } }
51
+ Summary = "Capture six fixed views and one bounded contact sheet while releasing every tile immediately.",
52
+ Description = "Capture front/back/left/right/top/bird_eye PNGs plus a 3x2 contact sheet. Tile and contact-sheet dimensions each must satisfy 1..8192 and 16777216 pixels per image. Before dry-run or the first effect, the seven-output aggregate plan must fit default ceilings of 33554432 total pixels, 128 MiB peak temporary memory and 64 MiB artifacts; public maxima are 67108864, 256 MiB and 256 MiB. radius must be positive and fov must be 10..120. Each tile is copied into the sheet, encoded, written and destroyed before the next capture. Editor-only; dry-run returns the same frozen plan without effects.",
53
+ Group = "screenshot", DestructiveHint = true, SideEffects = new[] { "asset-write" },
54
+ Target = "editor", PlayModeSupport = "editor-only", SupportsDryRun = true,
55
+ DefaultTimeoutMs = 30000, MaxTimeoutMs = 600000,
56
+ Examples = new[] { new ToolExample { Description = "Preview surround artifacts", Input = new Request { Target = "Player" }, Execution = new ToolExampleExecution { DryRun = true } } }
61
57
  };
62
58
 
63
59
  public System.Threading.Tasks.ValueTask<Result> ExecuteAsync(Request request, UnityCliProjectToolContext ctx, System.Threading.CancellationToken ct = default)
64
60
  {
65
- var go = UnityCliBridge.ObjectRefResolver.FindGameObjectByPath(request.Target);
66
- if (go == null)
67
- throw new UnityCliBridge.BridgeCommandException("E_TARGET_NOT_FOUND", "screenshot.surround requires a target GameObject.", "Pass --target <path>.");
61
+ var requestBudget = ctx.UseReadBudget(request.Budget);
62
+ var gameObject = UnityCliBridge.ObjectRefResolver.FindGameObjectByExactPath(request.Target, requestBudget);
63
+ if (gameObject == null) throw new UnityCliBridge.BridgeCommandException("E_TARGET_NOT_FOUND", "screenshot.surround requires a target GameObject.", "Pass an exact hierarchy path.");
68
64
  var outputDir = request.OutputDir ?? "Assets/Screenshots/Surround";
69
65
  var outputFullDir = ScreenshotToolSupport.AssetFullPath(outputDir);
70
66
  var contactSheetPath = ScreenshotToolSupport.ResolveScreenshotOutputPath(request.ContactSheetPath ?? Path.Combine(outputDir, "surround_contact_sheet.png"));
71
- var width = Math.Max(1, request.Width ?? 512);
72
- var height = Math.Max(1, request.Height ?? 512);
73
- var bounds = ScreenshotToolSupport.TargetRenderBounds(go);
67
+ var width = request.Width ?? 512;
68
+ var height = request.Height ?? 512;
69
+ var bounds = ScreenshotToolSupport.TargetRenderBounds(gameObject, requestBudget);
74
70
  var center = bounds.center;
75
71
  var radius = request.Radius ?? Mathf.Max(bounds.extents.magnitude * 2.5f, 5f);
72
+ var fov = request.Fov ?? 60f;
73
+ if (float.IsNaN(radius) || float.IsInfinity(radius) || radius <= 0f)
74
+ throw new UnityCliBridge.BridgeCommandException("E_SCREENSHOT_LIMIT_INVALID", "radius must be finite and greater than zero.", "Pass a positive finite radius.");
75
+ if (float.IsNaN(fov) || float.IsInfinity(fov) || fov < 10f || fov > 120f)
76
+ throw new UnityCliBridge.BridgeCommandException("E_SCREENSHOT_LIMIT_INVALID", "fov must be finite in 10..120.", "Pass a supported field of view.");
77
+ var captureBudget = ScreenshotToolSupport.PrepareCaptureBudget(width, height, 6, true, request.Limits, requestBudget);
76
78
 
77
79
  var labels = new[] { "front", "back", "left", "right", "top", "bird_eye" };
78
80
  var positions = new[]
79
81
  {
80
- center + new Vector3(0, 0, -radius),
81
- center + new Vector3(0, 0, radius),
82
- center + new Vector3(-radius, 0, 0),
83
- center + new Vector3(radius, 0, 0),
84
- center + new Vector3(0, radius, 0),
85
- center + new Vector3(radius * 0.7f, radius * 0.7f, -radius * 0.7f)
82
+ center + new Vector3(0, 0, -radius), center + new Vector3(0, 0, radius),
83
+ center + new Vector3(-radius, 0, 0), center + new Vector3(radius, 0, 0),
84
+ center + new Vector3(0, radius, 0), center + new Vector3(radius * 0.7f, radius * 0.7f, -radius * 0.7f)
86
85
  };
87
86
  var shotPaths = new List<string>();
88
- for (var i = 0; i < labels.Length; i++)
87
+ for (var index = 0; index < labels.Length; index++)
89
88
  {
90
- var path = Path.Combine(outputFullDir, "surround_" + labels[i] + ".png");
91
- if (File.Exists(path) && !request.Overwrite)
92
- throw new UnityCliBridge.BridgeCommandException("E_OUTPUT_EXISTS", "Screenshot output already exists: " + path, "Pass overwrite=true or choose a different output-dir.");
89
+ var path = Path.Combine(outputFullDir, "surround_" + labels[index] + ".png");
90
+ if (File.Exists(path) && !request.Overwrite) throw new UnityCliBridge.BridgeCommandException("E_OUTPUT_EXISTS", "Screenshot output already exists: " + path, "Pass overwrite=true or choose a different outputDir.");
93
91
  shotPaths.Add(path);
94
92
  }
95
- if (File.Exists(contactSheetPath) && !request.Overwrite)
96
- throw new UnityCliBridge.BridgeCommandException("E_OUTPUT_EXISTS", "Contact sheet output already exists: " + contactSheetPath, "Pass overwrite=true or choose a different contactSheetPath.");
97
-
93
+ if (File.Exists(contactSheetPath) && !request.Overwrite) throw new UnityCliBridge.BridgeCommandException("E_OUTPUT_EXISTS", "Contact sheet output already exists: " + contactSheetPath, "Pass overwrite=true or choose another path.");
94
+ var planned = BuildPlannedShots(labels, shotPaths, width, height, positions);
98
95
  if (ctx.DryRun)
99
96
  {
100
97
  return new System.Threading.Tasks.ValueTask<Result>(new Result
101
98
  {
102
- DryRun = true,
103
- Center = new double[] { center.x, center.y, center.z },
104
- ContactSheet = new ScreenshotArtifactEntry { Source = "surround-contact-sheet", Path = contactSheetPath, Width = width * 3, Height = height * 2, Format = "png" },
105
- Artifacts = BuildPlannedShots(labels, shotPaths, width, height, positions)
99
+ DryRun = true, Center = new[] { (double)center.x, center.y, center.z }, Count = 6,
100
+ ContactSheet = new ScreenshotArtifactEntry { Source = "surround-contact-sheet", Path = contactSheetPath, Width = width * 3L, Height = height * 2L, Format = "png" },
101
+ Artifacts = planned, CapturePlan = captureBudget.Plan
106
102
  });
107
103
  }
108
104
 
109
105
  ctx.CommitEffect();
110
106
  Directory.CreateDirectory(outputFullDir);
111
107
  ScreenshotToolSupport.CreateScreenshotDirectory(contactSheetPath);
112
- var tempGo = new GameObject("__unitycli_surround_cam__");
113
- var cam = tempGo.AddComponent<Camera>();
114
- cam.fieldOfView = Mathf.Clamp(request.Fov ?? 60f, 10f, 120f);
115
- cam.nearClipPlane = 0.1f;
116
- cam.farClipPlane = Mathf.Max(radius * 6f, 100f);
117
- cam.clearFlags = CameraClearFlags.SolidColor;
118
- cam.backgroundColor = new Color(0.18f, 0.18f, 0.18f, 1f);
119
- var tiles = new List<Texture2D>();
108
+ GameObject cameraObject = null;
109
+ Camera camera = null;
110
+ Texture2D sheet = null;
120
111
  var artifacts = new List<SurroundShot>();
121
112
  byte[] contactBytes = null;
113
+ ScreenshotArtifactProof contactProof = null;
122
114
  try
123
115
  {
124
- for (var i = 0; i < labels.Length; i++)
116
+ cameraObject = new GameObject("__unitycli_surround_cam__");
117
+ camera = cameraObject.AddComponent<Camera>();
118
+ camera.fieldOfView = fov;
119
+ camera.nearClipPlane = 0.1f;
120
+ camera.farClipPlane = Mathf.Max(radius * 6f, 100f);
121
+ camera.clearFlags = CameraClearFlags.SolidColor;
122
+ camera.backgroundColor = new Color(0.18f, 0.18f, 0.18f, 1f);
123
+ sheet = ScreenshotToolSupport.CreateContactSheet(width, height, captureBudget);
124
+ for (var index = 0; index < labels.Length; index++)
125
125
  {
126
- cam.transform.position = positions[i];
127
- cam.transform.LookAt(center);
128
- var tile = ScreenshotToolSupport.RenderScreenshotTexture(cam, width, height);
129
- tiles.Add(tile);
130
- var proof = ScreenshotToolSupport.WriteScreenshotBytes(shotPaths[i], tile.EncodeToPNG());
131
- artifacts.Add(new SurroundShot
126
+ camera.transform.position = positions[index];
127
+ camera.transform.LookAt(center);
128
+ var tile = ScreenshotToolSupport.RenderScreenshotTexture(camera, width, height, captureBudget);
129
+ try
132
130
  {
133
- Angle = labels[i],
134
- Artifact = new ScreenshotArtifactEntry { Source = "surround", Path = shotPaths[i], Camera = "surround:" + labels[i], Width = width, Height = height, Format = "png", SizeBytes = proof.SizeBytes, Sha256 = proof.Sha256 },
135
- CameraPosition = new double[] { positions[i].x, positions[i].y, positions[i].z }
136
- });
131
+ ScreenshotToolSupport.CopyContactSheetTile(sheet, tile, index, width, height);
132
+ var proof = ScreenshotToolSupport.WriteScreenshotBytes(shotPaths[index], tile.EncodeToPNG(), captureBudget);
133
+ artifacts.Add(new SurroundShot
134
+ {
135
+ Angle = labels[index],
136
+ Artifact = new ScreenshotArtifactEntry { Source = "surround", Path = shotPaths[index], Camera = "surround:" + labels[index], Width = width, Height = height, Format = "png", SizeBytes = proof.SizeBytes, Sha256 = proof.Sha256 },
137
+ CameraPosition = new[] { (double)positions[index].x, positions[index].y, positions[index].z }
138
+ });
139
+ }
140
+ finally { UnityEngine.Object.DestroyImmediate(tile); }
137
141
  }
138
- var contact = ScreenshotToolSupport.ComposeScreenshotContactSheet(tiles, width, height);
139
- try { contactBytes = contact.EncodeToPNG(); ScreenshotToolSupport.WriteScreenshotBytes(contactSheetPath, contactBytes); }
140
- finally { UnityEngine.Object.DestroyImmediate(contact); }
142
+ sheet.Apply(false, false);
143
+ contactBytes = sheet.EncodeToPNG();
144
+ contactProof = ScreenshotToolSupport.WriteScreenshotBytes(contactSheetPath, contactBytes, captureBudget);
141
145
  }
142
146
  finally
143
147
  {
144
- foreach (var tile in tiles) UnityEngine.Object.DestroyImmediate(tile);
145
- UnityEngine.Object.DestroyImmediate(tempGo);
148
+ if (sheet != null) UnityEngine.Object.DestroyImmediate(sheet);
149
+ if (cameraObject != null) UnityEngine.Object.DestroyImmediate(cameraObject);
146
150
  }
147
151
 
148
- var contactProof = ScreenshotToolSupport.VerifyScreenshotArtifact(contactSheetPath, "png");
149
152
  var result = new Result
150
153
  {
151
- Center = new double[] { center.x, center.y, center.z }, Count = 6,
152
- ContactSheet = new ScreenshotArtifactEntry { Source = "surround-contact-sheet", Path = contactSheetPath, Width = width * 3, Height = height * 2, Format = "png", SizeBytes = contactProof.SizeBytes, Sha256 = contactProof.Sha256 },
153
- Artifacts = artifacts
154
+ Center = new[] { (double)center.x, center.y, center.z }, Count = 6,
155
+ ContactSheet = new ScreenshotArtifactEntry { Source = "surround-contact-sheet", Path = contactSheetPath, Width = width * 3L, Height = height * 2L, Format = "png", SizeBytes = contactProof.SizeBytes, Sha256 = contactProof.Sha256 },
156
+ Artifacts = artifacts, CapturePlan = captureBudget.Plan
154
157
  };
155
- if (request.InlineBase64 && contactBytes != null && contactBytes.Length <= 512 * 1024)
158
+ string encoded;
159
+ if (request.InlineBase64 && contactBytes != null && contactBytes.Length <= 512 * 1024 && captureBudget.TryCreateInlineBase64(contactBytes, out encoded))
156
160
  {
157
- result.ImageBase64 = Convert.ToBase64String(contactBytes);
158
- result.ImageWidth = width * 3;
159
- result.ImageHeight = height * 2;
161
+ result.ImageBase64 = encoded;
162
+ result.ImageWidth = width * 3L;
163
+ result.ImageHeight = height * 2L;
160
164
  result.ImageMimeType = "image/png";
161
165
  }
162
166
  return new System.Threading.Tasks.ValueTask<Result>(result);
163
167
  }
164
168
 
165
- private static List<SurroundShot> BuildPlannedShots(string[] labels, List<string> shotPaths, int width, int height, Vector3[] positions)
169
+ private static List<SurroundShot> BuildPlannedShots(string[] labels, List<string> paths, int width, int height, Vector3[] positions)
166
170
  {
167
- var shots = new List<SurroundShot>();
168
- for (var i = 0; i < labels.Length; i++)
171
+ var result = new List<SurroundShot>();
172
+ for (var index = 0; index < labels.Length; index++)
169
173
  {
170
- shots.Add(new SurroundShot
174
+ result.Add(new SurroundShot
171
175
  {
172
- Angle = labels[i],
173
- Artifact = new ScreenshotArtifactEntry { Source = "surround", Path = shotPaths[i], Camera = "surround:" + labels[i], Width = width, Height = height, Format = "png" },
174
- CameraPosition = new double[] { positions[i].x, positions[i].y, positions[i].z }
176
+ Angle = labels[index],
177
+ Artifact = new ScreenshotArtifactEntry { Source = "surround", Path = paths[index], Camera = "surround:" + labels[index], Width = width, Height = height, Format = "png" },
178
+ CameraPosition = new[] { (double)positions[index].x, positions[index].y, positions[index].z }
175
179
  });
176
180
  }
177
- return shots;
181
+ return result;
178
182
  }
179
183
  }
180
184
  }
@@ -15,9 +15,11 @@ namespace UnityCli
15
15
  {
16
16
  public string Target { get; set; }
17
17
  public string Output { get; set; }
18
- public int? Width { get; set; }
19
- public int? Height { get; set; }
18
+ [UnityCliRange(1L, 8192L)] public int? Width { get; set; }
19
+ [UnityCliRange(1L, 8192L)] public int? Height { get; set; }
20
20
  public bool Overwrite { get; set; }
21
+ public ScreenshotBudgetInput Limits { get; set; }
22
+ public UnityCliReadBudgetInput Budget { get; set; }
21
23
  }
22
24
 
23
25
  public sealed class Result
@@ -67,12 +69,13 @@ namespace UnityCli
67
69
  var document = go.GetComponent<UIDocument>();
68
70
  if (document == null) throw new UnityCliBridge.BridgeCommandException("E_UI_DOCUMENT_NOT_FOUND", "No UIDocument on target.", "Pass a GameObject containing UIDocument.");
69
71
 
70
- var width = Mathf.Max(1, request.Width ?? 1280);
71
- var height = Mathf.Max(1, request.Height ?? 720);
72
+ var width = request.Width ?? 1280;
73
+ var height = request.Height ?? 720;
72
74
  var outputPath = ScreenshotToolSupport.ResolveScreenshotOutputPath(request.Output ?? "Assets/UI/preview.png");
73
75
  if (File.Exists(outputPath) && !request.Overwrite)
74
76
  throw new UnityCliBridge.BridgeCommandException("E_OUTPUT_EXISTS", "UI preview output already exists: " + outputPath, "Pass overwrite=true or choose a different output path.");
75
77
 
78
+ var captureBudget = ScreenshotToolSupport.PrepareCaptureBudget(width, height, 1, false, request.Limits, ctx.UseReadBudget(request.Budget));
76
79
  var effects = new List<UiPlannedEffect> { new UiPlannedEffect { Type = "ui.render-preview", Target = request.Target, Path = outputPath } };
77
80
  if (ctx.DryRun)
78
81
  {
@@ -80,20 +83,24 @@ namespace UnityCli
80
83
  }
81
84
 
82
85
  ctx.CommitEffect();
86
+ captureBudget.BeginCapture(width, height);
83
87
  var originalPanelSettings = document.panelSettings;
84
88
  var originalEnabled = document.enabled;
85
- var renderTexture = new RenderTexture(width, height, 24, RenderTextureFormat.ARGB32);
86
- var panelSettings = ScriptableObject.CreateInstance<PanelSettings>();
89
+ RenderTexture renderTexture = null;
90
+ PanelSettings panelSettings = null;
87
91
  Texture2D texture = null;
88
92
  var previousActive = RenderTexture.active;
89
93
  try
90
94
  {
95
+ renderTexture = new RenderTexture(width, height, 24, RenderTextureFormat.ARGB32);
96
+ panelSettings = ScriptableObject.CreateInstance<PanelSettings>();
91
97
  renderTexture.Create();
92
98
  panelSettings.targetTexture = renderTexture;
93
99
  panelSettings.scaleMode = PanelScaleMode.ConstantPixelSize;
94
100
  document.panelSettings = panelSettings;
95
101
  document.enabled = true;
96
102
  await ctx.Wait.NextFrame(2, ct);
103
+ ctx.ReadBudget.ResumeAfterWait();
97
104
  ct.ThrowIfCancellationRequested();
98
105
 
99
106
  RenderTexture.active = renderTexture;
@@ -101,8 +108,7 @@ namespace UnityCli
101
108
  texture.ReadPixels(new Rect(0, 0, width, height), 0, 0, false);
102
109
  texture.Apply(false, false);
103
110
  var bytes = texture.EncodeToPNG();
104
- ScreenshotToolSupport.WriteScreenshotBytes(outputPath, bytes);
105
- var artifact = ScreenshotToolSupport.VerifyScreenshotArtifact(outputPath, "png");
111
+ var artifact = ScreenshotToolSupport.WriteScreenshotBytes(outputPath, bytes, captureBudget);
106
112
  return new Result
107
113
  {
108
114
  DryRun = false,
@@ -118,9 +124,8 @@ namespace UnityCli
118
124
  document.panelSettings = originalPanelSettings;
119
125
  document.enabled = originalEnabled;
120
126
  if (texture != null) Object.DestroyImmediate(texture);
121
- renderTexture.Release();
122
- Object.DestroyImmediate(renderTexture);
123
- Object.DestroyImmediate(panelSettings);
127
+ if (renderTexture != null) { renderTexture.Release(); Object.DestroyImmediate(renderTexture); }
128
+ if (panelSettings != null) Object.DestroyImmediate(panelSettings);
124
129
  }
125
130
  }
126
131
  }