cozyclay 2.1.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (583) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +12 -30
  3. package/bin/agent/agent-routes.mjs +40 -113
  4. package/bin/agent/agent-runner.mjs +9 -10
  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/studio-motion-reference.mjs +23 -0
  14. package/bin/agent/studio-prompt.mjs +1 -1
  15. package/bin/agent/studio-rebase-text.mjs +32 -0
  16. package/bin/agent/studio-rebase.mjs +26 -39
  17. package/bin/agent/studio-tools.mjs +9 -28
  18. package/bin/agent/tool-arguments.mjs +49 -0
  19. package/bin/agent/turn-frames.mjs +66 -0
  20. package/bin/codex-auth.mjs +9 -2
  21. package/bin/cozyclay.mjs +10 -116
  22. package/bin/live/cli.mjs +22 -6
  23. package/bin/mcp-runtime.mjs +2 -12
  24. package/bin/package-signature.mjs +0 -4
  25. package/dist/app/index.html +7 -6
  26. package/dist/assets/EXRLoader-CvVIvGVE.js +1 -0
  27. package/dist/assets/analytics-C9sUnndd.js +1 -0
  28. package/dist/assets/app-CCh--1Hc.js +5087 -0
  29. package/dist/assets/app-f-fkV5uP.css +1 -0
  30. package/dist/assets/compile-worker-CkxiD6lH.js +5 -0
  31. package/dist/assets/fflate.module-KbGj5-H6.js +5 -0
  32. package/dist/assets/{first-shot-handoff-BlT33CGf.js → first-shot-handoff-CC4xe4HM.js} +1 -1
  33. package/dist/assets/{landing-BSesrCpL.js → landing-DjHD8u7k.js} +1 -1
  34. package/dist/assets/scene-asset-sync-Hx_FzdOH.js +20 -0
  35. package/dist/assets/scene-asset-sync-_S8NQl5B.css +1 -0
  36. package/dist/assets/workflow-C4yKH1p4.js +179 -0
  37. package/dist/cozyclay-package.json +1 -1
  38. package/dist/index.html +3 -3
  39. package/dist/llms.txt +14 -0
  40. package/dist/sw.js +3 -6
  41. package/dist/workflow/index.html +5 -4
  42. package/mcp/LIVE-PROTOCOL.md +7 -47
  43. package/mcp/README.md +16 -26
  44. package/mcp/live-hub.mjs +6 -10
  45. package/mcp/package.json +1 -3
  46. package/mcp/tool-handlers.mjs +4 -168
  47. package/package.json +3 -22
  48. package/src/App.jsx +174 -2639
  49. package/src/agent-progress.js +79 -0
  50. package/src/agent-turn-mark.js +30 -0
  51. package/src/analytics.js +1 -19
  52. package/src/app-context.js +5 -0
  53. package/src/app-stage.jsx +2 -34
  54. package/src/asset-pane.jsx +6 -5
  55. package/src/camera-block.js +21 -0
  56. package/src/camera-direction.js +120 -0
  57. package/src/character-kind.js +3 -9
  58. package/src/command-bus.js +8 -6
  59. package/src/commands/cast.js +5 -34
  60. package/src/commands/elements/character.js +1 -1
  61. package/src/commands/elements/motion.js +1 -6
  62. package/src/commands/elements/object.js +6 -2
  63. package/src/commands/elements/shot.js +4 -1
  64. package/src/commands/elements.js +38 -14
  65. package/src/commands/index.js +3 -1
  66. package/src/commands/kits.js +37 -0
  67. package/src/commands/motion-timing.js +57 -0
  68. package/src/commands/motion.js +9 -152
  69. package/src/commands/motion3-compile.js +65 -0
  70. package/src/commands/motion3.js +207 -0
  71. package/src/commands/objects.js +6 -4
  72. package/src/commands/shot.js +49 -11
  73. package/src/commands/stage.js +14 -1
  74. package/src/commands/view.js +4 -1
  75. package/src/document-mirrors.js +42 -0
  76. package/src/document-store.js +4 -0
  77. package/src/domains/cast.js +19 -194
  78. package/src/domains/motion/motion-range-pin-rules.js +1 -1
  79. package/src/domains/motion/take-source.js +51 -0
  80. package/src/domains/motion.js +60 -1838
  81. package/src/domains/scenes.js +12 -55
  82. package/src/domains/shots.js +2 -2
  83. package/src/domains/stage.js +4 -2
  84. package/src/draft-ghost.js +46 -0
  85. package/src/draft-ghost.jsx +129 -0
  86. package/src/dualview.jsx +4 -3
  87. package/src/effects/EffectsLayer.jsx +112 -0
  88. package/src/effects/effect-meshes.js +232 -0
  89. package/src/effects/effect-record.js +103 -0
  90. package/src/effects/emitter.js +59 -0
  91. package/src/effects/simulate.js +261 -0
  92. package/src/effects/state.js +59 -0
  93. package/src/execution-telemetry.js +2 -3
  94. package/src/first-success-guide.jsx +5 -5
  95. package/src/ghost-set.js +257 -0
  96. package/src/held-object-edit.jsx +113 -0
  97. package/src/held-object-pose.js +175 -0
  98. package/src/hierarchy-model.js +1 -1
  99. package/src/hierarchy-panel.jsx +1 -1
  100. package/src/imported-mesh.jsx +103 -0
  101. package/src/kits/held.js +84 -0
  102. package/src/kits/index.js +70 -0
  103. package/src/kits/smoking.js +57 -0
  104. package/src/light-blockers.js +93 -0
  105. package/src/motion/beat-board-model.js +84 -0
  106. package/src/motion/beat-board.css +124 -0
  107. package/src/motion/beat-board.jsx +59 -0
  108. package/src/motion/center-of-mass.js +66 -0
  109. package/src/motion/fix-collisions.js +850 -0
  110. package/src/motion/physics-review.js +118 -0
  111. package/src/{ardy → motion}/physics-support.js +1 -76
  112. package/src/motion/rig-snapshot.js +22 -0
  113. package/src/motion/take-segments.jsx +140 -0
  114. package/src/{ardy → motion}/timeline-coordinates.js +0 -21
  115. package/src/{ardy → motion}/timeline.css +1 -2
  116. package/src/{ardy → motion}/timeline.jsx +48 -419
  117. package/src/motion/waypoints.js +76 -0
  118. package/src/motion3/beats.js +83 -0
  119. package/src/motion3/body.js +158 -0
  120. package/src/motion3/bones.js +47 -0
  121. package/src/motion3/carried.js +47 -0
  122. package/src/motion3/compile-job.js +75 -0
  123. package/src/motion3/compile-worker.js +10 -0
  124. package/src/motion3/compiler/anchors.js +105 -0
  125. package/src/motion3/compiler/arm-orient.js +128 -0
  126. package/src/motion3/compiler/channels.js +125 -0
  127. package/src/motion3/compiler/compile.js +205 -0
  128. package/src/motion3/compiler/gait-rhythm.js +97 -0
  129. package/src/motion3/compiler/grip.js +171 -0
  130. package/src/motion3/compiler/head-steady.js +48 -0
  131. package/src/motion3/compiler/hold.js +134 -0
  132. package/src/motion3/compiler/idle.js +113 -0
  133. package/src/motion3/compiler/limbs.js +170 -0
  134. package/src/motion3/compiler/math.js +72 -0
  135. package/src/motion3/compiler/pose.js +162 -0
  136. package/src/motion3/compiler/quat.js +49 -0
  137. package/src/motion3/compiler/skeleton.js +74 -0
  138. package/src/motion3/compiler/validate-common.js +32 -0
  139. package/src/motion3/compiler/validate-goals.js +118 -0
  140. package/src/motion3/compiler/validate-objects.js +143 -0
  141. package/src/motion3/compiler/validate-parts.js +116 -0
  142. package/src/motion3/compiler/validate.js +122 -0
  143. package/src/motion3/compiler/vocab.js +73 -0
  144. package/src/motion3/compiler/walk-pelvis.js +275 -0
  145. package/src/motion3/compiler/walk.js +207 -0
  146. package/src/motion3/compiler/wrist-clamp.js +32 -0
  147. package/src/motion3/delta.js +47 -0
  148. package/src/motion3/describe.js +174 -0
  149. package/src/motion3/docs.js +27 -0
  150. package/src/motion3/edit-carry.js +21 -0
  151. package/src/motion3/fix/ease.js +16 -0
  152. package/src/motion3/fix/ground.js +37 -0
  153. package/src/motion3/fix/index.js +52 -0
  154. package/src/motion3/fix/push.js +71 -0
  155. package/src/motion3/fix/reach.js +46 -0
  156. package/src/motion3/fix/retime.js +79 -0
  157. package/src/motion3/fix/self.js +211 -0
  158. package/src/motion3/fix/slide.js +70 -0
  159. package/src/motion3/fix/turn.js +51 -0
  160. package/src/motion3/follow.js +39 -0
  161. package/src/motion3/free-motion.js +173 -0
  162. package/src/motion3/grip-choice.js +130 -0
  163. package/src/motion3/hold-plan.js +157 -0
  164. package/src/motion3/json-patch.js +77 -0
  165. package/src/motion3/key-aim.js +105 -0
  166. package/src/motion3/key-edit.js +82 -0
  167. package/src/motion3/lenient.js +127 -0
  168. package/src/motion3/object-keys.js +236 -0
  169. package/src/motion3/object-pose.js +182 -0
  170. package/src/motion3/object-tracks.js +12 -0
  171. package/src/motion3/pipeline.js +102 -0
  172. package/src/motion3/playback.js +51 -0
  173. package/src/motion3/probes.js +91 -0
  174. package/src/motion3/qa/body-checks.js +34 -0
  175. package/src/motion3/qa/check-names.js +14 -0
  176. package/src/motion3/qa/checks.js +121 -0
  177. package/src/motion3/qa/contact-checks.js +51 -0
  178. package/src/motion3/qa/explain.js +80 -0
  179. package/src/motion3/qa/fmt.js +5 -0
  180. package/src/motion3/qa/foot-checks.js +72 -0
  181. package/src/motion3/qa/geometry.js +77 -0
  182. package/src/motion3/qa/goals.js +191 -0
  183. package/src/motion3/qa/ground.js +60 -0
  184. package/src/motion3/qa/hints-contact.js +179 -0
  185. package/src/motion3/qa/hints-goal.js +160 -0
  186. package/src/motion3/qa/hints-knee.js +24 -0
  187. package/src/motion3/qa/hints-motion.js +209 -0
  188. package/src/motion3/qa/hints-object.js +91 -0
  189. package/src/motion3/qa/index.js +42 -0
  190. package/src/motion3/qa/key-checks.js +82 -0
  191. package/src/motion3/qa/near.js +96 -0
  192. package/src/motion3/qa/object-checks.js +135 -0
  193. package/src/motion3/qa/patch.js +161 -0
  194. package/src/motion3/qa/severity.js +38 -0
  195. package/src/motion3/qa/spec-checks.js +65 -0
  196. package/src/motion3/qa/style.js +218 -0
  197. package/src/motion3/qa/wrist-checks.js +69 -0
  198. package/src/motion3/qa/wrist-obs.js +60 -0
  199. package/src/motion3/refs.js +120 -0
  200. package/src/motion3/repair-tune.js +121 -0
  201. package/src/motion3/repair.js +83 -0
  202. package/src/motion3/report.js +67 -0
  203. package/src/motion3/rigs/index.js +14 -0
  204. package/src/motion3/rigs/x-bot-tpose-body.json +1 -0
  205. package/src/motion3/rigs/x-bot-tpose.json +1 -0
  206. package/src/motion3/rigs/y-bot-tpose-body.json +1 -0
  207. package/src/motion3/rigs/y-bot-tpose.json +1 -0
  208. package/src/motion3/scene.js +60 -0
  209. package/src/motion3/schema/brief.js +89 -0
  210. package/src/motion3/schema/fields.js +44 -0
  211. package/src/motion3/schema/spec-examples.js +46 -0
  212. package/src/motion3/schema/topics.js +111 -0
  213. package/src/motion3/schema/types.js +147 -0
  214. package/src/motion3/search.js +117 -0
  215. package/src/motion3/second-hand.js +112 -0
  216. package/src/motion3/skills/basic.js +82 -0
  217. package/src/motion3/skills/book.js +76 -0
  218. package/src/motion3/skills/car.js +145 -0
  219. package/src/motion3/skills/frames-auto.js +29 -0
  220. package/src/motion3/skills/index.js +83 -0
  221. package/src/motion3/skills/info.js +65 -0
  222. package/src/motion3/skills/smoke-kit.js +109 -0
  223. package/src/motion3/skills/smoke.js +160 -0
  224. package/src/motion3/spec-store.js +59 -0
  225. package/src/motion3/take-edit.js +155 -0
  226. package/src/motion3/take.js +63 -0
  227. package/src/motion3/timeline.js +127 -0
  228. package/src/motion3/views.js +121 -0
  229. package/src/motion3/walk-plan.js +119 -0
  230. package/src/object-catalog.jsx +7 -1
  231. package/src/object-colors.js +98 -0
  232. package/src/object-gizmo-parts.js +115 -0
  233. package/src/object-gizmo.jsx +37 -122
  234. package/src/object-glow.js +139 -0
  235. package/src/object-hidden.js +33 -0
  236. package/src/object-look.js +196 -0
  237. package/src/object-size.js +10 -0
  238. package/src/object-travel.js +71 -6
  239. package/src/panels/CameraPanel.jsx +7 -7
  240. package/src/panels/HdrEnvironmentControls.jsx +59 -0
  241. package/src/panels/LightPanel.jsx +25 -4
  242. package/src/panels/ObjectLookPanel.jsx +88 -0
  243. package/src/panels/PoseImagePanel.jsx +1 -1
  244. package/src/panels/PosePanel.jsx +2 -17
  245. package/src/panels/RigControlPanel.jsx +5 -53
  246. package/src/panels/SubjectBox.jsx +4 -1
  247. package/src/panels/details.css +14 -0
  248. package/src/posestudio.jsx +4 -27
  249. package/src/project-browser.jsx +3 -4
  250. package/src/project-resources.js +18 -31
  251. package/src/project.js +13 -104
  252. package/src/props.jsx +25 -280
  253. package/src/rail-dot-drag.js +23 -0
  254. package/src/render-passes.js +10 -0
  255. package/src/resource-status.jsx +1 -0
  256. package/src/result-modal.jsx +4 -19
  257. package/src/room.jsx +20 -12
  258. package/src/scene-assets.js +8 -6
  259. package/src/scene-light-place.js +72 -0
  260. package/src/scene-lights.jsx +148 -0
  261. package/src/scene-objects.js +31 -139
  262. package/src/scenes.js +15 -105
  263. package/src/semantic-edit.js +2 -5
  264. package/src/set-pieces.jsx +183 -0
  265. package/src/settings-menu.jsx +3 -11
  266. package/src/shell/BottomDock.jsx +11 -53
  267. package/src/shell/CompositionGuideButton.jsx +20 -0
  268. package/src/shell/DetailsSlot.jsx +16 -200
  269. package/src/shell/GhostsMenuGroup.jsx +74 -0
  270. package/src/shell/LibrarySlot.jsx +1 -3
  271. package/src/shell/MenuBar.jsx +3 -3
  272. package/src/shell/PreferencesDialog.jsx +15 -264
  273. package/src/shell/PreferencesSlot.jsx +2 -8
  274. package/src/shell/ShotPreviewHeader.jsx +17 -0
  275. package/src/shell/TopBar.jsx +5 -83
  276. package/src/shell/ViewportToolbar.jsx +23 -93
  277. package/src/shell/agent-glass.css +19 -6
  278. package/src/shell/details-selection.js +42 -0
  279. package/src/shell/ghosts-menu.css +22 -0
  280. package/src/shell/glass-regions.css +29 -3
  281. package/src/shell/preferences.css +6 -1
  282. package/src/shell/viewport.css +12 -0
  283. package/src/shot-authoring.js +4 -7
  284. package/src/soft-shadows.js +37 -0
  285. package/src/stage-attach-frames.js +12 -12
  286. package/src/stage-canvas-rigs.jsx +3 -3
  287. package/src/stage-capture.jsx +42 -8
  288. package/src/stage-characters.jsx +22 -216
  289. package/src/stage-editor-gizmos.jsx +6 -6
  290. package/src/stage-environment.js +93 -0
  291. package/src/stage-hdr-builtins.js +107 -0
  292. package/src/stage-hdr-image.js +116 -0
  293. package/src/stage-hdr-library.js +70 -0
  294. package/src/stage-hdr-map.js +94 -0
  295. package/src/stage-hdr.js +130 -0
  296. package/src/stage-lighting.js +149 -0
  297. package/src/stage-occlusion.js +210 -0
  298. package/src/stage-sky.jsx +159 -0
  299. package/src/stage-startup-defaults.js +5 -9
  300. package/src/studio-actions.js +12 -14
  301. package/src/studio-agent-commands.js +46 -16
  302. package/src/studio-agent-motion.js +9 -9
  303. package/src/studio-agent-protocol.js +46 -73
  304. package/src/studio-app-binding.js +29 -45
  305. package/src/studio-asset-list.js +17 -0
  306. package/src/studio-contact-sheet.js +37 -5
  307. package/src/studio-elements.js +27 -7
  308. package/src/studio-entity-bounds.js +18 -0
  309. package/src/studio-error-codes.js +8 -0
  310. package/src/studio-export-framing.js +152 -0
  311. package/src/studio-lighting-check.js +103 -0
  312. package/src/studio-motion-visual.js +150 -0
  313. package/src/studio-schema-errors.js +80 -0
  314. package/src/studio-support-gap.js +74 -0
  315. package/src/styles.css +1 -19
  316. package/src/timeline-defaults.js +5 -0
  317. package/src/timeline-extent.js +8 -20
  318. package/src/tutorial-walk.js +17 -0
  319. package/src/ui-scrub.js +1 -1
  320. package/src/use-shot-camera-tracks.js +12 -0
  321. package/src/view-ghosts.js +97 -0
  322. package/src/view-ghosts.jsx +49 -0
  323. package/src/workflow/AgentPanel.jsx +47 -166
  324. package/src/workflow/AgentProgressCard.jsx +107 -0
  325. package/src/workflow/AgentReplyText.jsx +37 -0
  326. package/src/workflow/AgentTurnSteps.jsx +84 -0
  327. package/src/workflow/LoginPanel.jsx +140 -0
  328. package/src/workflow/WorkflowBuilder.jsx +7 -23
  329. package/src/workflow/agent-client.js +54 -51
  330. package/src/workflow/agent-panel.css +71 -1
  331. package/src/workflow/agent-progress-card.css +167 -0
  332. package/src/workflow/agent-progress-view.js +47 -0
  333. package/src/workflow/agent-turn-steps.css +113 -0
  334. package/src/workflow/agent-turn.js +191 -0
  335. package/src/workflow/login-client.js +34 -0
  336. package/src/workflow/motion-input.js +18 -31
  337. package/src/workflow/node-schema.js +1 -1
  338. package/src/workflow/reply-markdown.js +69 -0
  339. package/src/workflow/scene-asset-sync.js +8 -8
  340. package/tools/agent-session-stats.mjs +61 -0
  341. package/tools/dev-full.mjs +5 -56
  342. package/tools/eslint-baseline.json +12 -13
  343. package/tools/headless-studio.mjs +85 -0
  344. package/tools/knip-baseline.json +0 -179
  345. package/tools/loc-baseline.json +33 -83
  346. package/tools/motion3/extract-body.mjs +103 -0
  347. package/tools/motion3/extract-rig.mjs +85 -0
  348. package/tools/process-supervisor.mjs +0 -88
  349. package/tools/run-tests.mjs +9 -124
  350. package/bin/agent/motion-runtime.mjs +0 -76
  351. package/dist/ardy/cskel27-rest.json +0 -557
  352. package/dist/assets/analytics-B41FmPYI.js +0 -1
  353. package/dist/assets/app-Do-Og5Pu.css +0 -1
  354. package/dist/assets/app-DwP-tl2x.js +0 -4920
  355. package/dist/assets/config-Bx7pYsD9.js +0 -1
  356. package/dist/assets/config-DeoFTYzb.css +0 -1
  357. package/dist/assets/demo-Dt0qZfS7.js +0 -1
  358. package/dist/assets/scene-asset-sync-BCWDJB1d.js +0 -19
  359. package/dist/assets/scene-asset-sync-DjJ_cJ2R.css +0 -1
  360. package/dist/assets/ticket-jqMhqL88.js +0 -1
  361. package/dist/assets/workflow-Br-K7Srs.js +0 -179
  362. package/dist/demo/walk-then-stop.npz +0 -0
  363. package/mcp/ardy-prompts.mjs +0 -218
  364. package/src/ardy/auto-fix-panel.css +0 -108
  365. package/src/ardy/auto-physics.js +0 -1194
  366. package/src/ardy/client.js +0 -153
  367. package/src/ardy/convert.js +0 -313
  368. package/src/ardy/cskel27-neutral.js +0 -39
  369. package/src/ardy/cskel27.js +0 -77
  370. package/src/ardy/export.js +0 -165
  371. package/src/ardy/fix-collisions.js +0 -2257
  372. package/src/ardy/foot-lock.js +0 -230
  373. package/src/ardy/motion-calibration.js +0 -118
  374. package/src/ardy/motion-edit.js +0 -213
  375. package/src/ardy/motion-url.js +0 -55
  376. package/src/ardy/mp4-duration.js +0 -102
  377. package/src/ardy/npz.js +0 -595
  378. package/src/ardy/physics-panel.css +0 -21
  379. package/src/ardy/physics-panel.jsx +0 -89
  380. package/src/ardy/physics-refine.js +0 -111
  381. package/src/ardy/physics-review.js +0 -567
  382. package/src/ardy/physics-temporal.js +0 -54
  383. package/src/ardy/platform-fit-panel.css +0 -64
  384. package/src/ardy/platform-fit-panel.jsx +0 -41
  385. package/src/ardy/platform-fit.js +0 -326
  386. package/src/ardy/playback.js +0 -711
  387. package/src/ardy/pose-pin.js +0 -97
  388. package/src/ardy/prompt-clips.js +0 -30
  389. package/src/ardy/retime.js +0 -211
  390. package/src/ardy/root-drop.js +0 -369
  391. package/src/ardy/timeline-resize.js +0 -13
  392. package/src/ardy/to-cskel27.js +0 -225
  393. package/src/ardy/trim.js +0 -33
  394. package/src/ardy/waypoints.js +0 -369
  395. package/src/ardy-bridge-contract.js +0 -281
  396. package/src/domains/motion/motion-video-capture.js +0 -664
  397. package/src/i2v-motion-client.js +0 -77
  398. package/src/i2v-motion-studio.jsx +0 -180
  399. package/src/line-edit.js +0 -1348
  400. package/src/motion/generation.js +0 -105
  401. package/src/motion-readiness-ui.jsx +0 -67
  402. package/src/motion-readiness.js +0 -40
  403. package/src/motion-resources.js +0 -177
  404. package/src/motion-store.js +0 -65
  405. package/src/motion-trail.js +0 -601
  406. package/src/multimodel-ingest.js +0 -394
  407. package/src/panels/PromptBlocksPanel.jsx +0 -444
  408. package/src/panels/VideoCapturePanel.jsx +0 -181
  409. package/src/pose-extract/browser.js +0 -42
  410. package/src/pose-extract/detector.js +0 -100
  411. package/src/pose-extract/fit.js +0 -227
  412. package/src/pose-extract/image-frame.js +0 -51
  413. package/src/pose-extract/index.js +0 -58
  414. package/src/pose-extract/keyframes.js +0 -74
  415. package/src/pose-extract/landmarks.js +0 -162
  416. package/src/pose-extract/mirror.js +0 -153
  417. package/src/pose-extract/take.js +0 -131
  418. package/src/pose-extract/video-frames.js +0 -91
  419. package/src/take-recipe.js +0 -165
  420. package/src/trail-key-conflicts.js +0 -39
  421. package/src/trail-pick.js +0 -59
  422. package/src/use-i2v-capture-modal.js +0 -15
  423. package/tools/ardy/BRIDGE.md +0 -37
  424. package/tools/ardy/GVHMR-NPZ.md +0 -68
  425. package/tools/ardy/README.md +0 -124
  426. package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
  427. package/tools/ardy/artifacts.mjs +0 -34
  428. package/tools/ardy/bridge.mjs +0 -1732
  429. package/tools/ardy/bvh-cskel27.mjs +0 -1265
  430. package/tools/ardy/cclay_constrained_generate.py +0 -561
  431. package/tools/ardy/cclay_gvhmr_worker.py +0 -264
  432. package/tools/ardy/cclay_motion_edit.py +0 -510
  433. package/tools/ardy/cclay_sequence_generate.py +0 -705
  434. package/tools/ardy/cclay_worker.py +0 -304
  435. package/tools/ardy/dump-npz.py +0 -205
  436. package/tools/ardy/extract-rest.mjs +0 -299
  437. package/tools/ardy/extract.mjs +0 -507
  438. package/tools/ardy/footage.mjs +0 -467
  439. package/tools/ardy/gvhmr-floor.mjs +0 -102
  440. package/tools/ardy/gvhmr-to-cskel27.mjs +0 -21
  441. package/tools/ardy/gvhmr_fastpath.py +0 -168
  442. package/tools/ardy/gvhmr_trajectory.py +0 -240
  443. package/tools/ardy/merge-text-encoder.py +0 -160
  444. package/tools/ardy/mocap-metrics.mjs +0 -200
  445. package/tools/ardy/mocap-quality-gate.mjs +0 -110
  446. package/tools/ardy/motion-stabilize.mjs +0 -348
  447. package/tools/ardy/npz.mjs +0 -414
  448. package/tools/ardy/pose-to-npz.mjs +0 -106
  449. package/tools/ardy/prompt-limits.mjs +0 -5
  450. package/tools/ardy/run-edit-on-box.sh +0 -73
  451. package/tools/ardy/run-local.mjs +0 -233
  452. package/tools/ardy/run-on-box.sh +0 -593
  453. package/tools/ardy/run-sequence-on-box.sh +0 -177
  454. package/tools/ardy/runners/gvhmr-worker.mjs +0 -206
  455. package/tools/ardy/runners/index.mjs +0 -18
  456. package/tools/ardy/runners/local.mjs +0 -455
  457. package/tools/ardy/runners/proc.mjs +0 -125
  458. package/tools/ardy/runners/remote.mjs +0 -252
  459. package/tools/ardy/setup-local.mjs +0 -189
  460. package/tools/ardy/setup-text-encoder-on-box.sh +0 -93
  461. package/tools/ardy/setup-text-encoder.py +0 -306
  462. package/tools/ardy/smpl-cskel27.mjs +0 -178
  463. package/tools/ardy/visual-qa.mjs +0 -196
  464. package/tools/ardy/vq-car.mjs +0 -52
  465. package/tools/bench/EXP3.md +0 -61
  466. package/tools/bench/cclay_bench_extract_incam.py +0 -125
  467. package/tools/bench/cclay_bench_extract_obs.py +0 -204
  468. package/tools/bench/cclay_bench_runner.py +0 -42
  469. package/tools/bench/cube-contact.mjs +0 -162
  470. package/tools/bench/exp3.mjs +0 -152
  471. package/tools/bench/extract-bench-lib.mjs +0 -216
  472. package/tools/bench/extract-bench.mjs +0 -302
  473. package/tools/bench/fal-generate.mjs +0 -88
  474. package/tools/bench/fit/README.md +0 -189
  475. package/tools/bench/fit/camera.mjs +0 -59
  476. package/tools/bench/fit/contact.mjs +0 -419
  477. package/tools/bench/fit/footlock.mjs +0 -234
  478. package/tools/bench/fit/motion.mjs +0 -85
  479. package/tools/bench/fit/pin.mjs +0 -27
  480. package/tools/bench/fit/remote.mjs +0 -105
  481. package/tools/bench/fit-bench.mjs +0 -126
  482. package/tools/bench/fit-sanity.mjs +0 -45
  483. package/tools/bench/metrics.mjs +0 -313
  484. package/tools/bench/obs/depth.mjs +0 -258
  485. package/tools/bench/obs/extrinsics.mjs +0 -175
  486. package/tools/bench/obs/fit_mannequin_betas.py +0 -340
  487. package/tools/bench/obs/ground.mjs +0 -294
  488. package/tools/bench/obs/heading.mjs +0 -151
  489. package/tools/bench/obs/ladder.mjs +0 -555
  490. package/tools/bench/obs/mannequin-betas.json +0 -124
  491. package/tools/bench/obs/remote.mjs +0 -83
  492. package/tools/bench/obs/rest_joints.py +0 -52
  493. package/tools/bench/obs/ybot-targets.mjs +0 -49
  494. package/tools/bench/obs-bench.mjs +0 -520
  495. package/tools/bench/score.mjs +0 -343
  496. package/tools/bench/summarize.mjs +0 -86
  497. package/tools/demo-worker/README.md +0 -137
  498. package/tools/demo-worker/api-client.mjs +0 -233
  499. package/tools/demo-worker/generate.mjs +0 -301
  500. package/tools/demo-worker/index.mjs +0 -466
  501. package/tools/demo-worker/sign-core.mjs +0 -112
  502. package/tools/demo-worker/sign.mjs +0 -161
  503. package/tools/gt-render/browser.mjs +0 -159
  504. package/tools/gt-render/camera-math.mjs +0 -276
  505. package/tools/gt-render/page.mjs +0 -267
  506. package/tools/gt-render/render.mjs +0 -514
  507. package/tools/gt-render/scene-box.mjs +0 -31
  508. package/tools/gt-render/take-transform.mjs +0 -87
  509. package/tools/kimodo/compare-seam.mjs +0 -108
  510. package/tools/kimodo/constraints.mjs +0 -244
  511. package/tools/kimodo/edit.mjs +0 -259
  512. package/tools/kimodo/effector-constraints.mjs +0 -200
  513. package/tools/kimodo/generate.mjs +0 -646
  514. package/tools/kimodo/local-output.mjs +0 -265
  515. package/tools/kimodo/measure-pose.mjs +0 -65
  516. package/tools/kimodo/measure-preserve.mjs +0 -899
  517. package/tools/kimodo/measure-waypoints.mjs +0 -55
  518. package/tools/kimodo/mlx-generate.py +0 -111
  519. package/tools/kimodo/pose-constraints.mjs +0 -248
  520. package/tools/kimodo/preserve-mask.mjs +0 -543
  521. package/tools/kimodo/read-npz.mjs +0 -189
  522. package/tools/kimodo/run-edit-on-box.mjs +0 -155
  523. package/tools/kimodo/run-sequence-on-box.mjs +0 -178
  524. package/tools/kimodo/runner.mjs +0 -284
  525. package/tools/kimodo/seam-batch.mjs +0 -77
  526. package/tools/kimodo/setup-local.mjs +0 -30
  527. package/tools/kimodo/setup-on-box.sh +0 -339
  528. package/tools/kimodo/soma77-to-cskel27.mjs +0 -305
  529. package/tools/morphgs/assets/gen_truth.py +0 -39
  530. package/tools/morphgs/assets/mesh_ori_rig.txt +0 -27
  531. package/tools/morphgs/assets/playback-check.mjs +0 -30
  532. package/tools/morphgs/demo-gate.sh +0 -26
  533. package/tools/morphgs/fbx2morphgs.mjs +0 -68
  534. package/tools/morphgs/morphgs-to-cskel27.mjs +0 -105
  535. package/tools/morphgs/patches/preprocess_src-none-mode.patch +0 -76
  536. package/tools/morphgs/setup-on-cluster.sh +0 -126
  537. package/tools/projflow/driver.py +0 -1069
  538. package/tools/projflow/generate.mjs +0 -805
  539. package/tools/projflow/hml22-to-cskel27.mjs +0 -735
  540. package/tools/projflow/line-edit-job.mjs +0 -547
  541. package/tools/projflow/replay.mjs +0 -452
  542. package/tools/projflow/runner.mjs +0 -269
  543. package/tools/projflow/service.mjs +0 -742
  544. package/tools/track/DESIGN.md +0 -513
  545. package/tools/track/backfill-provenance.mjs +0 -116
  546. package/tools/track/budget.mjs +0 -8
  547. package/tools/track/check-rig.mjs +0 -209
  548. package/tools/track/diagnostics.schema.json +0 -60
  549. package/tools/track/export-rig.mjs +0 -256
  550. package/tools/track/fallback.mjs +0 -7
  551. package/tools/track/fk-parity-fixture.mjs +0 -161
  552. package/tools/track/gate.mjs +0 -349
  553. package/tools/track/masks.mjs +0 -318
  554. package/tools/track/metrics.mjs +0 -201
  555. package/tools/track/publish-obs.mjs +0 -108
  556. package/tools/track/py/check_env.py +0 -61
  557. package/tools/track/py/eval_lr.py +0 -229
  558. package/tools/track/py/lr_viterbi.py +0 -294
  559. package/tools/track/py/masks.py +0 -405
  560. package/tools/track/py/objective.py +0 -382
  561. package/tools/track/py/rig.py +0 -508
  562. package/tools/track/py/scene.py +0 -326
  563. package/tools/track/py/test_joint_indices.py +0 -123
  564. package/tools/track/py/test_lr_viterbi.py +0 -195
  565. package/tools/track/py/test_masks.py +0 -229
  566. package/tools/track/py/test_rig.py +0 -288
  567. package/tools/track/py/test_scene.py +0 -354
  568. package/tools/track/py/test_track.py +0 -328
  569. package/tools/track/py/track.py +0 -411
  570. package/tools/track/remote.mjs +0 -140
  571. package/tools/track/rig-dump.mjs +0 -227
  572. package/tools/track/run-box-tests.mjs +0 -14
  573. package/tools/track/run-box.mjs +0 -168
  574. package/tools/track/setup-box.sh +0 -59
  575. package/tools/track/study-2d.mjs +0 -728
  576. /package/src/{ardy → motion}/collision-blockers.js +0 -0
  577. /package/src/{ardy → motion}/ground.js +0 -0
  578. /package/src/{ardy → motion}/ik-drag.js +0 -0
  579. /package/src/{ardy → motion}/ik-key-json.js +0 -0
  580. /package/src/{ardy → motion}/ik.js +0 -0
  581. /package/src/{ardy → motion}/physics-surface.js +0 -0
  582. /package/src/{ardy → motion}/playback-clock.js +0 -0
  583. /package/src/{ardy → motion}/range-pin.js +0 -0
@@ -3,14 +3,14 @@
3
3
  import * as THREE from 'three';
4
4
  import { clone as cloneSkeleton } from 'three/examples/jsm/utils/SkeletonUtils.js';
5
5
  import { StudioProtocolError, freezeStudioData } from './studio-agent-protocol.js';
6
- import { applyMotionFrame } from './ardy/playback.js';
7
- import { resolveIkRig, createIkState, ikEvaluate, findBone, hasBindPose } from './ardy/ik.js';
8
- import { PHYSICS_LIMITS, createSupportSampler, copyPhysicsKeys, supportIntervals, physicsMetrics } from './ardy/physics-review.js';
9
- import { createDynamicsSampler, supportDiagnostics } from './ardy/physics-support.js';
10
- import { computeCenterOfMass } from './ardy/auto-physics.js';
11
- import { buildCollisionCapsules, detectPenetrations, supportsCollisionCleanup } from './ardy/fix-collisions.js';
12
- import { collisionBlockers, blockerSummary } from './ardy/collision-blockers.js';
13
- import { createGroundSampler } from './ardy/ground.js';
6
+ import { applyTakeFrame } from './motion3/playback.js';
7
+ import { resolveIkRig, createIkState, ikEvaluate, findBone, hasBindPose } from './motion/ik.js';
8
+ import { PHYSICS_LIMITS, createSupportSampler, copyPhysicsKeys, supportIntervals, physicsMetrics } from './motion/physics-review.js';
9
+ import { createDynamicsSampler, supportDiagnostics } from './motion/physics-support.js';
10
+ import { computeCenterOfMass } from './motion/center-of-mass.js';
11
+ import { buildCollisionCapsules, detectPenetrations, supportsCollisionCleanup } from './motion/fix-collisions.js';
12
+ import { collisionBlockers, blockerSummary } from './motion/collision-blockers.js';
13
+ import { createGroundSampler } from './motion/ground.js';
14
14
  import { OBJECT_LIBRARY } from './scene-objects.js';
15
15
  import { objectTransformAt } from './object-path.js';
16
16
  import { sampleAt } from './sample-at.js';
@@ -50,7 +50,7 @@ function disposeRig(value) {
50
50
  }
51
51
  function poseFrame(evaluator, motion, layer, frame) {
52
52
  restore(evaluator.rest);
53
- if (motion) applyMotionFrame(evaluator.rig, motion, sampleAt({ frameCount: motion.frames, motion }, null, frame).motionFrame);
53
+ if (motion) applyTakeFrame(evaluator.rig, motion, sampleAt({ frameCount: motion.frames, motion }, null, frame).motionFrame);
54
54
  if (layer) ikEvaluate(evaluator.chains, layer, frame, evaluator.fkJoints, motion ? BLEND : 0);
55
55
  evaluator.parent.updateMatrixWorld(true);
56
56
  }
@@ -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 } 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 };
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
@@ -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.
@@ -277,7 +271,7 @@ const readback = object({}, { position: vec3, yawDeg: number(), rotationDeg: vec
277
271
  const CAMERA_SIDES = ["left", "right", "on-axis"];
278
272
  const checks = object({ coverage: name }, { relationSatisfied: bool, overlapIds: ids(100, 0), actualGapM: number(), requestedGapM: number(0), maximumFootprintOverlapM: number(0),
279
273
  basis: choices(STUDIO_VARIANTS.positionBases), clipped: bool, occluded: bool, behindCamera: bool, screenFraction: number(0), derivedSize: choices(STUDIO_VARIANTS.framingSizes), support: name, baseY: number(), facesTargetId: id,
280
- cameraSide: choices(CAMERA_SIDES), axisConsistent: bool });
274
+ cameraSide: choices(CAMERA_SIDES), axisConsistent: bool, evaluatedFrames: integer(1), sampledFrames: array(integer(), 64, 1), sampledChecks: array(object({ frame: integer(), screenFraction: number(), clipped: bool, behindCamera: bool, occluded: bool }), 64, 1), issueFrames: array(object({ frame: integer(), codes: array(name, 4, 1), screenFraction: number() }), 64, 0) });
281
275
  // Code-computed shot geometry verify_result carries beside a framing check
282
276
  // (src/studio-geometry-facts.js): bounded to 8 shots and 24 subjects.
283
277
  const geometryFacts = object({
@@ -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,35 +491,18 @@ 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.");
511
497
  }
512
498
  if (command.name === "verify_result") {
513
499
  if (Boolean(args.receiptId) === Boolean(args.targets)) fail("INVALID_ARGUMENT", "Exactly one of receiptId or targets is required.");
514
- if (args.checks.includes("motion") && args.range === undefined) args.range = "whole_clip";
515
- }
516
- return { name: command.name, args };
517
- }
518
- export function compileStudioBeats(value) {
519
- const source = validateStudioSchema(generateSource, value);
520
- const totalSeconds = validateGenerationTiming(source);
521
- const frameCount = Math.round(totalSeconds * 24);
522
- const blocks = []; let cumulative = 0, startFrame = 0;
523
- for (const [sourceBeat, beat] of source.beats.entries()) {
524
- cumulative += beat.seconds ?? totalSeconds / source.beats.length;
525
- const end = sourceBeat === source.beats.length - 1 ? frameCount : Math.round(cumulative * 24);
526
- const pieces = Math.ceil((end - startFrame) / 120), start = startFrame;
527
- for (let i = 0; i < pieces; i++) {
528
- const endFrameExclusive = start + Math.round((end - start) * (i + 1) / pieces);
529
- blocks.push({ sourceBeat, text: beat.text.trim().replace(/\s+/g, " "), startFrame, endFrameExclusive }); startFrame = endFrameExclusive;
500
+ if (args.checks.includes("motion") && args.range === undefined) {
501
+ args.range = "whole_clip";
502
+ Object.defineProperty(args, "rangeWasDefaulted", { value: true, enumerable: false });
530
503
  }
531
504
  }
532
- return freezeStudioData({ fps: 24, frameCount, durationSeconds: frameCount / 24, blocks });
505
+ return { name: command.name, args };
533
506
  }
534
507
  export function validateReceipt(value) {
535
508
  if (value?.ok === false) {
@@ -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, studioObjectCatalogue } from "./studio-agent-commands.js";
12
- import { verifyInstalledTake } from "./studio-agent-motion.js";
13
- import { shotGeometryFacts } from "./studio-geometry-facts.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";
14
15
  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";
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" } : {}) },
@@ -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,13 +275,18 @@ 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
  }
286
+ if (args.checks.includes("framing") && args.range !== undefined && !args.rangeWasDefaulted) {
287
+ const sampled = sampledFramingResult(args, scene, ports, s.frameCount, measured, receipt);
288
+ if (sampled.reason) reasons.framing = sampled.reason; else result.checks.framing = sampled;
289
+ }
296
290
  if (args.checks.includes("motion") && !receipt?.verification) {
297
291
  const subjects = args.targets ?? receipt.affectedIds.filter(id => s.characters.some(c => c.id === id));
298
292
  if (!subjects.length) skipped.push("The receipt affected no character.");
@@ -305,17 +299,7 @@ export function createStudioAppBinding(ports) {
305
299
  .then(verification => { verified[index] = verification; }, error => { if (!(error instanceof StudioProtocolError)) throw error; skipped.push(`${name}: ${error.message}`); }));
306
300
  });
307
301
  }
308
- // A frame is the shot camera's view; a plan is the Top View, the set from above
309
- // with names and the shot camera's wedge.
310
- if (args.visual !== "none") {
311
- if (args.visual === "contact_sheet") {
312
- // One image of frames across the range, each rendered through the export
313
- // path, which puts the playhead pose, shot camera and props back.
314
- const frames = sampleContactSheetFrames(args.range, s.frameCount), sheet = buildContactSheet(frames, ports.renderFrameBuffer), imageId = crypto.randomUUID();
315
- 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 });
316
- result.visualRefs.push({ imageId, frames, layout: CONTACT_SHEET_LAYOUT });
317
- } 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" } : {}) }); }
318
- }
302
+ if (args.checks.includes("motion")) exportFramingInto(result, { args, s, receipt, scene, ports }); if (args.visual !== "none") studioVisual(args, { s, receipt, result, images, ports });
319
303
  const finish = () => {
320
304
  const computed = verified.filter(Boolean);
321
305
  if (computed.length) result.verification = computed.length === 1 ? computed[0] : computed;
@@ -343,7 +327,7 @@ export function createStudioAppBinding(ports) {
343
327
  // the model pays for): the revision is what the agent's next command is
344
328
  // admitted at, so a scope without it leaves that admission stale.
345
329
  const head = { revision: c.revision, host: (({ workspaceId, documentEpoch, sceneId, sceneEpoch }) => ({ workspaceId, documentEpoch, sceneId, sceneEpoch }))(c.host) };
346
- if (command.args.scope === "catalogue") return { ...head, ...studioObjectCatalogue() };
330
+ if (command.args.scope === "catalogue") return { ...head, ...studioObjectCatalogue(), kits: KIT_CATALOGUE, environments: hdrEnvironmentCatalogue() };
347
331
  if (command.args.scope === "document") return { ...head, scope: "document",
348
332
  ...readElementDocument(refresh().document, command.args, c.host.sceneId) };
349
333
  // Discovery for run_action: every registered action with its label, kind,
@@ -366,7 +350,8 @@ export function createStudioAppBinding(ports) {
366
350
  // Stable id order, so an offset cursor survives unrelated edits.
367
351
  const filtered = entityProjection(s).filter(wanted).sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
368
352
  const offset = args.cursor ? validateStudioCursor(args.cursor, c) : 0, limit = command.args.limit;
369
- 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,
370
355
  nextCursor: offset + limit < filtered.length ? studioEntityCursor(c, offset + limit) : null };
371
356
  },
372
357
  operate_studio: request => execute({ ...request, name: "operate_studio" }),
@@ -374,7 +359,6 @@ export function createStudioAppBinding(ports) {
374
359
  arrange_characters: request => execute({ ...request, name: "arrange_characters" }),
375
360
  patch_elements: request => execute({ ...request, name: "patch_elements" }),
376
361
  frame_shot: request => execute({ ...request, name: "frame_shot" }),
377
- generate_motion: request => execute({ ...request, name: "generate_motion" }),
378
362
  verify_result: request => execute({ ...request, name: "verify_result" }),
379
363
  undo_edit: request => runAction(request, { action: "edit.undo", args: request.args }),
380
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
  }