@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
@@ -3,162 +3,382 @@ using System;
3
3
  using System.Collections.Generic;
4
4
  using System.Globalization;
5
5
  using System.IO;
6
- using System.Linq;
7
6
  using System.Security.Cryptography;
8
- using UnityEditor;
9
7
  using UnityEngine;
10
8
 
11
9
  namespace UnityCli
12
10
  {
13
- /// <summary>
14
- /// screenshot 域 IUnityCliTool 共享的基础设施 helper:渲染纹理、contact sheet、内联 base64。
15
- /// 仅供 screenshot 域自包含工具复用;复用 CameraToolSupport 的路径/格式/捕获 helper。
16
- /// </summary>
11
+ public sealed class ScreenshotBudgetInput
12
+ {
13
+ [UnityCliRange(1L, ScreenshotToolSupport.AbsoluteMaxTotalPixels)] public long? MaxTotalPixels { get; set; }
14
+ [UnityCliRange(1L, ScreenshotToolSupport.AbsoluteMaxTemporaryBytes)] public long? MaxTemporaryBytes { get; set; }
15
+ [UnityCliRange(1024L, ScreenshotToolSupport.AbsoluteMaxArtifactBytes)] public long? MaxArtifactBytes { get; set; }
16
+ }
17
+
18
+ /// <summary>首个捕获 effect 前冻结的 aggregate 资源计划。</summary>
19
+ public sealed class ScreenshotCapturePlan
20
+ {
21
+ public long CaptureCount { get; set; }
22
+ public long TotalPixels { get; set; }
23
+ public long PeakTemporaryBytes { get; set; }
24
+ public long ArtifactByteCeiling { get; set; }
25
+ public long PlannedWorstCaseArtifactBytes { get; set; }
26
+ public long MaxPixelsPerCapture { get; set; }
27
+ }
28
+
29
+ /// <summary>截图域的尺寸、内存、捕获次数和 artifact byte 统一 owner。</summary>
17
30
  public static class ScreenshotToolSupport
18
31
  {
19
- /// <summary>Unity 工程根目录。</summary>
20
- public static string ProjectRoot()
21
- {
22
- return Directory.GetParent(Application.dataPath).FullName;
23
- }
32
+ public const int MaxImageDimension = 8192;
33
+ public const long MaxPixelsPerImage = 16777216L;
34
+ public const int MaxCaptureCount = 36;
35
+ public const long DefaultMaxTotalPixels = 33554432L;
36
+ public const long AbsoluteMaxTotalPixels = 67108864L;
37
+ public const long DefaultMaxTemporaryBytes = 128L * 1024L * 1024L;
38
+ public const long AbsoluteMaxTemporaryBytes = 256L * 1024L * 1024L;
39
+ public const long DefaultMaxArtifactBytes = 64L * 1024L * 1024L;
40
+ public const long AbsoluteMaxArtifactBytes = 256L * 1024L * 1024L;
41
+ private const int InlineImageMaxBytes = 512 * 1024;
24
42
 
25
- /// <summary>Assets/ 路径转工程内绝对路径。</summary>
26
- public static string AssetFullPath(string assetPath)
27
- {
28
- return Path.Combine(ProjectRoot(), assetPath.Replace('/', Path.DirectorySeparatorChar));
29
- }
43
+ public static string ProjectRoot() { return Directory.GetParent(Application.dataPath).FullName; }
44
+ public static string AssetFullPath(string assetPath) { return Path.Combine(ProjectRoot(), assetPath.Replace('/', Path.DirectorySeparatorChar)); }
30
45
 
31
- /// <summary>解析截图输出绝对路径;相对路径解析到 Unity 工程根目录下。</summary>
32
46
  public static string ResolveScreenshotOutputPath(string rawPath)
33
47
  {
34
- if (string.IsNullOrEmpty(rawPath))
35
- {
36
- throw new UnityCliBridge.BridgeCommandException("E_OUTPUT_PATH_REQUIRED", "Screenshot requires a path.", "Pass --output <path> or a positional screenshot path.");
37
- }
48
+ if (string.IsNullOrEmpty(rawPath)) throw new UnityCliBridge.BridgeCommandException("E_OUTPUT_PATH_REQUIRED", "Screenshot requires a path.", "Pass a non-empty screenshot output path.");
38
49
  var normalized = rawPath.Replace('\\', Path.DirectorySeparatorChar).Replace('/', Path.DirectorySeparatorChar);
39
50
  return Path.GetFullPath(Path.IsPathRooted(normalized) ? normalized : Path.Combine(ProjectRoot(), normalized));
40
51
  }
41
52
 
42
- /// <summary>创建截图输出目录(缺失时)。</summary>
43
53
  public static void CreateScreenshotDirectory(string outputPath)
44
54
  {
45
55
  try { Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); }
46
56
  catch (Exception exception) { throw new UnityCliBridge.BridgeCommandException("E_IO_ERROR", "Failed to create screenshot output directory: " + exception.Message); }
47
57
  }
48
58
 
49
- /// <summary>写入截图后重新读取字节、校验格式并返回稳定 artifact proof。</summary>
50
- public static ScreenshotArtifactProof WriteScreenshotBytes(string outputPath, byte[] bytes)
59
+ /// <summary>按所有图片合计值冻结计划;任何无效或超限输入都在 caller CommitEffect 前失败。</summary>
60
+ public static ScreenshotCaptureBudget PrepareCaptureBudget(int width, int height, int captureCount, bool contactSheet, ScreenshotBudgetInput input, UnityCliRequestBudget requestBudget)
51
61
  {
52
- if (bytes == null || bytes.Length == 0)
53
- throw new UnityCliBridge.BridgeCommandException("E_SCREENSHOT_ARTIFACT_EMPTY", "Screenshot encoder produced no image data.", "Check the camera and render settings, then retry the screenshot.");
54
- try { File.WriteAllBytes(outputPath, bytes); }
55
- catch (Exception exception) { throw new UnityCliBridge.BridgeCommandException("E_IO_ERROR", "Failed to write screenshot output: " + exception.Message); }
56
- return VerifyScreenshotArtifact(outputPath, Path.GetExtension(outputPath).TrimStart('.'));
62
+ input = input ?? new ScreenshotBudgetInput();
63
+ var maxTotalPixels = input.MaxTotalPixels ?? DefaultMaxTotalPixels;
64
+ var maxTemporaryBytes = input.MaxTemporaryBytes ?? DefaultMaxTemporaryBytes;
65
+ var maxArtifactBytes = input.MaxArtifactBytes ?? DefaultMaxArtifactBytes;
66
+ ValidateLimit(maxTotalPixels, 1L, AbsoluteMaxTotalPixels, "maxTotalPixels");
67
+ ValidateLimit(maxTemporaryBytes, 1L, AbsoluteMaxTemporaryBytes, "maxTemporaryBytes");
68
+ ValidateLimit(maxArtifactBytes, 1024L, AbsoluteMaxArtifactBytes, "maxArtifactBytes");
69
+ ValidateDimensions(width, height, "capture");
70
+ if (captureCount < 1 || captureCount > MaxCaptureCount) throw Limit("capture count must be between 1 and 36.");
71
+ var imagePixels = checked((long)width * height);
72
+ var sheetPixels = contactSheet ? checked(imagePixels * 6L) : 0L;
73
+ if (contactSheet) ValidateDimensions(checked(width * 3), checked(height * 2), "contact sheet");
74
+ var totalPixels = checked(imagePixels * captureCount + sheetPixels);
75
+ // 同时覆盖 RenderTexture/depth、readable texture、编码缓冲与 contact-sheet copy 缓冲。
76
+ var peakTemporaryBytes = contactSheet
77
+ ? checked(imagePixels * 12L + sheetPixels * 8L + 65536L)
78
+ : checked(imagePixels * 12L + 65536L);
79
+ var worstCaseArtifacts = checked(totalPixels * 4L + (captureCount + (contactSheet ? 1 : 0)) * 65536L);
80
+ if (totalPixels > maxTotalPixels) throw Limit("aggregate totalPixels " + totalPixels.ToString(CultureInfo.InvariantCulture) + " exceeds maxTotalPixels.");
81
+ if (peakTemporaryBytes > maxTemporaryBytes) throw Limit("peak temporary bytes " + peakTemporaryBytes.ToString(CultureInfo.InvariantCulture) + " exceeds maxTemporaryBytes.");
82
+ if (worstCaseArtifacts > maxArtifactBytes) throw Limit("planned worst-case artifact bytes " + worstCaseArtifacts.ToString(CultureInfo.InvariantCulture) + " exceeds maxArtifactBytes.");
83
+ return new ScreenshotCaptureBudget(new ScreenshotCapturePlan
84
+ {
85
+ CaptureCount = captureCount + (contactSheet ? 1 : 0), TotalPixels = totalPixels,
86
+ PeakTemporaryBytes = peakTemporaryBytes, ArtifactByteCeiling = maxArtifactBytes,
87
+ PlannedWorstCaseArtifactBytes = worstCaseArtifacts,
88
+ MaxPixelsPerCapture = contactSheet ? sheetPixels : imagePixels
89
+ }, requestBudget);
57
90
  }
58
91
 
59
- /// <summary>artifact proof 必须来自落盘后的 fresh readback,不能只检查 Exists。</summary>
60
- public static ScreenshotArtifactProof VerifyScreenshotArtifact(string outputPath, string format)
92
+ public static void ValidateDimensions(int width, int height, string owner)
61
93
  {
62
- if (!File.Exists(outputPath))
63
- throw new UnityCliBridge.BridgeCommandException("E_SCREENSHOT_ARTIFACT_MISSING", "Screenshot artifact was not written: " + outputPath, "Check the output path and available disk space, then retry the screenshot.");
64
- var bytes = File.ReadAllBytes(outputPath);
65
- var normalized = NormalizeFormat(format, outputPath);
66
- var valid = normalized == "png"
67
- ? bytes.Length >= 8 && bytes[0] == 0x89 && bytes[1] == 0x50 && bytes[2] == 0x4e && bytes[3] == 0x47 && bytes[4] == 0x0d && bytes[5] == 0x0a && bytes[6] == 0x1a && bytes[7] == 0x0a
68
- : bytes.Length >= 3 && bytes[0] == 0xff && bytes[1] == 0xd8 && bytes[2] == 0xff;
69
- if (!valid)
70
- throw new UnityCliBridge.BridgeCommandException("E_SCREENSHOT_ARTIFACT_INVALID", "Screenshot artifact is empty or does not match its requested format: " + outputPath, "Use a .png or .jpg output path and retry the capture.");
71
- using (var sha = SHA256.Create())
72
- return new ScreenshotArtifactProof { Path = outputPath, SizeBytes = bytes.LongLength, Sha256 = string.Concat(Array.ConvertAll(sha.ComputeHash(bytes), value => value.ToString("x2"))), Format = normalized };
94
+ if (width < 1 || width > MaxImageDimension || height < 1 || height > MaxImageDimension)
95
+ throw Limit(owner + " dimensions must each be between 1 and 8192 pixels.");
96
+ var pixels = checked((long)width * height);
97
+ if (pixels > MaxPixelsPerImage) throw Limit(owner + " image pixels must not exceed 16777216.");
73
98
  }
74
99
 
75
- /// <summary>推断图片格式:显式 format > 路径扩展名 > png;仅接受 png/jpg。</summary>
76
- public static string NormalizeFormat(string rawFormat, string outputPath)
100
+ public static ScreenshotArtifactProof WriteScreenshotBytes(string outputPath, byte[] bytes, ScreenshotCaptureBudget budget)
77
101
  {
78
- var fmt = string.IsNullOrEmpty(rawFormat) ? Path.GetExtension(outputPath).TrimStart('.').ToLowerInvariant() : rawFormat.ToLowerInvariant();
79
- if (string.IsNullOrEmpty(fmt)) fmt = "png";
80
- if (fmt == "jpeg") fmt = "jpg";
81
- if (fmt != "png" && fmt != "jpg")
102
+ if (bytes == null || bytes.Length == 0) throw new UnityCliBridge.BridgeCommandException("E_SCREENSHOT_ARTIFACT_EMPTY", "Screenshot encoder produced no image data.", "Check the camera and render settings, then retry.");
103
+ budget.ChargeArtifact(bytes.LongLength);
104
+ try
105
+ {
106
+ using (var stream = new FileStream(outputPath, FileMode.Create, FileAccess.Write, FileShare.None))
107
+ {
108
+ const int chunkSize = 64 * 1024;
109
+ for (var offset = 0; offset < bytes.Length; offset += chunkSize)
110
+ {
111
+ budget.Checkpoint();
112
+ var count = Math.Min(chunkSize, bytes.Length - offset);
113
+ stream.Write(bytes, offset, count);
114
+ }
115
+ stream.Flush(true);
116
+ }
117
+ var proof = VerifyScreenshotArtifact(outputPath, Path.GetExtension(outputPath).TrimStart('.'), budget);
118
+ if (proof.SizeBytes != bytes.LongLength)
119
+ throw new UnityCliBridge.BridgeCommandException("E_SCREENSHOT_ARTIFACT_INVALID", "Persisted screenshot size differs from the encoded artifact.", "Inspect output storage before retrying.");
120
+ return proof;
121
+ }
122
+ catch (UnityCliBridge.BridgeCommandException) { DeleteArtifactNoThrow(outputPath); throw; }
123
+ catch (UnityCliRequestBudgetExceededException) { DeleteArtifactNoThrow(outputPath); throw; }
124
+ catch (OperationCanceledException) { DeleteArtifactNoThrow(outputPath); throw; }
125
+ catch (Exception exception)
82
126
  {
83
- throw new UnityCliBridge.BridgeCommandException("E_SCREENSHOT_FORMAT_UNSUPPORTED", "Screenshot format must be png or jpg.");
127
+ DeleteArtifactNoThrow(outputPath);
128
+ throw new UnityCliBridge.BridgeCommandException("E_IO_ERROR", "Failed to write screenshot output: " + exception.Message);
84
129
  }
85
- return fmt;
86
130
  }
87
131
 
88
- /// <summary>渲染指定 Camera Texture2D(不写文件),供 orbit/surround 复用。</summary>
89
- public static Texture2D RenderScreenshotTexture(Camera camera, int width, int height)
132
+ /// <summary>fresh proof 使用 bounded stream header/hash,并在每个固定块检查同一 deadline/cancel。</summary>
133
+ public static ScreenshotArtifactProof VerifyScreenshotArtifact(string outputPath, string format, ScreenshotCaptureBudget budget)
90
134
  {
135
+ if (!File.Exists(outputPath)) throw new UnityCliBridge.BridgeCommandException("E_SCREENSHOT_ARTIFACT_MISSING", "Screenshot artifact was not written: " + outputPath, "Check output permissions before retrying.");
136
+ var info = new FileInfo(outputPath);
137
+ budget.EnsureArtifactSize(info.Length);
138
+ var normalized = NormalizeFormat(format, outputPath);
139
+ var header = new byte[8];
140
+ string hash;
141
+ using (var stream = new FileStream(outputPath, FileMode.Open, FileAccess.Read, FileShare.Read))
142
+ {
143
+ budget.Checkpoint();
144
+ var read = stream.Read(header, 0, header.Length);
145
+ var valid = normalized == "png"
146
+ ? read == 8 && header[0] == 0x89 && header[1] == 0x50 && header[2] == 0x4e && header[3] == 0x47 && header[4] == 0x0d && header[5] == 0x0a && header[6] == 0x1a && header[7] == 0x0a
147
+ : read >= 3 && header[0] == 0xff && header[1] == 0xd8 && header[2] == 0xff;
148
+ if (!valid) throw new UnityCliBridge.BridgeCommandException("E_SCREENSHOT_ARTIFACT_INVALID", "Screenshot artifact does not match its requested format: " + outputPath, "Use a .png or .jpg output path and retry.");
149
+ stream.Position = 0L;
150
+ using (var sha = SHA256.Create())
151
+ {
152
+ var buffer = new byte[64 * 1024];
153
+ int count;
154
+ while ((count = stream.Read(buffer, 0, buffer.Length)) > 0)
155
+ {
156
+ budget.Checkpoint();
157
+ sha.TransformBlock(buffer, 0, count, buffer, 0);
158
+ }
159
+ sha.TransformFinalBlock(Array.Empty<byte>(), 0, 0);
160
+ hash = string.Concat(Array.ConvertAll(sha.Hash, value => value.ToString("x2", CultureInfo.InvariantCulture)));
161
+ }
162
+ }
163
+ budget.Checkpoint();
164
+ return new ScreenshotArtifactProof { Path = outputPath, SizeBytes = info.Length, Sha256 = hash, Format = normalized };
165
+ }
166
+
167
+ public static string NormalizeFormat(string rawFormat, string outputPath)
168
+ {
169
+ var format = string.IsNullOrEmpty(rawFormat) ? Path.GetExtension(outputPath).TrimStart('.').ToLowerInvariant() : rawFormat.ToLowerInvariant();
170
+ if (string.IsNullOrEmpty(format)) format = "png";
171
+ if (format == "jpeg") format = "jpg";
172
+ if (format != "png" && format != "jpg") throw new UnityCliBridge.BridgeCommandException("E_SCREENSHOT_FORMAT_UNSUPPORTED", "Screenshot format must be png or jpg.");
173
+ return format;
174
+ }
175
+
176
+ public static Texture2D RenderScreenshotTexture(Camera camera, int width, int height, ScreenshotCaptureBudget budget)
177
+ {
178
+ budget.BeginCapture(width, height);
91
179
  var previousTarget = camera.targetTexture;
92
180
  var previousActive = RenderTexture.active;
93
- RenderTexture rt = null;
94
- Texture2D texture = null;
181
+ RenderTexture renderTexture = null;
95
182
  try
96
183
  {
97
- rt = new RenderTexture(width, height, 24, RenderTextureFormat.ARGB32);
98
- texture = new Texture2D(width, height, TextureFormat.RGB24, false);
99
- camera.targetTexture = rt;
184
+ renderTexture = new RenderTexture(width, height, 24, RenderTextureFormat.ARGB32);
185
+ camera.targetTexture = renderTexture;
100
186
  camera.Render();
101
- RenderTexture.active = rt;
102
- texture.ReadPixels(new Rect(0, 0, width, height), 0, 0);
103
- texture.Apply();
104
- return texture;
187
+ RenderTexture.active = renderTexture;
188
+ return CreateTextureWithOwnership(
189
+ () => new Texture2D(width, height, TextureFormat.RGB24, false),
190
+ texture =>
191
+ {
192
+ texture.ReadPixels(new Rect(0, 0, width, height), 0, 0);
193
+ texture.Apply();
194
+ },
195
+ texture => UnityEngine.Object.DestroyImmediate(texture));
105
196
  }
106
197
  finally
107
198
  {
108
199
  RenderTexture.active = previousActive;
109
200
  camera.targetTexture = previousTarget;
110
- if (rt != null) { rt.Release(); UnityEngine.Object.DestroyImmediate(rt); }
201
+ if (renderTexture != null) { renderTexture.Release(); UnityEngine.Object.DestroyImmediate(renderTexture); }
111
202
  }
112
203
  }
113
204
 
114
- /// <summary>计算 GameObject 的渲染 Bounds(含所有子 Renderer)。</summary>
115
- public static Bounds TargetRenderBounds(GameObject go)
205
+ public static Texture2D CreateContactSheet(int tileWidth, int tileHeight, ScreenshotCaptureBudget budget)
206
+ {
207
+ budget.BeginCapture(tileWidth * 3, tileHeight * 2);
208
+ var sheet = new Texture2D(tileWidth * 3, tileHeight * 2, TextureFormat.RGB24, false);
209
+ return sheet;
210
+ }
211
+
212
+ /// <summary>只有完整populate成功才转移texture ownership;任何异常都同步销毁新资源。</summary>
213
+ internal static Texture2D CreateTextureWithOwnership(
214
+ Func<Texture2D> create,
215
+ Action<Texture2D> populate,
216
+ Action<Texture2D> destroy)
116
217
  {
117
- var renderers = go.GetComponentsInChildren<Renderer>();
118
- if (renderers.Length == 0) return new Bounds(go.transform.position, Vector3.one);
119
- var bounds = renderers[0].bounds;
120
- for (var i = 1; i < renderers.Length; i++)
218
+ if (create == null) throw new ArgumentNullException("create");
219
+ if (populate == null) throw new ArgumentNullException("populate");
220
+ if (destroy == null) throw new ArgumentNullException("destroy");
221
+ var texture = create();
222
+ var ownershipTransferred = false;
223
+ try
224
+ {
225
+ populate(texture);
226
+ ownershipTransferred = true;
227
+ return texture;
228
+ }
229
+ finally
121
230
  {
122
- if (renderers[i] != null && renderers[i].gameObject.activeInHierarchy) bounds.Encapsulate(renderers[i].bounds);
231
+ if (!ownershipTransferred && texture != null) destroy(texture);
123
232
  }
124
- return bounds;
125
233
  }
126
234
 
127
- /// <summary>组合 6 张截图到 3x2 contact sheet。</summary>
128
- public static Texture2D ComposeScreenshotContactSheet(List<Texture2D> tiles, int tileWidth, int tileHeight)
235
+ /// <summary>逐 tile 写入 sheet caller 立即释放 tile,避免同时持有全部捕获纹理。</summary>
236
+ public static void CopyContactSheetTile(Texture2D sheet, Texture2D tile, int index, int tileWidth, int tileHeight)
237
+ {
238
+ var column = index % 3;
239
+ var row = 1 - index / 3;
240
+ var pixels = tile.GetPixels32();
241
+ sheet.SetPixels32(column * tileWidth, row * tileHeight, tileWidth, tileHeight, pixels);
242
+ }
243
+
244
+ /// <summary>逐节点和逐child读取渲染边界,组件数组仅在已验证cardinality与剩余预算后创建。</summary>
245
+ public static Bounds TargetRenderBounds(GameObject gameObject, UnityCliRequestBudget budget)
129
246
  {
130
- var columns = 3;
131
- var rows = 2;
132
- var sheet = new Texture2D(tileWidth * columns, tileHeight * rows, TextureFormat.RGB24, false);
133
- var fill = Enumerable.Repeat(new Color(0.05f, 0.05f, 0.05f, 1f), sheet.width * sheet.height).ToArray();
134
- sheet.SetPixels(fill);
135
- for (var i = 0; i < tiles.Count; i++)
247
+ if (!budget.TryConsumeGetters(2)) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
248
+ var root = gameObject.transform;
249
+ var stack = new Stack<TransformTraversalFrame>();
250
+ stack.Push(new TransformTraversalFrame(root));
251
+ var found = false;
252
+ var bounds = new Bounds(root.position, Vector3.one);
253
+ while (stack.Count > 0)
136
254
  {
137
- var column = i % columns;
138
- var row = rows - 1 - (i / columns);
139
- sheet.SetPixels(column * tileWidth, row * tileHeight, tileWidth, tileHeight, tiles[i].GetPixels());
255
+ var frame = stack.Peek();
256
+ if (!frame.Entered)
257
+ {
258
+ if (!budget.TryCheckpoint() || !budget.TryConsumeScannedRecord() || !budget.TryConsumeNode() || !budget.TryConsumeGetters(2))
259
+ throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
260
+ frame.Entered = true;
261
+ frame.ChildCount = frame.Transform.childCount;
262
+ var components = UnityCliBridge.ObjectRefResolver.BoundedComponents(frame.Transform.gameObject, budget);
263
+ foreach (var component in components)
264
+ {
265
+ var renderer = component as Renderer;
266
+ if (renderer == null) continue;
267
+ if (!budget.TryConsumeGetters(2)) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
268
+ if (!renderer.gameObject.activeInHierarchy) continue;
269
+ var rendererBounds = renderer.bounds;
270
+ if (!found) { bounds = rendererBounds; found = true; }
271
+ else bounds.Encapsulate(rendererBounds);
272
+ }
273
+ }
274
+ if (frame.NextChildIndex >= frame.ChildCount)
275
+ {
276
+ stack.Pop();
277
+ continue;
278
+ }
279
+ if (!budget.TryCheckpoint() || !budget.TryConsumeGetter()) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
280
+ stack.Push(new TransformTraversalFrame(frame.Transform.GetChild(frame.NextChildIndex++)));
140
281
  }
141
- sheet.Apply();
142
- return sheet;
282
+ return bounds;
143
283
  }
144
284
 
145
- // 内联 base64 上限
146
- private const int InlineImageMaxBytes = 512 * 1024;
147
-
148
- /// <summary>在图片字节小于上限时附带 inline base64 数据。</summary>
149
- public static void MaybeAppendInline(ScreenshotCaptureResult result, byte[] bytes, int width, int height, string mimeType, bool inlineBase64)
285
+ private sealed class TransformTraversalFrame
150
286
  {
151
- if (inlineBase64 && bytes != null && bytes.Length <= InlineImageMaxBytes)
287
+ internal readonly Transform Transform;
288
+ internal bool Entered;
289
+ internal int ChildCount;
290
+ internal int NextChildIndex;
291
+
292
+ internal TransformTraversalFrame(Transform transform)
152
293
  {
153
- result.ImageBase64 = Convert.ToBase64String(bytes);
154
- result.ImageWidth = width;
155
- result.ImageHeight = height;
156
- result.ImageMimeType = mimeType;
294
+ Transform = transform;
157
295
  }
158
296
  }
297
+
298
+ public static void MaybeAppendInline(ScreenshotCaptureResult result, byte[] bytes, int width, int height, string mimeType, bool inlineBase64, ScreenshotCaptureBudget budget)
299
+ {
300
+ if (!inlineBase64 || bytes == null || bytes.Length > InlineImageMaxBytes) return;
301
+ string encoded;
302
+ if (!budget.TryCreateInlineBase64(bytes, out encoded)) return;
303
+ result.ImageBase64 = encoded;
304
+ result.ImageWidth = width;
305
+ result.ImageHeight = height;
306
+ result.ImageMimeType = mimeType;
307
+ }
308
+
309
+ private static void DeleteArtifactNoThrow(string outputPath)
310
+ {
311
+ try { if (File.Exists(outputPath)) File.Delete(outputPath); } catch { }
312
+ }
313
+
314
+ private static void ValidateLimit(long value, long minimum, long maximum, string name)
315
+ {
316
+ if (value < minimum || value > maximum) throw Limit(name + " must be between " + minimum.ToString(CultureInfo.InvariantCulture) + " and " + maximum.ToString(CultureInfo.InvariantCulture) + ".");
317
+ }
318
+
319
+ private static UnityCliBridge.BridgeCommandException Limit(string message)
320
+ {
321
+ return new UnityCliBridge.BridgeCommandException("E_SCREENSHOT_LIMIT_INVALID", message, "Reduce dimensions/capture count or pass a documented positive aggregate ceiling before retrying.");
322
+ }
323
+ }
324
+
325
+ public sealed class ScreenshotCaptureBudget
326
+ {
327
+ private readonly UnityCliRequestBudget requestBudget;
328
+ private long captures;
329
+ private long capturedPixels;
330
+ private long artifactBytes;
331
+
332
+ internal ScreenshotCaptureBudget(ScreenshotCapturePlan plan, UnityCliRequestBudget requestBudget)
333
+ {
334
+ Plan = plan ?? throw new ArgumentNullException("plan");
335
+ this.requestBudget = requestBudget ?? throw new ArgumentNullException("requestBudget");
336
+ }
337
+
338
+ public ScreenshotCapturePlan Plan { get; private set; }
339
+
340
+ public void BeginCapture(int width, int height)
341
+ {
342
+ requestBudget.CheckpointOrThrow();
343
+ ScreenshotToolSupport.ValidateDimensions(width, height, "capture");
344
+ var pixels = checked((long)width * height);
345
+ captures++;
346
+ capturedPixels = checked(capturedPixels + pixels);
347
+ if (captures > Plan.CaptureCount || pixels > Plan.MaxPixelsPerCapture || capturedPixels > Plan.TotalPixels)
348
+ throw new UnityCliBridge.BridgeCommandException("E_SCREENSHOT_LIMIT_EXCEEDED", "Capture count or pixels exceeded the frozen aggregate plan.", "Submit a new request with a valid aggregate plan.");
349
+ }
350
+
351
+ public void Checkpoint()
352
+ {
353
+ requestBudget.CheckpointOrThrow();
354
+ }
355
+
356
+ public void ChargeArtifact(long bytes)
357
+ {
358
+ requestBudget.CheckpointOrThrow();
359
+ if (bytes <= 0L || bytes > Plan.ArtifactByteCeiling - artifactBytes)
360
+ throw new UnityCliBridge.BridgeCommandException("E_SCREENSHOT_LIMIT_EXCEEDED", "Encoded artifact bytes exceed the frozen request ceiling.", "Reduce dimensions or capture count.");
361
+ artifactBytes += bytes;
362
+ }
363
+
364
+ public void EnsureArtifactSize(long bytes)
365
+ {
366
+ requestBudget.CheckpointOrThrow();
367
+ if (bytes <= 0L || bytes > Plan.ArtifactByteCeiling)
368
+ throw new UnityCliBridge.BridgeCommandException("E_SCREENSHOT_LIMIT_EXCEEDED", "Persisted artifact size exceeds the frozen request ceiling.", "Reduce dimensions or capture count.");
369
+ }
370
+
371
+ public bool TryCreateInlineBase64(byte[] bytes, out string value)
372
+ {
373
+ value = null;
374
+ if (bytes == null) return false;
375
+ var encodedBytes = checked(((bytes.LongLength + 2L) / 3L) * 4L);
376
+ if (!requestBudget.TryReserveSerializedUtf8Bytes(encodedBytes)) return false;
377
+ value = Convert.ToBase64String(bytes);
378
+ return true;
379
+ }
159
380
  }
160
381
 
161
- /// <summary>screenshot 域截图捕获结果 DTO。</summary>
162
382
  public class ScreenshotCaptureResult
163
383
  {
164
384
  public string ScreenshotPath { get; set; }
@@ -170,9 +390,9 @@ namespace UnityCli
170
390
  public string ImageMimeType { get; set; }
171
391
  public long SizeBytes { get; set; }
172
392
  public string Sha256 { get; set; }
393
+ public ScreenshotCapturePlan CapturePlan { get; set; }
173
394
  }
174
395
 
175
- /// <summary>screenshot 域 artifact 条目 DTO(orbit/surround 多图输出)。</summary>
176
396
  public sealed class ScreenshotArtifactEntry
177
397
  {
178
398
  public string Source { get; set; }
@@ -10,7 +10,7 @@ namespace UnityCli
10
10
  {
11
11
  Name = "screenshot.editor-element",
12
12
  Summary = "Capture one bounded Unity Editor UI Toolkit element as a verified PNG artifact.",
13
- Description = "Unity 6000.7+ only. Selects an attached EditorWindow by exact type name or title, then resolves a UI Toolkit selector with up to 16 type/#id/.class descendant or child segments. The scan is capped at 10000 elements. Capture dimensions default to 2048x2048 maximum and cannot exceed 4096x4096; PNG bytes default to 8 MiB maximum and cannot exceed 32 MiB. The artifact is written only to the request artifact directory; inline or arbitrary output paths are not accepted.",
13
+ Description = "Unity 6000.7+ only. Selects an attached EditorWindow by exact type/title and resolves up to 16 type/#id/.class segments without materializing descendant lists. Window/element scans share the request scan/node/reflection/time/UTF-8 budget and remain capped at 10000 elements. maxWidth/maxHeight default 2048 and are 1..4096; maxBytes defaults 8 MiB and is 1024..32 MiB. The complete dimension/pixel/temp/artifact plan is frozen before capture. The artifact is written only to the request artifact directory; Editor-only.",
14
14
  Group = "screenshot",
15
15
  DestructiveHint = false,
16
16
  SupportsDryRun = true,
@@ -31,7 +31,7 @@ namespace UnityCli
31
31
  public System.Threading.Tasks.ValueTask<EditorElementCaptureResult> ExecuteAsync(EditorElementCaptureRequest request, UnityCliProjectToolContext ctx, System.Threading.CancellationToken ct = default)
32
32
  {
33
33
  ct.ThrowIfCancellationRequested();
34
- var plan = EditorElementCaptureToolSupport.Prepare(request);
34
+ var plan = EditorElementCaptureToolSupport.Prepare(request, ctx.UseReadBudget(request.Budget));
35
35
  if (ctx.DryRun) return new System.Threading.Tasks.ValueTask<EditorElementCaptureResult>(EditorElementCaptureToolSupport.Preview(plan));
36
36
  ctx.CommitEffect();
37
37
  return new System.Threading.Tasks.ValueTask<EditorElementCaptureResult>(EditorElementCaptureToolSupport.Capture(plan, ctx));
@@ -44,9 +44,10 @@ namespace UnityCli
44
44
  public string Window { get; set; }
45
45
  [UnityCliRequired]
46
46
  public string Selector { get; set; }
47
- public long? MaxWidth { get; set; }
48
- public long? MaxHeight { get; set; }
49
- public long? MaxBytes { get; set; }
47
+ [UnityCliRange(1L, 4096L)] public long? MaxWidth { get; set; }
48
+ [UnityCliRange(1L, 4096L)] public long? MaxHeight { get; set; }
49
+ [UnityCliRange(1024L, 33554432L)] public long? MaxBytes { get; set; }
50
+ public UnityCliReadBudgetInput Budget { get; set; }
50
51
  }
51
52
 
52
53
  public sealed class EditorElementWindowIdentity
@@ -71,6 +72,7 @@ namespace UnityCli
71
72
  public long Width { get; set; }
72
73
  public long Height { get; set; }
73
74
  public long MaxBytes { get; set; }
75
+ public ScreenshotCapturePlan CapturePlan { get; set; }
74
76
  }
75
77
 
76
78
  public sealed class EditorElementCaptureResult