cozyclay 2.1.1 → 3.0.1

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 (585) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/README.md +12 -30
  3. package/bin/agent/agent-routes.mjs +43 -116
  4. package/bin/agent/agent-runner.mjs +14 -15
  5. package/bin/agent/cliproxy-provider.mjs +135 -0
  6. package/bin/agent/credential-store.mjs +30 -7
  7. package/bin/agent/pi-tools.mjs +4 -1
  8. package/bin/agent/provider-keys.mjs +32 -0
  9. package/bin/agent/provider-login.mjs +110 -0
  10. package/bin/agent/provider-routes.mjs +99 -0
  11. package/bin/agent/providers.mjs +19 -97
  12. package/bin/agent/script-tool.mjs +17 -4
  13. package/bin/agent/session-store.mjs +2 -2
  14. package/bin/agent/studio-context-budget.mjs +93 -0
  15. package/bin/agent/studio-motion-reference.mjs +23 -0
  16. package/bin/agent/studio-prompt.mjs +1 -1
  17. package/bin/agent/studio-rebase-text.mjs +32 -0
  18. package/bin/agent/studio-rebase.mjs +26 -39
  19. package/bin/agent/studio-tools.mjs +9 -28
  20. package/bin/agent/tool-arguments.mjs +49 -0
  21. package/bin/agent/turn-frames.mjs +66 -0
  22. package/bin/codex-auth.mjs +9 -2
  23. package/bin/cozyclay.mjs +10 -116
  24. package/bin/live/cli.mjs +3 -3
  25. package/bin/mcp-runtime.mjs +2 -12
  26. package/bin/package-signature.mjs +0 -4
  27. package/dist/app/index.html +7 -6
  28. package/dist/assets/EXRLoader-CvVIvGVE.js +1 -0
  29. package/dist/assets/analytics-BWCqdgWA.js +1 -0
  30. package/dist/assets/app-DfTCxaKk.js +5087 -0
  31. package/dist/assets/app-f-fkV5uP.css +1 -0
  32. package/dist/assets/compile-worker-BxKXh07s.js +5 -0
  33. package/dist/assets/fflate.module-KbGj5-H6.js +5 -0
  34. package/dist/assets/{first-shot-handoff-msLI71g1.js → first-shot-handoff-DNO8kv_N.js} +1 -1
  35. package/dist/assets/{landing-ChUvB_zL.js → landing-yfZaJkWc.js} +1 -1
  36. package/dist/assets/scene-asset-sync-U3kS2rvK.js +20 -0
  37. package/dist/assets/scene-asset-sync-_S8NQl5B.css +1 -0
  38. package/dist/assets/workflow-D78eiKTa.js +179 -0
  39. package/dist/cozyclay-package.json +1 -1
  40. package/dist/index.html +3 -3
  41. package/dist/sw.js +3 -6
  42. package/dist/workflow/index.html +5 -4
  43. package/mcp/LIVE-PROTOCOL.md +7 -47
  44. package/mcp/README.md +16 -26
  45. package/mcp/live-hub.mjs +6 -10
  46. package/mcp/package.json +1 -3
  47. package/mcp/tool-handlers.mjs +4 -168
  48. package/package.json +3 -22
  49. package/src/App.jsx +151 -2625
  50. package/src/agent-progress.js +80 -0
  51. package/src/agent-turn-mark.js +30 -0
  52. package/src/analytics.js +1 -19
  53. package/src/app-context.js +12 -2
  54. package/src/app-stage.jsx +2 -34
  55. package/src/asset-pane.jsx +6 -5
  56. package/src/camera-block.js +21 -0
  57. package/src/camera-direction.js +120 -0
  58. package/src/character-kind.js +3 -9
  59. package/src/command-bus.js +10 -8
  60. package/src/commands/cast.js +5 -34
  61. package/src/commands/elements/character.js +1 -1
  62. package/src/commands/elements/motion.js +1 -6
  63. package/src/commands/elements/object.js +6 -2
  64. package/src/commands/elements/shot.js +4 -1
  65. package/src/commands/elements.js +38 -14
  66. package/src/commands/index.js +3 -1
  67. package/src/commands/kits.js +37 -0
  68. package/src/commands/motion-timing.js +57 -0
  69. package/src/commands/motion.js +9 -152
  70. package/src/commands/motion3-compile.js +65 -0
  71. package/src/commands/motion3.js +214 -0
  72. package/src/commands/objects.js +6 -4
  73. package/src/commands/shot.js +55 -13
  74. package/src/commands/stage.js +14 -1
  75. package/src/commands/view.js +4 -1
  76. package/src/document-mirrors.js +42 -0
  77. package/src/document-store.js +4 -0
  78. package/src/domains/cast.js +19 -194
  79. package/src/domains/motion/motion-range-pin-rules.js +1 -1
  80. package/src/domains/motion/take-source.js +53 -0
  81. package/src/domains/motion.js +60 -1838
  82. package/src/domains/scenes.js +12 -55
  83. package/src/domains/shots.js +2 -2
  84. package/src/domains/stage.js +4 -2
  85. package/src/draft-ghost.js +47 -0
  86. package/src/draft-ghost.jsx +129 -0
  87. package/src/dualview.jsx +4 -3
  88. package/src/effects/EffectsLayer.jsx +112 -0
  89. package/src/effects/effect-meshes.js +232 -0
  90. package/src/effects/effect-record.js +103 -0
  91. package/src/effects/emitter.js +59 -0
  92. package/src/effects/simulate.js +261 -0
  93. package/src/effects/state.js +59 -0
  94. package/src/execution-telemetry.js +2 -3
  95. package/src/first-success-guide.jsx +5 -5
  96. package/src/ghost-set.js +257 -0
  97. package/src/held-object-edit.jsx +113 -0
  98. package/src/held-object-pose.js +175 -0
  99. package/src/hierarchy-model.js +1 -1
  100. package/src/hierarchy-panel.jsx +1 -1
  101. package/src/imported-mesh.jsx +103 -0
  102. package/src/kits/held.js +84 -0
  103. package/src/kits/index.js +70 -0
  104. package/src/kits/smoking.js +57 -0
  105. package/src/light-blockers.js +93 -0
  106. package/src/motion/beat-board-model.js +84 -0
  107. package/src/motion/beat-board.css +124 -0
  108. package/src/motion/beat-board.jsx +60 -0
  109. package/src/motion/center-of-mass.js +66 -0
  110. package/src/motion/fix-collisions.js +850 -0
  111. package/src/motion/physics-review.js +118 -0
  112. package/src/{ardy → motion}/physics-support.js +1 -76
  113. package/src/motion/rig-snapshot.js +22 -0
  114. package/src/motion/take-segments.jsx +140 -0
  115. package/src/{ardy → motion}/timeline-coordinates.js +0 -21
  116. package/src/{ardy → motion}/timeline.css +1 -2
  117. package/src/{ardy → motion}/timeline.jsx +42 -413
  118. package/src/motion/waypoints.js +76 -0
  119. package/src/motion3/beats.js +83 -0
  120. package/src/motion3/body.js +158 -0
  121. package/src/motion3/bones.js +47 -0
  122. package/src/motion3/carried.js +47 -0
  123. package/src/motion3/compile-job.js +79 -0
  124. package/src/motion3/compile-worker.js +10 -0
  125. package/src/motion3/compiler/anchors.js +113 -0
  126. package/src/motion3/compiler/arm-orient.js +128 -0
  127. package/src/motion3/compiler/channels.js +125 -0
  128. package/src/motion3/compiler/compile.js +205 -0
  129. package/src/motion3/compiler/gait-rhythm.js +97 -0
  130. package/src/motion3/compiler/grip.js +171 -0
  131. package/src/motion3/compiler/head-steady.js +48 -0
  132. package/src/motion3/compiler/hold.js +134 -0
  133. package/src/motion3/compiler/idle.js +113 -0
  134. package/src/motion3/compiler/limbs.js +197 -0
  135. package/src/motion3/compiler/math.js +72 -0
  136. package/src/motion3/compiler/pose.js +177 -0
  137. package/src/motion3/compiler/quat.js +49 -0
  138. package/src/motion3/compiler/skeleton.js +74 -0
  139. package/src/motion3/compiler/validate-common.js +32 -0
  140. package/src/motion3/compiler/validate-goals.js +118 -0
  141. package/src/motion3/compiler/validate-objects.js +144 -0
  142. package/src/motion3/compiler/validate-parts.js +118 -0
  143. package/src/motion3/compiler/validate.js +122 -0
  144. package/src/motion3/compiler/vocab.js +74 -0
  145. package/src/motion3/compiler/walk-pelvis.js +275 -0
  146. package/src/motion3/compiler/walk.js +207 -0
  147. package/src/motion3/compiler/wrist-clamp.js +32 -0
  148. package/src/motion3/delta.js +47 -0
  149. package/src/motion3/describe.js +177 -0
  150. package/src/motion3/docs.js +27 -0
  151. package/src/motion3/edit-carry.js +21 -0
  152. package/src/motion3/fix/ease.js +16 -0
  153. package/src/motion3/fix/ground.js +37 -0
  154. package/src/motion3/fix/index.js +52 -0
  155. package/src/motion3/fix/push.js +71 -0
  156. package/src/motion3/fix/reach.js +46 -0
  157. package/src/motion3/fix/retime.js +79 -0
  158. package/src/motion3/fix/self.js +211 -0
  159. package/src/motion3/fix/slide.js +70 -0
  160. package/src/motion3/fix/turn.js +51 -0
  161. package/src/motion3/follow.js +39 -0
  162. package/src/motion3/free-motion.js +204 -0
  163. package/src/motion3/grip-choice.js +130 -0
  164. package/src/motion3/hold-plan.js +157 -0
  165. package/src/motion3/json-patch.js +77 -0
  166. package/src/motion3/key-aim.js +105 -0
  167. package/src/motion3/key-edit.js +82 -0
  168. package/src/motion3/lenient.js +127 -0
  169. package/src/motion3/object-keys.js +236 -0
  170. package/src/motion3/object-pose.js +182 -0
  171. package/src/motion3/object-tracks.js +12 -0
  172. package/src/motion3/people.js +82 -0
  173. package/src/motion3/pipeline.js +102 -0
  174. package/src/motion3/playback.js +51 -0
  175. package/src/motion3/probes.js +91 -0
  176. package/src/motion3/qa/body-checks.js +34 -0
  177. package/src/motion3/qa/check-names.js +14 -0
  178. package/src/motion3/qa/checks.js +125 -0
  179. package/src/motion3/qa/contact-checks.js +51 -0
  180. package/src/motion3/qa/explain.js +80 -0
  181. package/src/motion3/qa/fmt.js +5 -0
  182. package/src/motion3/qa/foot-checks.js +72 -0
  183. package/src/motion3/qa/geometry.js +77 -0
  184. package/src/motion3/qa/goals.js +191 -0
  185. package/src/motion3/qa/ground.js +60 -0
  186. package/src/motion3/qa/hints-contact.js +195 -0
  187. package/src/motion3/qa/hints-goal.js +160 -0
  188. package/src/motion3/qa/hints-knee.js +24 -0
  189. package/src/motion3/qa/hints-motion.js +209 -0
  190. package/src/motion3/qa/hints-object.js +91 -0
  191. package/src/motion3/qa/index.js +44 -0
  192. package/src/motion3/qa/key-checks.js +82 -0
  193. package/src/motion3/qa/near.js +96 -0
  194. package/src/motion3/qa/object-checks.js +135 -0
  195. package/src/motion3/qa/patch.js +161 -0
  196. package/src/motion3/qa/people-checks.js +37 -0
  197. package/src/motion3/qa/severity.js +38 -0
  198. package/src/motion3/qa/spec-checks.js +65 -0
  199. package/src/motion3/qa/style.js +228 -0
  200. package/src/motion3/qa/wrist-checks.js +69 -0
  201. package/src/motion3/qa/wrist-obs.js +60 -0
  202. package/src/motion3/refs.js +129 -0
  203. package/src/motion3/repair-tune.js +121 -0
  204. package/src/motion3/repair.js +83 -0
  205. package/src/motion3/report.js +67 -0
  206. package/src/motion3/rigs/index.js +14 -0
  207. package/src/motion3/rigs/x-bot-tpose-body.json +1 -0
  208. package/src/motion3/rigs/x-bot-tpose.json +1 -0
  209. package/src/motion3/rigs/y-bot-tpose-body.json +1 -0
  210. package/src/motion3/rigs/y-bot-tpose.json +1 -0
  211. package/src/motion3/scene.js +60 -0
  212. package/src/motion3/schema/brief.js +89 -0
  213. package/src/motion3/schema/fields.js +44 -0
  214. package/src/motion3/schema/spec-examples.js +46 -0
  215. package/src/motion3/schema/topics.js +113 -0
  216. package/src/motion3/schema/types.js +149 -0
  217. package/src/motion3/search.js +117 -0
  218. package/src/motion3/second-hand.js +112 -0
  219. package/src/motion3/settle.js +136 -0
  220. package/src/motion3/skills/basic.js +83 -0
  221. package/src/motion3/skills/book.js +76 -0
  222. package/src/motion3/skills/car.js +145 -0
  223. package/src/motion3/skills/flip.js +200 -0
  224. package/src/motion3/skills/frames-auto.js +29 -0
  225. package/src/motion3/skills/impact.js +109 -0
  226. package/src/motion3/skills/index.js +85 -0
  227. package/src/motion3/skills/info.js +80 -0
  228. package/src/motion3/skills/smoke-kit.js +109 -0
  229. package/src/motion3/skills/smoke.js +160 -0
  230. package/src/motion3/spec-store.js +59 -0
  231. package/src/motion3/take-edit.js +155 -0
  232. package/src/motion3/take.js +63 -0
  233. package/src/motion3/timeline.js +127 -0
  234. package/src/motion3/views.js +121 -0
  235. package/src/motion3/walk-plan.js +119 -0
  236. package/src/object-catalog.jsx +7 -1
  237. package/src/object-colors.js +98 -0
  238. package/src/object-gizmo-parts.js +115 -0
  239. package/src/object-gizmo.jsx +37 -122
  240. package/src/object-glow.js +139 -0
  241. package/src/object-hidden.js +33 -0
  242. package/src/object-look.js +196 -0
  243. package/src/object-size.js +10 -0
  244. package/src/object-travel.js +71 -6
  245. package/src/panels/CameraPanel.jsx +7 -7
  246. package/src/panels/HdrEnvironmentControls.jsx +59 -0
  247. package/src/panels/LightPanel.jsx +25 -4
  248. package/src/panels/ObjectLookPanel.jsx +88 -0
  249. package/src/panels/PosePanel.jsx +2 -17
  250. package/src/panels/RigControlPanel.jsx +5 -53
  251. package/src/panels/SubjectBox.jsx +4 -1
  252. package/src/panels/details.css +14 -0
  253. package/src/posestudio.jsx +4 -27
  254. package/src/project-browser.jsx +3 -4
  255. package/src/project-resources.js +18 -31
  256. package/src/project.js +13 -104
  257. package/src/props.jsx +25 -280
  258. package/src/qa-label.js +25 -0
  259. package/src/rail-dot-drag.js +23 -0
  260. package/src/render-passes.js +10 -0
  261. package/src/resource-status.jsx +1 -0
  262. package/src/result-modal.jsx +4 -19
  263. package/src/room.jsx +20 -12
  264. package/src/scene-assets.js +8 -6
  265. package/src/scene-light-place.js +72 -0
  266. package/src/scene-lights.jsx +148 -0
  267. package/src/scene-objects.js +31 -139
  268. package/src/scenes.js +15 -105
  269. package/src/semantic-edit.js +2 -5
  270. package/src/set-pieces.jsx +183 -0
  271. package/src/settings-menu.jsx +3 -11
  272. package/src/shell/BottomDock.jsx +9 -51
  273. package/src/shell/DetailsSlot.jsx +16 -200
  274. package/src/shell/GhostsMenuGroup.jsx +74 -0
  275. package/src/shell/LibrarySlot.jsx +1 -3
  276. package/src/shell/MenuBar.jsx +3 -3
  277. package/src/shell/PreferencesDialog.jsx +15 -264
  278. package/src/shell/PreferencesSlot.jsx +2 -8
  279. package/src/shell/TopBar.jsx +5 -84
  280. package/src/shell/ViewportToolbar.jsx +10 -70
  281. package/src/shell/agent-glass.css +19 -6
  282. package/src/shell/details-selection.js +42 -0
  283. package/src/shell/ghosts-menu.css +22 -0
  284. package/src/shell/glass-regions.css +29 -3
  285. package/src/shell/preferences.css +6 -1
  286. package/src/shot-authoring.js +4 -7
  287. package/src/soft-shadows.js +37 -0
  288. package/src/stage-attach-frames.js +12 -12
  289. package/src/stage-canvas-rigs.jsx +3 -3
  290. package/src/stage-capture.jsx +42 -8
  291. package/src/stage-characters.jsx +22 -216
  292. package/src/stage-editor-gizmos.jsx +6 -6
  293. package/src/stage-environment.js +93 -0
  294. package/src/stage-hdr-builtins.js +107 -0
  295. package/src/stage-hdr-image.js +116 -0
  296. package/src/stage-hdr-library.js +70 -0
  297. package/src/stage-hdr-map.js +94 -0
  298. package/src/stage-hdr.js +130 -0
  299. package/src/stage-lighting.js +149 -0
  300. package/src/stage-occlusion.js +210 -0
  301. package/src/stage-sky.jsx +159 -0
  302. package/src/stage-startup-defaults.js +5 -9
  303. package/src/studio-actions.js +16 -14
  304. package/src/studio-agent-commands.js +19 -16
  305. package/src/studio-agent-motion.js +9 -9
  306. package/src/studio-agent-protocol.js +47 -77
  307. package/src/studio-app-binding.js +27 -47
  308. package/src/studio-asset-list.js +17 -0
  309. package/src/studio-contact-sheet.js +37 -5
  310. package/src/studio-elements.js +27 -7
  311. package/src/studio-entity-bounds.js +18 -0
  312. package/src/studio-error-codes.js +15 -0
  313. package/src/studio-export-framing.js +152 -0
  314. package/src/studio-lighting-check.js +103 -0
  315. package/src/studio-motion-visual.js +150 -0
  316. package/src/studio-schema-errors.js +80 -0
  317. package/src/studio-support-gap.js +74 -0
  318. package/src/styles.css +1 -19
  319. package/src/timeline-defaults.js +5 -0
  320. package/src/timeline-extent.js +8 -20
  321. package/src/tutorial-walk.js +17 -0
  322. package/src/ui-scrub.js +1 -1
  323. package/src/use-shot-camera-tracks.js +12 -0
  324. package/src/view-ghosts.js +97 -0
  325. package/src/view-ghosts.jsx +49 -0
  326. package/src/workflow/AgentPanel.jsx +47 -166
  327. package/src/workflow/AgentProgressCard.jsx +109 -0
  328. package/src/workflow/AgentReplyText.jsx +37 -0
  329. package/src/workflow/AgentTurnSteps.jsx +84 -0
  330. package/src/workflow/LoginPanel.jsx +140 -0
  331. package/src/workflow/WorkflowBuilder.jsx +7 -23
  332. package/src/workflow/agent-client.js +54 -51
  333. package/src/workflow/agent-panel.css +71 -1
  334. package/src/workflow/agent-progress-card.css +167 -0
  335. package/src/workflow/agent-progress-view.js +47 -0
  336. package/src/workflow/agent-turn-steps.css +113 -0
  337. package/src/workflow/agent-turn.js +191 -0
  338. package/src/workflow/login-client.js +34 -0
  339. package/src/workflow/motion-input.js +18 -31
  340. package/src/workflow/node-schema.js +1 -1
  341. package/src/workflow/reply-markdown.js +69 -0
  342. package/src/workflow/scene-asset-sync.js +8 -8
  343. package/tools/agent-session-stats.mjs +61 -0
  344. package/tools/dev-full.mjs +5 -56
  345. package/tools/eslint-baseline.json +12 -13
  346. package/tools/knip-baseline.json +0 -179
  347. package/tools/loc-baseline.json +31 -81
  348. package/tools/motion3/extract-body.mjs +103 -0
  349. package/tools/motion3/extract-rig.mjs +85 -0
  350. package/tools/process-supervisor.mjs +0 -88
  351. package/tools/run-tests.mjs +9 -125
  352. package/bin/agent/motion-runtime.mjs +0 -76
  353. package/dist/ardy/cskel27-rest.json +0 -557
  354. package/dist/assets/analytics-C9pMpW7s.js +0 -1
  355. package/dist/assets/app-B_2XB9rw.css +0 -1
  356. package/dist/assets/app-DAuvKPP8.js +0 -4920
  357. package/dist/assets/config-Bx7pYsD9.js +0 -1
  358. package/dist/assets/config-DeoFTYzb.css +0 -1
  359. package/dist/assets/demo-CvKuBgy5.js +0 -1
  360. package/dist/assets/scene-asset-sync-DKkpjKBV.js +0 -19
  361. package/dist/assets/scene-asset-sync-DjJ_cJ2R.css +0 -1
  362. package/dist/assets/ticket-BPk2Ni95.js +0 -1
  363. package/dist/assets/workflow-jJRBR94Z.js +0 -179
  364. package/dist/demo/walk-then-stop.npz +0 -0
  365. package/mcp/ardy-prompts.mjs +0 -218
  366. package/src/ardy/auto-fix-panel.css +0 -108
  367. package/src/ardy/auto-physics.js +0 -1194
  368. package/src/ardy/client.js +0 -153
  369. package/src/ardy/convert.js +0 -313
  370. package/src/ardy/cskel27-neutral.js +0 -39
  371. package/src/ardy/cskel27.js +0 -77
  372. package/src/ardy/export.js +0 -165
  373. package/src/ardy/fix-collisions.js +0 -2257
  374. package/src/ardy/foot-lock.js +0 -230
  375. package/src/ardy/motion-calibration.js +0 -118
  376. package/src/ardy/motion-edit.js +0 -213
  377. package/src/ardy/motion-url.js +0 -55
  378. package/src/ardy/mp4-duration.js +0 -102
  379. package/src/ardy/npz.js +0 -595
  380. package/src/ardy/physics-panel.css +0 -21
  381. package/src/ardy/physics-panel.jsx +0 -89
  382. package/src/ardy/physics-refine.js +0 -111
  383. package/src/ardy/physics-review.js +0 -567
  384. package/src/ardy/physics-temporal.js +0 -54
  385. package/src/ardy/platform-fit-panel.css +0 -64
  386. package/src/ardy/platform-fit-panel.jsx +0 -41
  387. package/src/ardy/platform-fit.js +0 -326
  388. package/src/ardy/playback.js +0 -711
  389. package/src/ardy/pose-pin.js +0 -97
  390. package/src/ardy/prompt-clips.js +0 -30
  391. package/src/ardy/retime.js +0 -211
  392. package/src/ardy/root-drop.js +0 -369
  393. package/src/ardy/timeline-resize.js +0 -13
  394. package/src/ardy/to-cskel27.js +0 -225
  395. package/src/ardy/trim.js +0 -33
  396. package/src/ardy/waypoints.js +0 -369
  397. package/src/ardy-bridge-contract.js +0 -281
  398. package/src/domains/motion/motion-video-capture.js +0 -664
  399. package/src/i2v-motion-client.js +0 -77
  400. package/src/i2v-motion-studio.jsx +0 -180
  401. package/src/line-edit.js +0 -1348
  402. package/src/motion/generation.js +0 -105
  403. package/src/motion-readiness-ui.jsx +0 -67
  404. package/src/motion-readiness.js +0 -40
  405. package/src/motion-resources.js +0 -177
  406. package/src/motion-store.js +0 -65
  407. package/src/motion-trail.js +0 -601
  408. package/src/multimodel-ingest.js +0 -394
  409. package/src/panels/PromptBlocksPanel.jsx +0 -444
  410. package/src/panels/VideoCapturePanel.jsx +0 -181
  411. package/src/pose-extract/browser.js +0 -42
  412. package/src/pose-extract/detector.js +0 -100
  413. package/src/pose-extract/fit.js +0 -227
  414. package/src/pose-extract/image-frame.js +0 -51
  415. package/src/pose-extract/index.js +0 -58
  416. package/src/pose-extract/keyframes.js +0 -74
  417. package/src/pose-extract/landmarks.js +0 -162
  418. package/src/pose-extract/mirror.js +0 -153
  419. package/src/pose-extract/take.js +0 -131
  420. package/src/pose-extract/video-frames.js +0 -91
  421. package/src/take-recipe.js +0 -165
  422. package/src/trail-key-conflicts.js +0 -39
  423. package/src/trail-pick.js +0 -59
  424. package/src/use-i2v-capture-modal.js +0 -15
  425. package/tools/ardy/BRIDGE.md +0 -37
  426. package/tools/ardy/GVHMR-NPZ.md +0 -68
  427. package/tools/ardy/README.md +0 -124
  428. package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
  429. package/tools/ardy/artifacts.mjs +0 -34
  430. package/tools/ardy/bridge.mjs +0 -1732
  431. package/tools/ardy/bvh-cskel27.mjs +0 -1265
  432. package/tools/ardy/cclay_constrained_generate.py +0 -561
  433. package/tools/ardy/cclay_gvhmr_worker.py +0 -264
  434. package/tools/ardy/cclay_motion_edit.py +0 -510
  435. package/tools/ardy/cclay_sequence_generate.py +0 -705
  436. package/tools/ardy/cclay_worker.py +0 -304
  437. package/tools/ardy/dump-npz.py +0 -205
  438. package/tools/ardy/extract-rest.mjs +0 -299
  439. package/tools/ardy/extract.mjs +0 -507
  440. package/tools/ardy/footage.mjs +0 -467
  441. package/tools/ardy/gvhmr-floor.mjs +0 -102
  442. package/tools/ardy/gvhmr-to-cskel27.mjs +0 -21
  443. package/tools/ardy/gvhmr_fastpath.py +0 -168
  444. package/tools/ardy/gvhmr_trajectory.py +0 -240
  445. package/tools/ardy/merge-text-encoder.py +0 -160
  446. package/tools/ardy/mocap-metrics.mjs +0 -200
  447. package/tools/ardy/mocap-quality-gate.mjs +0 -110
  448. package/tools/ardy/motion-stabilize.mjs +0 -348
  449. package/tools/ardy/npz.mjs +0 -414
  450. package/tools/ardy/pose-to-npz.mjs +0 -106
  451. package/tools/ardy/prompt-limits.mjs +0 -5
  452. package/tools/ardy/run-edit-on-box.sh +0 -73
  453. package/tools/ardy/run-local.mjs +0 -233
  454. package/tools/ardy/run-on-box.sh +0 -593
  455. package/tools/ardy/run-sequence-on-box.sh +0 -177
  456. package/tools/ardy/runners/gvhmr-worker.mjs +0 -206
  457. package/tools/ardy/runners/index.mjs +0 -18
  458. package/tools/ardy/runners/local.mjs +0 -455
  459. package/tools/ardy/runners/proc.mjs +0 -125
  460. package/tools/ardy/runners/remote.mjs +0 -252
  461. package/tools/ardy/setup-local.mjs +0 -189
  462. package/tools/ardy/setup-text-encoder-on-box.sh +0 -93
  463. package/tools/ardy/setup-text-encoder.py +0 -306
  464. package/tools/ardy/smpl-cskel27.mjs +0 -178
  465. package/tools/ardy/visual-qa.mjs +0 -196
  466. package/tools/ardy/vq-car.mjs +0 -52
  467. package/tools/bench/EXP3.md +0 -61
  468. package/tools/bench/cclay_bench_extract_incam.py +0 -125
  469. package/tools/bench/cclay_bench_extract_obs.py +0 -204
  470. package/tools/bench/cclay_bench_runner.py +0 -42
  471. package/tools/bench/cube-contact.mjs +0 -162
  472. package/tools/bench/exp3.mjs +0 -152
  473. package/tools/bench/extract-bench-lib.mjs +0 -216
  474. package/tools/bench/extract-bench.mjs +0 -302
  475. package/tools/bench/fal-generate.mjs +0 -88
  476. package/tools/bench/fit/README.md +0 -189
  477. package/tools/bench/fit/camera.mjs +0 -59
  478. package/tools/bench/fit/contact.mjs +0 -419
  479. package/tools/bench/fit/footlock.mjs +0 -234
  480. package/tools/bench/fit/motion.mjs +0 -85
  481. package/tools/bench/fit/pin.mjs +0 -27
  482. package/tools/bench/fit/remote.mjs +0 -105
  483. package/tools/bench/fit-bench.mjs +0 -126
  484. package/tools/bench/fit-sanity.mjs +0 -45
  485. package/tools/bench/metrics.mjs +0 -313
  486. package/tools/bench/obs/depth.mjs +0 -258
  487. package/tools/bench/obs/extrinsics.mjs +0 -175
  488. package/tools/bench/obs/fit_mannequin_betas.py +0 -340
  489. package/tools/bench/obs/ground.mjs +0 -294
  490. package/tools/bench/obs/heading.mjs +0 -151
  491. package/tools/bench/obs/ladder.mjs +0 -555
  492. package/tools/bench/obs/mannequin-betas.json +0 -124
  493. package/tools/bench/obs/remote.mjs +0 -83
  494. package/tools/bench/obs/rest_joints.py +0 -52
  495. package/tools/bench/obs/ybot-targets.mjs +0 -49
  496. package/tools/bench/obs-bench.mjs +0 -520
  497. package/tools/bench/score.mjs +0 -343
  498. package/tools/bench/summarize.mjs +0 -86
  499. package/tools/demo-worker/README.md +0 -137
  500. package/tools/demo-worker/api-client.mjs +0 -233
  501. package/tools/demo-worker/generate.mjs +0 -301
  502. package/tools/demo-worker/index.mjs +0 -466
  503. package/tools/demo-worker/sign-core.mjs +0 -112
  504. package/tools/demo-worker/sign.mjs +0 -161
  505. package/tools/gt-render/browser.mjs +0 -182
  506. package/tools/gt-render/camera-math.mjs +0 -276
  507. package/tools/gt-render/page.mjs +0 -267
  508. package/tools/gt-render/render.mjs +0 -514
  509. package/tools/gt-render/scene-box.mjs +0 -31
  510. package/tools/gt-render/take-transform.mjs +0 -87
  511. package/tools/kimodo/compare-seam.mjs +0 -108
  512. package/tools/kimodo/constraints.mjs +0 -244
  513. package/tools/kimodo/edit.mjs +0 -259
  514. package/tools/kimodo/effector-constraints.mjs +0 -200
  515. package/tools/kimodo/generate.mjs +0 -646
  516. package/tools/kimodo/local-output.mjs +0 -265
  517. package/tools/kimodo/measure-pose.mjs +0 -65
  518. package/tools/kimodo/measure-preserve.mjs +0 -899
  519. package/tools/kimodo/measure-waypoints.mjs +0 -55
  520. package/tools/kimodo/mlx-generate.py +0 -111
  521. package/tools/kimodo/pose-constraints.mjs +0 -248
  522. package/tools/kimodo/preserve-mask.mjs +0 -543
  523. package/tools/kimodo/read-npz.mjs +0 -189
  524. package/tools/kimodo/run-edit-on-box.mjs +0 -155
  525. package/tools/kimodo/run-sequence-on-box.mjs +0 -178
  526. package/tools/kimodo/runner.mjs +0 -284
  527. package/tools/kimodo/seam-batch.mjs +0 -77
  528. package/tools/kimodo/setup-local.mjs +0 -30
  529. package/tools/kimodo/setup-on-box.sh +0 -339
  530. package/tools/kimodo/soma77-to-cskel27.mjs +0 -305
  531. package/tools/morphgs/assets/gen_truth.py +0 -39
  532. package/tools/morphgs/assets/mesh_ori_rig.txt +0 -27
  533. package/tools/morphgs/assets/playback-check.mjs +0 -30
  534. package/tools/morphgs/demo-gate.sh +0 -26
  535. package/tools/morphgs/fbx2morphgs.mjs +0 -68
  536. package/tools/morphgs/morphgs-to-cskel27.mjs +0 -105
  537. package/tools/morphgs/patches/preprocess_src-none-mode.patch +0 -76
  538. package/tools/morphgs/setup-on-cluster.sh +0 -126
  539. package/tools/projflow/driver.py +0 -1069
  540. package/tools/projflow/generate.mjs +0 -805
  541. package/tools/projflow/hml22-to-cskel27.mjs +0 -735
  542. package/tools/projflow/line-edit-job.mjs +0 -547
  543. package/tools/projflow/replay.mjs +0 -452
  544. package/tools/projflow/runner.mjs +0 -269
  545. package/tools/projflow/service.mjs +0 -742
  546. package/tools/track/DESIGN.md +0 -513
  547. package/tools/track/backfill-provenance.mjs +0 -116
  548. package/tools/track/budget.mjs +0 -8
  549. package/tools/track/check-rig.mjs +0 -209
  550. package/tools/track/diagnostics.schema.json +0 -60
  551. package/tools/track/export-rig.mjs +0 -256
  552. package/tools/track/fallback.mjs +0 -7
  553. package/tools/track/fk-parity-fixture.mjs +0 -161
  554. package/tools/track/gate.mjs +0 -349
  555. package/tools/track/masks.mjs +0 -318
  556. package/tools/track/metrics.mjs +0 -201
  557. package/tools/track/publish-obs.mjs +0 -108
  558. package/tools/track/py/check_env.py +0 -61
  559. package/tools/track/py/eval_lr.py +0 -229
  560. package/tools/track/py/lr_viterbi.py +0 -294
  561. package/tools/track/py/masks.py +0 -405
  562. package/tools/track/py/objective.py +0 -382
  563. package/tools/track/py/rig.py +0 -508
  564. package/tools/track/py/scene.py +0 -326
  565. package/tools/track/py/test_joint_indices.py +0 -123
  566. package/tools/track/py/test_lr_viterbi.py +0 -195
  567. package/tools/track/py/test_masks.py +0 -229
  568. package/tools/track/py/test_rig.py +0 -288
  569. package/tools/track/py/test_scene.py +0 -354
  570. package/tools/track/py/test_track.py +0 -328
  571. package/tools/track/py/track.py +0 -411
  572. package/tools/track/remote.mjs +0 -140
  573. package/tools/track/rig-dump.mjs +0 -227
  574. package/tools/track/run-box-tests.mjs +0 -14
  575. package/tools/track/run-box.mjs +0 -168
  576. package/tools/track/setup-box.sh +0 -59
  577. package/tools/track/study-2d.mjs +0 -728
  578. /package/src/{ardy → motion}/collision-blockers.js +0 -0
  579. /package/src/{ardy → motion}/ground.js +0 -0
  580. /package/src/{ardy → motion}/ik-drag.js +0 -0
  581. /package/src/{ardy → motion}/ik-key-json.js +0 -0
  582. /package/src/{ardy → motion}/ik.js +0 -0
  583. /package/src/{ardy → motion}/physics-surface.js +0 -0
  584. /package/src/{ardy → motion}/playback-clock.js +0 -0
  585. /package/src/{ardy → motion}/range-pin.js +0 -0
@@ -1,5 +1,8 @@
1
1
  // Shared by the browser and sidecar. No Node, React, renderer or provider imports.
2
2
  import { STUDIO_ELEMENTS, isSettableElement } from "./studio-elements.js";
3
+ import { STUDIO_ERROR_CODES, refusalCode } from "./studio-error-codes.js";
4
+ import { EFFECT_VALUE_SCHEMA } from "./effects/effect-record.js";
5
+ import { wrongType, unknownField, missingField, arrayRefusal, duplicateItem, stringRefusal, enumRefusal, numberRefusal, unionRefusal } from "./studio-schema-errors.js";
3
6
  // Mirrors PREVIS_MODES (project.js) and POSTURES (scenes.js). Neither is imported:
4
7
  // both pull in three.js, which the agent sidecar must not load. The protocol test pins the lists to their owners.
5
8
  const PREVIS_MODES = ["storyboard", "animation"], POSTURES = ["stand", "sit", "lie"];
@@ -10,7 +13,7 @@ export const STUDIO_PROTOCOL_VERSION = "studio-agent-v1";
10
13
  export const STUDIO_CONTEXT_MAX_BYTES = 64 * 1024;
11
14
  export const STUDIO_CONTEXT_LIMITS = Object.freeze({ entities: 24, entityIndex: 400, shots: 8, assets: 48, recentReceipts: 3, jobs: 8 });
12
15
  export const STUDIO_TOOL_FAMILIES = Object.freeze(["inspect_studio", "run_action", "verify_result"]);
13
- export const STUDIO_TOOL_ALIASES = Object.freeze(["operate_studio", "arrange_objects", "arrange_characters", "patch_elements", "frame_shot", "generate_motion", "undo_edit"]);
16
+ export const STUDIO_TOOL_ALIASES = Object.freeze(["operate_studio", "arrange_objects", "arrange_characters", "patch_elements", "frame_shot", "undo_edit"]);
14
17
  export const STUDIO_TOOL_LABELS = Object.freeze({
15
18
  inspect_studio: "Read the scene",
16
19
  operate_studio: "Selection and view",
@@ -18,7 +21,6 @@ export const STUDIO_TOOL_LABELS = Object.freeze({
18
21
  arrange_characters: "Arrange characters",
19
22
  patch_elements: "Edit properties",
20
23
  frame_shot: "Frame the shot",
21
- generate_motion: "Generate motion",
22
24
  verify_result: "Verify the result",
23
25
  undo_edit: "Undo an edit",
24
26
  run_action: "Run an editor action",
@@ -28,12 +30,7 @@ export const STUDIO_TOOLS = Object.freeze(Object.keys(STUDIO_TOOL_LABELS));
28
30
  * object→objects, shot→shot, stage→stage. */
29
31
  export const STUDIO_PATCH_KINDS = Object.freeze(["character", "object", "shot", "stage"]);
30
32
  export const STUDIO_PATCH_DOMAINS = Object.freeze({ character: "cast", object: "objects", shot: "shot", stage: "stage" });
31
- export const STUDIO_ERROR_CODES = Object.freeze([
32
- "INVALID_ARGUMENT", "INVALID_CONTEXT", "INVALID_IDENTITY", "INVALID_TURN_ID", "INVALID_SESSION_ID", "INVALID_RECEIPT", "INVALID_RANGE", "INVALID_REQUEST",
33
- "UNKNOWN_TOOL", "UNKNOWN_VARIANT", "DUPLICATE_NAME", "CONTEXT_LIMIT", "CONTEXT_TOO_LARGE", "AMBIGUOUS_TARGET", "AMBIGUOUS_BASIS", "TARGET_NOT_READY", "TARGET_BUSY",
34
- "STALE_TARGET", "STALE_SCENE", "STALE_ENVIRONMENT", "STALE_CURSOR", "CAPABILITY_MISSING", "LIVE_HUB_UNAVAILABLE", "AUTH_REQUIRED", "GENERATION_LIMIT", "RATE_LIMITED", "BACKEND_UNAVAILABLE",
35
- "VERIFICATION_FAILED", "REPAIR_REGRESSED", "CANCELLED", "UNCERTAIN_APPLY", "UNDO_CONFLICT", "CONFIRMATION_REQUIRED", "TIMEOUT", "UNDO_EXPIRED",
36
- ]);
33
+ export { STUDIO_ERROR_CODES, refusalCode };
37
34
  export const STUDIO_VARIANTS = freezeStudioData({
38
35
  selectionKinds: ["scene", "object", "character", "rig", "camera"], modes: ["scene", "pose", "camera", "motion"], shotModes: ["keys", "follow", "rail"],
39
36
  framingSizes: ["extreme close-up", "close-up", "medium close-up", "medium shot", "medium-wide shot", "wide shot", "extreme wide shot"],
@@ -60,8 +57,7 @@ const record = value => value !== null && typeof value === "object" && !Array.is
60
57
  const text = (maxLength = 120) => ({ type: "string", minLength: 1, maxLength });
61
58
  const number = (minimum, maximum) => ({ type: "number", ...(minimum !== undefined ? { minimum } : {}), ...(maximum !== undefined ? { maximum } : {}) });
62
59
  const integer = (minimum = 0, maximum = Number.MAX_SAFE_INTEGER) => ({ ...number(minimum, maximum), type: "integer" });
63
- const bool = { type: "boolean" };
64
- const literal = value => ({ const: value });
60
+ const bool = { type: "boolean" }, literal = value => ({ const: value });
65
61
  const choices = values => ({ type: "string", enum: values });
66
62
  const nullable = schema => ({ oneOf: [schema, { type: "null" }] });
67
63
  const array = (items, maxItems, minItems = 0, uniqueItems = false) => ({ type: "array", items, minItems, maxItems, ...(uniqueItems ? { uniqueItems } : {}) });
@@ -114,9 +110,6 @@ const characterOp = union(
114
110
  object({ op: literal("update"), characterId: id }, { position, facing, scale: positive, name, hidden: bool }),
115
111
  object({ op: literal("remove"), characterId: id }),
116
112
  );
117
- export const BEAT_TEXT_MAX = 500;
118
- const generateSource = object({ kind: literal("generate"), beats: array(object({ text: { ...text(BEAT_TEXT_MAX), description: 'An English description of the body movement starting with "A person". The editor refuses prompts over 500 characters.' } }, { seconds: number(0.5, 60) }), 8, 1) }, { durationSeconds: number(2, 60), seed: integer(-2147483648, 2147483647) });
119
- const source = union(generateSource, object({ kind: literal("reuse"), artifactId: id }));
120
113
 
121
114
  /* ----------------------------------------------- element patches ----
122
115
  * The accepted fields of `patch_elements` are DERIVED from the element
@@ -126,18 +119,20 @@ const source = union(generateSource, object({ kind: literal("reuse"), artifactId
126
119
  * (schedules, routes, pictures) declare their shape here, because the table
127
120
  * records what an element IS, not how JSON carries it. */
128
121
  const dataImage = { ...text(2 * 1024 * 1024), pattern: "^data:image/[A-Za-z0-9.+-]+[;,]" };
129
- const promptBlock = object({ startFrame: integer(), endFrame: integer(1), text: text(2000) }, { id });
122
+
130
123
  const cameraKey = object({ frame: integer(), framing: object({ pos: vec3, yaw: number(), pitch: number(), fovDeg: number(1, 179) }) }, { id });
131
124
  // A mark is an arc fraction, or { t, bank, pitch } when the object should lean
132
125
  // there (degrees, +-90). The route normaliser (src/object-path.js) owns the
133
126
  // rules; this only carries the shape through untouched.
134
127
  const routeMark = union(number(0, 1), object({ t: number(0, 1) }, { bank: number(-90, 90), pitch: number(-90, 90) }));
135
128
  const objectRoute = nullable(object({ points: array(vec3, 64, 2) }, { speed: number(0, 50), faceTravel: bool, loop: bool, extend: bool, marks: array(routeMark, 6) }));
129
+ // A practical light and a glow (src/object-look.js owns the defaults and the repair).
130
+ const objectLight = object({}, { type: choices(["point", "spot"]), color: { ...text(32), pattern: "^#[0-9a-fA-F]{6}$" }, intensity: number(0, 1000), range: number(0, 100), angle: number(1, 89), softness: number(0, 1), shadow: bool, flicker: number(0, 1), aim: nullable(vec3) });
131
+ const objectGlow = nullable(object({}, { color: { ...text(32), pattern: "^#[0-9a-fA-F]{6}$" }, intensity: number(0, 20), halo: number(0, 4) }));
136
132
  const PATCH_VALUE_SCHEMAS = {
137
- "character.pose": nullable(id), "character.identityImage": nullable(dataImage), "character.promptBlocks": array(promptBlock, 64),
133
+ "character.pose": nullable(id), "character.identityImage": nullable(dataImage), "object.light": objectLight, "object.glow": objectGlow, "object.effect": EFFECT_VALUE_SCHEMA,
138
134
  "object.parent": nullable(id), "object.path": objectRoute, "stage.environmentImage": nullable(dataImage),
139
- "shot.cameraKeys": array(cameraKey, 64), "shot.targetModel": nullable(id),
140
- };
135
+ "shot.cameraKeys": array(cameraKey, 64), "shot.targetModel": nullable(id) };
141
136
  /** One declared element as a JSON value schema, or null when the declaration
142
137
  * carries no carriable shape (a structured element without a declared value
143
138
  * schema above). Null elements are omitted from the patch schema, and
@@ -151,9 +146,9 @@ export function patchValueSchema(element) {
151
146
  if (element.type === "color") return { ...text(32), pattern: "^#[0-9a-fA-F]{6}$" };
152
147
  if (element.type === "image") return nullable(dataImage);
153
148
  if (element.type === "vec3") {
154
- const bounds = element.gizmo ?? element;
155
- if (bounds.min && typeof bounds.min === "object" && bounds.max && typeof bounds.max === "object") {
156
- return object({ x: number(bounds.min.x, bounds.max.x), y: number(bounds.min.y, bounds.max.y), z: number(bounds.min.z, bounds.max.z) });
149
+ const { min, max } = element; // the element's own range; a gizmo envelope only limits the UI drag
150
+ if (min && typeof min === "object" && max && typeof max === "object") {
151
+ return object({ x: number(min.x, max.x), y: number(min.y, max.y), z: number(min.z, max.z) });
157
152
  }
158
153
  return vec3;
159
154
  }
@@ -165,10 +160,10 @@ export function patchValueSchema(element) {
165
160
  * derived from the same declaration table as `patchValueSchema` so the two
166
161
  * can never drift apart. */
167
162
  export function buildPatchDescriptors(elements) {
168
- return elements.filter(isSettableElement).map(({ path, type, min, max, enum: enumValues, gizmo, note }) => ({
163
+ return elements.filter(isSettableElement).map(({ path, type, min, max, enum: enumValues, note }) => ({
169
164
  path, type,
170
- ...(gizmo?.min !== undefined ? { min: { ...gizmo.min } } : min !== undefined ? { min: min && typeof min === "object" ? { ...min } : min } : {}),
171
- ...(gizmo?.max !== undefined ? { max: { ...gizmo.max } } : max !== undefined ? { max: max && typeof max === "object" ? { ...max } : max } : {}),
165
+ ...(min !== undefined ? { min: min && typeof min === "object" ? { ...min } : min } : {}),
166
+ ...(max !== undefined ? { max: max && typeof max === "object" ? { ...max } : max } : {}),
172
167
  ...(enumValues ? { enum: [...enumValues] } : {}),
173
168
  ...(note ? { note } : {}),
174
169
  }));
@@ -204,8 +199,7 @@ const toolSchemas = {
204
199
  arrange_characters: object({ ops: array(characterOp, 8, 1) }),
205
200
  patch_elements: object({ ops: array(patchOp, 32, 1) }),
206
201
  frame_shot: object({ subjectIds: ids(1), framing }, { shotId: id, caption: { type: "string", maxLength: 500 }, keyAtFrame: integer() }),
207
- generate_motion: object({ characterId: id, source }, { repair: { ...choices(["bounded", "none"]), default: "bounded" } }),
208
- verify_result: object({ checks: array(choices(["placement", "framing", "motion"]), 3, 1, true) }, { receiptId: id, targets: ids(), range: union(literal("whole_clip"), range), visual: { ...choices(["none", "frame", "contact_sheet", "plan"]), default: "none" } }),
202
+ verify_result: object({ checks: { ...array(choices(["placement", "framing", "motion", "lighting"]), 4, 1, true), description: "lighting measures the shot frame rendered at the playhead: checks.lighting { meanLuma 0..1, clippedPct (a channel at full scale), crushedPct (pure black), subjects: [{ id, meanLuma, contrast (5th to 95th percentile luma spread, 0..1) }] inside each target character's projected box, look: { preset, envLight, ao } the stage look's environment light and occlusion strength }. motion also answers exportFraming (one per character with a take): what the camera the export renders shows of the take over sampledFrames: camera (shot where a shot covers the frame, studio = the Studio camera as it stands when none does; shotIds), character { screenFraction [min, max] of the frame height, inFrame [lowest share of its joints' screen box inside the frame, frame], behindCamera, cut { head, hand_l, hand_r, foot_l, foot_r } }, and every object the take names { id, name, inFrame, out, cut }; out, cut and behindCamera are spans [first, last] of sampled frames; { character: { id }, reason } when it cannot be measured" } }, { receiptId: id, targets: ids(), range: union(literal("whole_clip"), range), visual: { ...choices(["none", "frame", "contact_sheet", "plan", "trace"]), default: "none" }, frames: { ...union(choices(["even", "beats"]), array(integer(), 8, 1, true)), default: "even", description: "contact_sheet frames: even spacing, beats (grab/release, beat starts, the worst QA frames, then even fill, answered in visualRefs[0].picks), or up to 8 timeline frames of your choice" }, views: { ...array(choices(["front", "side", "three-quarter", "top"]), 2, 1, true), description: "contact_sheet from cameras of its own around the character instead of the shot camera (which stays as it is): a row per view, each held still over the frames and framed on focus, so small actions fill the tile; side and three-quarter look from the focus's side; a sheet always holds two views at least 60 degrees apart: if the asked ones look along nearly one line, side, front or top is added and named in visualRefs[0].addedView" }, focus: { ...union(text(120), array(text(120), 3, 1, true)), description: "with views: what to frame, as refs (object:<id>, joint:<Bone>, prop:<prop>.<point>, landmark:mouth); default the whole character" }, trail: { ...union(text(120), array(text(120), 3, 1, true)), description: "with views: refs whose path over the range is drawn as one dot per frame (spacing shows speed; later frames faint, the tile's own frame ringed), colours in visualRefs[0].trails" }, ghosts: { ...bool, description: "with views: each tile also shows the first target's poses around its frame as translucent ghosts (onion skin), earlier ones blue, later ones orange, carried props included, so spacing and arcs read in one tile; how many and how far apart is view.setGhosts (while it is off: 3 each side, every 4 frames), answered in visualRefs[0].ghosts" } }),
209
203
  undo_edit: object({ receiptId: id }),
210
204
  run_action: object({ action: id }, { args: openObject, confirmationToken: id }),
211
205
  };
@@ -219,7 +213,7 @@ const entity = object({ id, kind: choices(["object", "character", "rig"]), token
219
213
  name, detailsOmitted: bool, position: vec3, yawDeg: number(), rotationDeg: vec3, scale: union(positive, positiveVec3), bounds: nullable(bounds),
220
214
  libraryKind: id, renderer: id, color: nullable(text(32)), tint: nullable(text(32)), modelId: nullable(text(120)), assetId: id, parentId: nullable(id), attachment: nullable(object({ characterId: id, bone: nullable(id) })), pathPointCount: integer(0, 64), pathMarks: array(union(number(0, 1), object({ t: number(0, 1) }, { bank: number(-90, 90), pitch: number(-90, 90) })), 6),
221
215
  local: object({ position: vec3, rotationDeg: vec3 }),
222
- motion: object({ takeId: nullable(id), frames: integer(), ikKeyCount: integer(), promptBlockCount: integer() }, { poseId: nullable(id), keyIds: ids(8, 0) }),
216
+ motion: object({ takeId: nullable(id), frames: integer(), ikKeyCount: integer() }, { poseId: nullable(id), keyIds: ids(8, 0) }),
223
217
  capabilities: object({ rigReady: bool, ik: bool, measuredFeet: bool }),
224
218
  // `kind` above already names the entity class ("character"); a character's own
225
219
  // kind is `characterKind`: a rigged model, or a rig-free capsule figure.
@@ -298,7 +292,9 @@ const authoredReceipt = { ...receiptBase, authored: literal(true), undo };
298
292
  // A run_action receipt names the registered action and what it did.
299
293
  const actionFields = { action: id, summary: text(240) };
300
294
  const receiptVariants = {
301
- completed: object({ ...receiptBase, status: literal("completed"), kind: choices(["job", "document", "transaction", "mutation"]) }, { ...actionFields, ...batchDetails, output: openObject, jobId: id, txId: id, nextHost: identity }),
295
+ // A mutation inside an agent turn answers "completed" (the turn owns its undo entry); a patch
296
+ // among them still reports each op, so ops ride here too.
297
+ completed: object({ ...receiptBase, status: literal("completed"), kind: choices(["job", "document", "transaction", "mutation"]) }, { ...actionFields, ...batchDetails, output: openObject, jobId: id, txId: id, nextHost: identity, ops: opResults }),
302
298
  started: object({ ...receiptBase, status: literal("started"), kind: literal("job"), jobId: id, authored: literal(false), undo: literal(null) }, { ...actionFields }),
303
299
  applied: object({ ...authoredReceipt, status: literal("applied") }, { mutated: literal(true), ops: opResults, ...batchDetails, ...actionFields }),
304
300
  partial: object({ ...authoredReceipt, status: literal("partial"), ops: opResults }, { mutated: literal(true), ...batchDetails }),
@@ -321,65 +317,66 @@ const failureSchema = object({ ok: literal(false), commandId: id, host: identity
321
317
  // These are JSON Schema data, not validators with hidden browser dependencies.
322
318
  // x-studio-range is the sole relational schema annotation: end > start.
323
319
  export const StudioSchemas = freezeStudioData({ catalogue: STUDIO_CATALOGUE, variants: STUDIO_VARIANTS, Vec3: vec3, FrameRange: range, PositionSpec: position, FacingSpec: facing,
324
- ObjectOp: objectOp, CharacterOp: characterOp, Framing: framing, GenerateSource: generateSource, Source: source, Identity: identity, Host: host, TargetGuard: guardSchema,
320
+ ObjectOp: objectOp, CharacterOp: characterOp, Framing: framing, Identity: identity, Host: host, TargetGuard: guardSchema,
325
321
  Entity: entity, ShotSummary: shotSummary, StudioContextV1: contextSchema, StudioTurn: turnSchema, StudioStop: stopSchema, Receipt: receiptSchema, ReceiptVariants: receiptVariants, Failure: failureSchema, GeometryFacts: geometryFacts });
326
322
 
327
- /** Validate the closed JSON Schema subset used above; return a detached value
328
- * with declared defaults. Errors contain schema paths, never payload values. */
323
+ /** Validate the closed JSON Schema subset used above; return a detached value with declared
324
+ * defaults. Errors name the path and what it allows (studio-schema-errors.js). */
329
325
  export function validateStudioSchema(schema, value, code = "INVALID_ARGUMENT", path = "$") {
330
- if (schema.oneOf) {
331
- const matches = [];
332
- const failures = [];
333
- for (const branch of schema.oneOf) {
326
+ if (schema.oneOf || schema.anyOf) {
327
+ const matches = [], failures = [];
328
+ for (const branch of schema.oneOf ?? schema.anyOf) {
334
329
  try { matches.push(validateStudioSchema(branch, value, code, path)); }
335
330
  catch (error) { if (!(error instanceof StudioProtocolError)) throw error; failures.push({ branch, error }); }
336
331
  }
337
332
  // No variant matched: the generic "one of N" message hides which path and
338
333
  // bound actually offended. Surface the failure from the variant the input
339
334
  // discriminates to (matching target.kind), or else the deepest failing
340
- // path, instead of the union's own shallow message.
335
+ // path, instead of the union's own shallow message. A union of plain values
336
+ // (true, false or "ifClean") that failed at its own path names every form.
341
337
  if (matches.length === 0 && failures.length) {
342
- const kind = record(value) && record(value.target) ? value.target.kind : undefined;
343
- const discriminated = kind !== undefined && failures.find(({ branch }) => branch.properties?.target?.properties?.kind?.const === kind);
338
+ const kind = record(value) && record(value.target) ? value.target.kind : undefined, discriminated = kind !== undefined && failures.find(({ branch }) => branch.properties?.target?.properties?.kind?.const === kind);
339
+ const forms = !discriminated && unionRefusal(path, failures, value);
340
+ if (forms) fail(code, forms, path);
344
341
  const chosen = discriminated || failures.reduce((best, next) => next.error.details.path.length > best.error.details.path.length ? next : best);
345
342
  throw chosen.error;
346
343
  }
347
- if (matches.length !== 1) fail(code, "Expected exactly one supported variant.", path);
344
+ if (matches.length !== 1 && schema.oneOf) fail(code, `${path}: matches ${matches.length} variants; send the fields of exactly one.`, path);
348
345
  return matches[0];
349
346
  }
350
- if (Object.hasOwn(schema, "const")) { if (value !== schema.const) fail(code, "Invalid constant.", path); return value; }
351
- if (schema.type === "null") { if (value !== null) fail(code, "Expected null.", path); return null; }
347
+ if (Object.hasOwn(schema, "const")) { if (value !== schema.const) fail(code, wrongType(path, schema, value), path); return value; }
348
+ if (schema.type === "null") { if (value !== null) fail(code, wrongType(path, schema, value), path); return null; }
352
349
  if (schema.type === "object") {
353
- if (!record(value)) fail(code, "Expected object.", path);
350
+ if (!record(value)) fail(code, wrongType(path, schema, value), path);
354
351
  const open = schema.additionalProperties === true;
355
- for (const key of Object.keys(value)) if (!Object.hasOwn(schema.properties, key) && !open) fail(code, "Unexpected field.", path);
352
+ for (const key of Object.keys(value)) if (!Object.hasOwn(schema.properties, key) && !open) fail(code, unknownField(path, schema, key), path);
356
353
  const result = {};
357
354
  if (open) for (const key of Object.keys(value)) if (!Object.hasOwn(schema.properties, key)) result[key] = structuredClone(value[key]);
358
355
  for (const [key, child] of Object.entries(schema.properties)) {
359
356
  if (Object.hasOwn(value, key)) result[key] = validateStudioSchema(child, value[key], code, `${path}.${key}`);
360
- else if (schema.required.includes(key)) fail(code, "Required field missing.", `${path}.${key}`);
357
+ else if (schema.required.includes(key)) fail(code, missingField(path, key), `${path}.${key}`);
361
358
  else if (Object.hasOwn(child, "default")) result[key] = structuredClone(child.default);
362
359
  }
363
360
  if (schema["x-studio-range"] && result.endFrameExclusive <= result.startFrame) fail(code, "Frame range must be nonempty and half-open.", path);
364
361
  return result;
365
362
  }
366
363
  if (schema.type === "array") {
367
- if (!Array.isArray(value) || value.length < schema.minItems || value.length > schema.maxItems) fail(code, "Array outside supported bounds.", path);
364
+ if (!Array.isArray(value) || value.length < schema.minItems || value.length > schema.maxItems) fail(code, arrayRefusal(path, schema, value), path);
368
365
  const result = value.map((item, i) => validateStudioSchema(schema.items, item, code, `${path}[${i}]`));
369
- if (schema.uniqueItems && new Set(result.map(item => JSON.stringify(item))).size !== result.length) fail(code, "Duplicate array member.", path);
366
+ if (schema.uniqueItems && new Set(result.map(item => JSON.stringify(item))).size !== result.length) fail(code, duplicateItem(path, result), path);
370
367
  return result;
371
368
  }
372
369
  if (schema.type === "string") {
373
- if (typeof value !== "string" || !value.trim() || (schema.maxLength !== undefined && [...value].length > schema.maxLength) || (schema.pattern && !new RegExp(schema.pattern).test(value))) fail(code, typeof value === "string" && schema.description && [...value].length > schema.maxLength ? `Over ${schema.maxLength} characters. ${schema.description}` : "Invalid or oversized string.", path);
374
- if (schema.enum && !schema.enum.includes(value)) fail(code, "Unsupported enum value.", path);
370
+ if (typeof value !== "string" || !value.trim() || (schema.maxLength !== undefined && [...value].length > schema.maxLength) || (schema.pattern && !new RegExp(schema.pattern).test(value))) fail(code, stringRefusal(path, schema, value), path);
371
+ if (schema.enum && !schema.enum.includes(value)) fail(code, enumRefusal(path, schema, value), path);
375
372
  return value;
376
373
  }
377
- if (schema.type === "boolean") { if (typeof value !== "boolean") fail(code, "Expected boolean.", path); return value; }
374
+ if (schema.type === "boolean") { if (typeof value !== "boolean") fail(code, wrongType(path, schema, value), path); return value; }
378
375
  if (schema.type === "number" || schema.type === "integer") {
379
- if (typeof value !== "number" || !Number.isFinite(value) || (schema.type === "integer" && !Number.isSafeInteger(value)) || (schema.exclusiveMinimum !== undefined && value <= schema.exclusiveMinimum)) fail(code, "Number outside supported bounds.", path);
376
+ if (typeof value !== "number" || !Number.isFinite(value) || (schema.type === "integer" && !Number.isSafeInteger(value)) || (schema.exclusiveMinimum !== undefined && value <= schema.exclusiveMinimum)) fail(code, numberRefusal(path, schema, value), path);
380
377
  if ((schema.minimum !== undefined && value < schema.minimum) || (schema.maximum !== undefined && value > schema.maximum)) {
381
378
  const bounds = [schema.minimum, schema.maximum].filter(bound => bound !== undefined);
382
- fail(code, `${path}: Expected a number within [${bounds.join(", ")}].`, path);
379
+ fail(code, `${path}: Expected a number within [${bounds.join(", ")}], got ${value}.`, path);
383
380
  }
384
381
  return value;
385
382
  }
@@ -455,13 +452,6 @@ export function validateStudioContextFreshness(submitted, current) {
455
452
  }
456
453
  const pickIdentity = value => Object.fromEntries(Object.keys(identityFields).map(key => [key, value[key]]));
457
454
 
458
- function validateGenerationTiming(value) {
459
- const timed = value.beats.filter(beat => beat.seconds !== undefined).length;
460
- if ((value.durationSeconds !== undefined && timed !== 0) || (value.durationSeconds === undefined && timed !== value.beats.length)) fail("INVALID_ARGUMENT", "Use total duration or a duration for every beat, not mixed timing.");
461
- const total = value.durationSeconds ?? value.beats.reduce((sum, beat) => sum + beat.seconds, 0);
462
- if (total < 2 || total > 60 || total / value.beats.length < 0.5) fail("INVALID_ARGUMENT", "Generation must be 2-60 seconds with at least 0.5 seconds per beat.");
463
- return total;
464
- }
465
455
  /** Unknown paths are answered with the paths that DO exist for that kind, so a
466
456
  * caller never has to guess the vocabulary from a rejection. */
467
457
  function validatePatchPaths(args) {
@@ -501,10 +491,6 @@ export function validateStudioCommand(command) {
501
491
  // final batch is still checked for overlap.
502
492
  if (args.collisionPolicy === "avoid" && !args.ops.some(op => ["create", "update"].includes(op.op) && op.position?.relativeTo)) fail("INVALID_ARGUMENT", "collisionPolicy avoid needs at least one op with position.relativeTo (it nudges outward from that reference); other ops are placed as given.");
503
493
  }
504
- if (command.name === "generate_motion" && args.source.kind === "generate") {
505
- args.source.beats = args.source.beats.map(beat => ({ ...beat, text: beat.text.trim().replace(/\s+/g, " ") }));
506
- validateGenerationTiming(args.source);
507
- }
508
494
  if (command.name === "frame_shot" && args.framing.exact && JSON.stringify(args.framing.exact.position) === JSON.stringify(args.framing.exact.lookAt)) fail("INVALID_ARGUMENT", "Camera position and aim cannot coincide.");
509
495
  if (command.name === "patch_elements") {
510
496
  for (const op of args.ops) if (!Object.keys(op.set).length) fail("INVALID_ARGUMENT", "Patch has no fields.");
@@ -518,22 +504,6 @@ export function validateStudioCommand(command) {
518
504
  }
519
505
  return { name: command.name, args };
520
506
  }
521
- export function compileStudioBeats(value) {
522
- const source = validateStudioSchema(generateSource, value);
523
- const totalSeconds = validateGenerationTiming(source);
524
- const frameCount = Math.round(totalSeconds * 24);
525
- const blocks = []; let cumulative = 0, startFrame = 0;
526
- for (const [sourceBeat, beat] of source.beats.entries()) {
527
- cumulative += beat.seconds ?? totalSeconds / source.beats.length;
528
- const end = sourceBeat === source.beats.length - 1 ? frameCount : Math.round(cumulative * 24);
529
- const pieces = Math.ceil((end - startFrame) / 120), start = startFrame;
530
- for (let i = 0; i < pieces; i++) {
531
- const endFrameExclusive = start + Math.round((end - start) * (i + 1) / pieces);
532
- blocks.push({ sourceBeat, text: beat.text.trim().replace(/\s+/g, " "), startFrame, endFrameExclusive }); startFrame = endFrameExclusive;
533
- }
534
- }
535
- return freezeStudioData({ fps: 24, frameCount, durationSeconds: frameCount / 24, blocks });
536
- }
537
507
  export function validateReceipt(value) {
538
508
  if (value?.ok === false) {
539
509
  const r = validateStudioSchema(failureSchema, value, "INVALID_RECEIPT");
@@ -3,16 +3,18 @@
3
3
  // history, the action registry); this module owns no React or renderer state.
4
4
  import { createCommandBus } from "./command-bus.js";
5
5
  import { readElementDocument, elementReadback, elementPatchArgs, elementTarget, elementPatchReceipt } from "./commands/elements.js";
6
- import { physicsKeyStamp } from "./ardy/physics-review.js";
6
+ import { physicsKeyStamp } from "./motion/physics-review.js";
7
7
  import { shotAtFrame } from "./cuts.js";
8
8
  import { isProxyFigure } from "./scenes.js";
9
- import { CUTOUT_KIND, MESH_KIND, OBJECT_LIBRARY, supportHeightForObject } from "./scene-objects.js";
9
+ import { supportHeightForObject } from "./scene-objects.js";
10
+ import { studioAssetList } from "./studio-asset-list.js";
10
11
  import { buildStudioContext, physicsFingerprintInput, studioEntityCursor, validateStudioCursor } from "./studio-agent-context.js";
11
- import { createStudioCommandJournal, entityWorldBounds, framingChecks, localTransformReadback, placementChecks, sampledFramingResult, studioObjectCatalogue } from "./studio-agent-commands.js";
12
- import { verifyInstalledTake } from "./studio-agent-motion.js";
13
- import { shotGeometryFacts } from "./studio-geometry-facts.js";
14
- import { STUDIO_TOOL_FAMILIES, StudioProtocolError, validateReceipt, validateStudioCommand, validateStudioIdentity } from "./studio-agent-protocol.js";
15
- import { CONTACT_SHEET_LAYOUT, buildContactSheet, sampleContactSheetFrames } from "./studio-contact-sheet.js";
12
+ import { createStudioCommandJournal, framingChecks, localTransformReadback, placementChecks, sampledFramingResult, studioObjectCatalogue } from "./studio-agent-commands.js";
13
+ import { verifyInstalledTake } from "./studio-agent-motion.js"; import { objectRowBounds } from "./studio-entity-bounds.js"; import { exportFramingInto } from "./studio-export-framing.js";
14
+ import { shotGeometryFacts } from "./studio-geometry-facts.js"; import { KIT_CATALOGUE } from "./kits/index.js";
15
+ import { STUDIO_TOOL_FAMILIES, StudioProtocolError, validateReceipt, validateStudioCommand, validateStudioIdentity, refusalCode } from "./studio-agent-protocol.js";
16
+ import { studioVisual, verifyLighting } from "./studio-motion-visual.js";
17
+ import { withEffectState } from "./effects/state.js"; import { hdrEnvironmentCatalogue } from "./stage-hdr-library.js";
16
18
 
17
19
  // App-owned adapter: the shared command modules remain the only
18
20
  // planners and validators. Ports below publish through the native editor stores.
@@ -34,8 +36,7 @@ export function createStudioAppBinding(ports) {
34
36
  const motionContentKey = value => {
35
37
  if (!value || typeof value !== "object") return null;
36
38
  if (typeof value.studioTakeId === "string" && value.studioTakeId) return value.studioTakeId;
37
- if (typeof value.motionRef?.motionId === "string" && value.motionRef.motionId) return value.motionRef.motionId;
38
- return `${value.frames ?? 0}:${value.fps ?? 0}:${value.rotMats?.length ?? 0}:${value.rootPos?.length ?? 0}:${value.posedJoints?.length ?? 0}`;
39
+ return `${value.frames ?? 0}:${value.fps ?? 0}`;
39
40
  };
40
41
  const calibrationContentKey = value => value && typeof value === "object" ? JSON.stringify(value) : null;
41
42
  const tokens = new Map(), receipts = new Map(), images = new Map();
@@ -121,30 +122,18 @@ export function createStudioAppBinding(ports) {
121
122
  return { id: c.id, kind: "character", token: tokens.get(c.id).token, name: c.subject || c.id,
122
123
  position: { x: c.x, y: c.y ?? 0, z: c.z }, yawDeg: c.rot ?? 0, scale: c.scale ?? 1, tint: c.tint ?? null, modelId: c.model ?? null,
123
124
  motion: { takeId: t?.motion?.studioTakeId ?? null, frames: t?.motion?.frames ?? 0,
124
- ikKeyCount: t?.ikState?.keys.size ?? 0, promptBlockCount: c.layer?.promptClips?.length ?? 0 },
125
+ ikKeyCount: t?.ikState?.keys.size ?? 0 },
125
126
  capabilities: { rigReady: Boolean(t?.rig), ik: Boolean(t?.rig?.userData?.poseBind), measuredFeet: false },
126
127
  characterKind: isProxyFigure(c) ? "proxy" : "rig", ...(isProxyFigure(c) ? { posture: c.posture ?? "stand" } : {}) };
127
128
  }), ...s.objects.map(o => { const local = localTransformReadback(o, s.objects); return { id: o.id, kind: "object", token: tokens.get(o.id).token, name: o.name || o.id,
128
129
  position: { x: o.x, y: o.y ?? 0, z: o.z }, yawDeg: o.rot ?? 0,
129
130
  rotationDeg: { x: o.rotX ?? 0, y: o.rot ?? 0, z: o.rotZ ?? 0 }, scale: { x: o.scaleX, y: o.scaleY, z: o.scaleZ },
130
- renderer: o.renderer, color: o.color ?? null, ...(o.assetId ? { assetId: o.assetId } : {}),
131
+ renderer: o.renderer, color: o.color ?? null, bounds: objectRowBounds(o, s, ports), ...(o.assetId ? { assetId: o.assetId } : {}),
131
132
  parentId: o.parent ?? null, attachment: o.attach ?? null, pathPointCount: o.path?.points.length ?? 0,
132
133
  // the dots on the route, with the lean (degrees) a mark carries
133
134
  ...(o.path?.marks?.length ? { pathMarks: o.path.marks.map(mark => typeof mark === "number" ? mark : { t: mark.t, ...(mark.bank !== undefined ? { bank: mark.bank } : {}), ...(mark.pitch !== undefined ? { pitch: mark.pitch } : {}) }) } : {}), ...(local ? { local } : {}) }; })];
134
135
  }
135
136
  const frameRange = row => ({ startFrame: row.startFrame, endFrameExclusive: row.endFrame + 1 });
136
- function assetList(s) {
137
- const catalogue = studioObjectCatalogue().objects.map(({ kind }) => {
138
- const entry = OBJECT_LIBRARY.find(row => row.kind === kind);
139
- return { kind, name: entry?.label ?? kind, type: entry?.group === "Primitives" ? "primitive" : "set-piece" };
140
- });
141
- const imported = new Map();
142
- for (const o of s.objects) {
143
- const assetId = o.renderer === CUTOUT_KIND ? o.sourceAssetId || o.assetId : o.renderer === MESH_KIND ? o.assetId : null;
144
- if (assetId && !imported.has(assetId)) imported.set(assetId, { id: assetId, name: o.name || assetId, type: o.renderer === CUTOUT_KIND ? "image" : "mesh" });
145
- }
146
- return [...catalogue, ...imported.values()];
147
- }
148
137
  function context() {
149
138
  const s = refresh(), entities = entityProjection(s), registry = ports.actions?.();
150
139
  const shot = s.shots.find(row => row.id === s.selectedShotId) ?? shotAtFrame(s.shots, s.view.frame);
@@ -158,7 +147,7 @@ export function createStudioAppBinding(ports) {
158
147
  // buildStudioContext selects the detailed rows and writes the real page.
159
148
  entities, entityPage: { returned: 0, total: 0, truncated: false, nextCursor: null },
160
149
  shots: s.shots.map(row => ({ id: row.id, name: row.name, range: range(row), keyCount: row.cameraKeys.length })), shotsTruncated: false,
161
- assets: assetList(s), recentReceipts: [...receipts.values()].filter(r => r.ok).reverse().slice(0, 3).map(r => ({ id: r.receiptId, summary: r.status, canUndoDirect: ports.canUndo(r) })),
150
+ assets: studioAssetList(s.objects), recentReceipts: [...receipts.values()].filter(r => r.ok).reverse().slice(0, 3).map(r => ({ id: r.receiptId, summary: r.status, canUndoDirect: ports.canUndo(r) })),
162
151
  jobs: [], capabilities: { profile: "studio-slice-1", tools: STUDIO_TOOL_FAMILIES,
163
152
  rigReady: Boolean(s.targets.get(s.activeCharacterId)?.rig), cameraReady: Boolean(s.camera), bridgeReady: s.bridgeReady,
164
153
  ...(s.previsMode ? { storyboard: s.previsMode === "storyboard" } : {}) },
@@ -176,7 +165,7 @@ export function createStudioAppBinding(ports) {
176
165
  // the first 120 characters the protocol carries.
177
166
  const words = [...String(error?.message ?? "").trim()];
178
167
  return validateReceipt({ ok: false, commandId: request.commandId, host: request.host ?? request.binding?.host,
179
- code: error.code ?? "INVALID_ARGUMENT", phase, affectedIds: [], expectedTargets: [], currentTargets: [], mutated: false,
168
+ code: refusalCode(error), phase, affectedIds: [], expectedTargets: [], currentTargets: [], mutated: false,
180
169
  preserved: { authoredState: "unchanged" }, recovery: { action: "inspect", retryAllowed: false },
181
170
  ...(words.length ? { message: words.length > 120 ? `${words.slice(0, 119).join("")}…` : words.join("") } : {}) });
182
171
  }
@@ -196,13 +185,13 @@ export function createStudioAppBinding(ports) {
196
185
  if (id === s.host.sceneId) return { selection: s.selection, activeCharacterId: s.activeCharacterId, shotId: s.selectedShotId, view: s.view,
197
186
  ...(patched.length ? { patched } : {}) };
198
187
  const object = s.objects.find(row => row.id === id), entity = object ?? s.characters.find(row => row.id === id);
199
- // Beyond the transform an object's patched paths already carry: the world box it occupies and, for a
200
- // group, who hangs off it. `compact` (a receipt near its size limit) drops back to the paths alone.
201
- // A character's box means cloning and posing its rig, too dear for every receipt: inspect or verify has it.
188
+ // Beyond the transform an object's patched paths already carry: the world box it occupies (an empty: its
189
+ // parts', as inspect's rows) and, for a group, who hangs off it. `compact` (a receipt near its size limit) drops back to the paths alone.
190
+ // A character's box means cloning and posing its rig, too dear for every receipt or inspect row: verify has it.
202
191
  const extras = () => {
203
192
  if (!object || compact) return {};
204
193
  const childIds = s.objects.filter(row => row.parent === id).map(row => row.id);
205
- return { bounds: entityWorldBounds(object, { ...s, frame: s.view.frame }, { bounds: ports.bounds }),
194
+ return { bounds: objectRowBounds(object, s, ports),
206
195
  ...(childIds.length ? { childIds: childIds.slice(0, 12), ...(childIds.length > 12 ? { childCount: childIds.length } : {}) } : {}) };
207
196
  };
208
197
  if (patched.length) return { patched, ...extras() };
@@ -286,11 +275,12 @@ export function createStudioAppBinding(ports) {
286
275
  // A framing check also carries the cut's geometry, read from the scene: side of the action line
287
276
  // and sight lines per shot, the receipt's or targets' shots first (8 at most).
288
277
  const result = { receiptId: receipt?.receiptId ?? null, revision: s.revision, evidenceRevision, stale: evidenceRevision !== s.revision, checks: measured ? { coverage: "current-scene-targets" } : receipt.checks,
289
- verification: receipt?.verification ?? null, semanticStatus: "unavailable", visualRefs: [], unsupportedChecks: [], unsupportedReasons: {}, ...(args.checks.includes("framing") ? { geometry: shotGeometryFacts(scene, { first: receipt ? receipt.affectedIds : args.targets, ports: { bounds: ports.bounds } }) } : {}) };
290
- const reasons = result.unsupportedReasons, verified = [], skipped = [], pending = [];
291
- for (const check of args.checks.filter(check => check !== "motion" && measured)) {
278
+ verification: receipt?.verification ?? null, semanticStatus: "unavailable", visualRefs: [], unsupportedChecks: [], unsupportedReasons: {}, ...(args.checks.includes("framing") ? { geometry: shotGeometryFacts(scene, { first: receipt ? receipt.affectedIds : args.targets, ports: { bounds: ports.bounds } }) } : {}) }, reasons = result.unsupportedReasons, verified = [], skipped = [], pending = [];
279
+ // Lighting is measured from the rendered shot frame (studio-lighting-check.js), never from a receipt.
280
+ if (args.checks.includes("lighting")) verifyLighting(result, args.targets ?? receipt.affectedIds, scene, ports);
281
+ for (const check of args.checks.filter(check => !["motion", "lighting"].includes(check) && measured)) {
292
282
  if (!measured.length) { reasons[check] = "No target is an object or character in the current scene."; continue; }
293
- try { result.checks[check] = check === "placement" ? placementChecks(measured, scene, { bounds: ports.bounds }) : framingChecks(measured, scene, { bounds: ports.bounds }); }
283
+ try { result.checks[check] = check === "placement" ? placementChecks(measured, scene, { bounds: ports.bounds, takes: s.targets }) : framingChecks(measured, scene, { bounds: ports.bounds }); }
294
284
  catch (error) { if (!(error instanceof StudioProtocolError)) throw error; reasons[check] = error.message; }
295
285
  }
296
286
  if (args.checks.includes("framing") && args.range !== undefined && !args.rangeWasDefaulted) {
@@ -309,17 +299,7 @@ export function createStudioAppBinding(ports) {
309
299
  .then(verification => { verified[index] = verification; }, error => { if (!(error instanceof StudioProtocolError)) throw error; skipped.push(`${name}: ${error.message}`); }));
310
300
  });
311
301
  }
312
- // A frame is the shot camera's view; a plan is the Top View, the set from above
313
- // with names and the shot camera's wedge.
314
- if (args.visual !== "none") {
315
- if (args.visual === "contact_sheet") {
316
- // One image of frames across the range, each rendered through the export
317
- // path, which puts the playhead pose, shot camera and props back.
318
- const frames = sampleContactSheetFrames(args.range, s.frameCount), sheet = buildContactSheet(frames, ports.renderFrameBuffer), imageId = crypto.randomUUID();
319
- images.set(imageId, { dataUrl: ports.encodePng(sheet.data, sheet), width: sheet.width, height: sheet.height, frames, layout: CONTACT_SHEET_LAYOUT, revision: s.revision, receiptId: result.receiptId });
320
- result.visualRefs.push({ imageId, frames, layout: CONTACT_SHEET_LAYOUT });
321
- } else { const capture = args.visual === "plan" ? (ports.capturePlan ?? (() => fail("CAPABILITY_MISSING", "This editor cannot render the top view.")))() : ports.capture(); const imageId = crypto.randomUUID(); images.set(imageId, { ...capture, revision: s.revision, receiptId: result.receiptId }); result.visualRefs.push({ imageId, ...(args.visual === "plan" ? { kind: "plan" } : {}) }); }
322
- }
302
+ if (args.checks.includes("motion")) exportFramingInto(result, { args, s, receipt, scene, ports }); if (args.visual !== "none") studioVisual(args, { s, receipt, result, images, ports });
323
303
  const finish = () => {
324
304
  const computed = verified.filter(Boolean);
325
305
  if (computed.length) result.verification = computed.length === 1 ? computed[0] : computed;
@@ -347,7 +327,7 @@ export function createStudioAppBinding(ports) {
347
327
  // the model pays for): the revision is what the agent's next command is
348
328
  // admitted at, so a scope without it leaves that admission stale.
349
329
  const head = { revision: c.revision, host: (({ workspaceId, documentEpoch, sceneId, sceneEpoch }) => ({ workspaceId, documentEpoch, sceneId, sceneEpoch }))(c.host) };
350
- if (command.args.scope === "catalogue") return { ...head, ...studioObjectCatalogue() };
330
+ if (command.args.scope === "catalogue") return { ...head, ...studioObjectCatalogue(), kits: KIT_CATALOGUE, environments: hdrEnvironmentCatalogue() };
351
331
  if (command.args.scope === "document") return { ...head, scope: "document",
352
332
  ...readElementDocument(refresh().document, command.args, c.host.sceneId) };
353
333
  // Discovery for run_action: every registered action with its label, kind,
@@ -370,7 +350,8 @@ export function createStudioAppBinding(ports) {
370
350
  // Stable id order, so an offset cursor survives unrelated edits.
371
351
  const filtered = entityProjection(s).filter(wanted).sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
372
352
  const offset = args.cursor ? validateStudioCursor(args.cursor, c) : 0, limit = command.args.limit;
373
- return { ...head, entities: filtered.slice(offset, offset + limit), total: filtered.length,
353
+ // An effect row also carries its particles at the playhead (src/effects/state.js).
354
+ return { ...head, entities: filtered.slice(offset, offset + limit).map(row => withEffectState(row, s.view.frame, s.objects)), total: filtered.length,
374
355
  nextCursor: offset + limit < filtered.length ? studioEntityCursor(c, offset + limit) : null };
375
356
  },
376
357
  operate_studio: request => execute({ ...request, name: "operate_studio" }),
@@ -378,7 +359,6 @@ export function createStudioAppBinding(ports) {
378
359
  arrange_characters: request => execute({ ...request, name: "arrange_characters" }),
379
360
  patch_elements: request => execute({ ...request, name: "patch_elements" }),
380
361
  frame_shot: request => execute({ ...request, name: "frame_shot" }),
381
- generate_motion: request => execute({ ...request, name: "generate_motion" }),
382
362
  verify_result: request => execute({ ...request, name: "verify_result" }),
383
363
  undo_edit: request => runAction(request, { action: "edit.undo", args: request.args }),
384
364
  run_action: request => execute({ ...request, name: "run_action" }),
@@ -0,0 +1,17 @@
1
+ // The asset summary the agent's turn context lists: every catalogue kind the
2
+ // Studio can create, then each imported picture or model once.
3
+ import { CUTOUT_KIND, MESH_KIND, OBJECT_MENU_ENTRIES } from "./scene-objects.js";
4
+ import { studioObjectCatalogue } from "./studio-agent-commands.js";
5
+
6
+ export function studioAssetList(objects) {
7
+ const catalogue = studioObjectCatalogue().objects.map(({ kind }) => {
8
+ const entry = OBJECT_MENU_ENTRIES.find(row => row.kind === kind);
9
+ return { kind, name: entry?.label ?? kind, type: entry?.group === "Primitives" ? "primitive" : "set-piece" };
10
+ });
11
+ const imported = new Map();
12
+ for (const o of objects) {
13
+ const assetId = o.renderer === CUTOUT_KIND ? o.sourceAssetId || o.assetId : o.renderer === MESH_KIND ? o.assetId : null;
14
+ if (assetId && !imported.has(assetId)) imported.set(assetId, { id: assetId, name: o.name || assetId, type: o.renderer === CUTOUT_KIND ? "image" : "mesh" });
15
+ }
16
+ return [...catalogue, ...imported.values()];
17
+ }
@@ -5,7 +5,7 @@
5
5
  const CONTACT_SHEET_COLUMNS = 3;
6
6
  const CONTACT_SHEET_ROWS = 2;
7
7
  export const CONTACT_SHEET_LAYOUT = "3x2 row-major";
8
- const CONTACT_SHEET_MAX_SIDE = 1600;
8
+ const CONTACT_SHEET_MAX_SIDE = 1600, VIEW_SHEET_MAX_HEIGHT = 900;
9
9
 
10
10
  /** Up to 6 frames spread evenly over a half-open range, first and last
11
11
  * included; "whole_clip" (or no range) is timeline frames 0..frameCount-1. */
@@ -53,21 +53,53 @@ export function tileRgba(tiles, tileWidth, tileHeight, { columns = CONTACT_SHEET
53
53
  return { data, width, height };
54
54
  }
55
55
 
56
+ /** The grid of a sheet: 3x2 row-major for up to 6 frames, 4x2 for 7-8. Views of its own
57
+ * (viewCount >= 1) take a column per frame and a row per view when there are several views
58
+ * or at most 3 frames. viewCount 0 is the shot camera. */
59
+ export function sheetGrid(frameCount, viewCount = 0) {
60
+ if (viewCount > 1 || (viewCount === 1 && frameCount <= 3)) return { columns: frameCount, rows: viewCount, layout: `${frameCount}x${viewCount} row-major, a row per view` };
61
+ return frameCount <= CONTACT_SHEET_COLUMNS * CONTACT_SHEET_ROWS ? { columns: CONTACT_SHEET_COLUMNS, rows: CONTACT_SHEET_ROWS, layout: CONTACT_SHEET_LAYOUT } : { columns: 4, rows: 2, layout: "4x2 row-major" };
62
+ }
63
+
64
+ /** Square tile side for a grid of own-camera views: the sheet stays within
65
+ * maxSide wide and VIEW_SHEET_MAX_HEIGHT high (1600x900 at most). */
66
+ export const viewTileSide = ({ columns, rows }, maxSide = CONTACT_SHEET_MAX_SIDE) => Math.max(1, Math.floor(Math.min(maxSide / columns, VIEW_SHEET_MAX_HEIGHT / rows)));
67
+
68
+ /** Paint dots on a bottom-up RGBA buffer: { x, y (from the bottom row), r, color [r,g,b],
69
+ * alpha 0..1, ring } with ring drawing a 1 px white circle around the dot at r + 2. */
70
+ export function paintDots(data, width, height, dots) {
71
+ const put = (x, y, [r, g, b], a) => {
72
+ if (x < 0 || y < 0 || x >= width || y >= height) return;
73
+ const i = (y * width + x) * 4;
74
+ data[i] = Math.round(data[i] + (r - data[i]) * a); data[i + 1] = Math.round(data[i + 1] + (g - data[i + 1]) * a); data[i + 2] = Math.round(data[i + 2] + (b - data[i + 2]) * a);
75
+ };
76
+ for (const { x, y, r, color, alpha = 1, ring = false } of dots) {
77
+ const cx = Math.round(x), cy = Math.round(y), reach = Math.ceil(r) + (ring ? 3 : 0);
78
+ for (let dy = -reach; dy <= reach; dy++) for (let dx = -reach; dx <= reach; dx++) {
79
+ const d = Math.hypot(dx, dy);
80
+ if (d <= r) put(cx + dx, cy + dy, color, alpha);
81
+ else if (ring && Math.abs(d - r - 2) <= 0.5) put(cx + dx, cy + dy, [255, 255, 255], 1);
82
+ }
83
+ }
84
+ return data;
85
+ }
86
+
56
87
  /** Render each frame (render(frame) -> { data, width, height }, rows bottom-up),
57
88
  * shrink it so the whole sheet's longest side is at most maxSide, and tile
58
- * the frames 3x2 row-major into one bottom-up buffer. */
59
- export function buildContactSheet(frames, render, { maxSide = CONTACT_SHEET_MAX_SIDE } = {}) {
89
+ * the frames row-major (3x2 unless columns and rows say) into one bottom-up buffer.
90
+ * A frame may be any cell the render understands (a frame and a view). */
91
+ export function buildContactSheet(frames, render, { maxSide = CONTACT_SHEET_MAX_SIDE, columns = CONTACT_SHEET_COLUMNS, rows = CONTACT_SHEET_ROWS } = {}) {
60
92
  const tiles = [];
61
93
  let tileWidth, tileHeight;
62
94
  for (const frame of frames) {
63
95
  const shot = render(frame);
64
96
  if (tileWidth === undefined) {
65
- const scale = Math.min(1, maxSide / Math.max(shot.width * CONTACT_SHEET_COLUMNS, shot.height * CONTACT_SHEET_ROWS));
97
+ const scale = Math.min(1, maxSide / Math.max(shot.width * columns, shot.height * rows));
66
98
  tileWidth = Math.max(1, Math.floor(shot.width * scale));
67
99
  tileHeight = Math.max(1, Math.floor(shot.height * scale));
68
100
  }
69
101
  // The renderer reuses one read-back buffer: shrink this frame before the next render.
70
102
  tiles.push(boxDownscaleRgba(shot.data, shot.width, shot.height, tileWidth, tileHeight));
71
103
  }
72
- return tileRgba(tiles, tileWidth, tileHeight, { bottomUp: true });
104
+ return tileRgba(tiles, tileWidth, tileHeight, { columns, rows, bottomUp: true });
73
105
  }