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
@@ -0,0 +1,214 @@
1
+ // CozyClay 3 motion: the agent writes a motion spec (keys, anchors, props, fits, generators,
2
+ // checks, goals), the deterministic compiler bakes every frame, QA measures it, and the take is
3
+ // installed on the character. Cheap enough to run in a loop until QA reports nothing. The words
4
+ // the agent reads live in motion3/docs.js; the contracts in docs/motion-api-v3.1.md.
5
+ import { fail, characterOf } from "./shared.js";
6
+ import { motionScene } from "../motion3/scene.js";
7
+ import { skeletonFor } from "../motion3/rigs/index.js";
8
+ import { fk } from "../motion3/compiler/skeleton.js";
9
+ import { add, apply, ry } from "../motion3/compiler/math.js";
10
+ import { resolveBone } from "../motion3/bones.js";
11
+ import { MOTION_ACTION_HELP as HELP } from "../motion3/docs.js";
12
+ import { DESCRIBE_TOPICS } from "../motion3/schema/fields.js";
13
+ import { describe, describeMany } from "../motion3/describe.js";
14
+ import { createSpecStore, specIdOf, SPEC_STORE_SIZE } from "../motion3/spec-store.js";
15
+ import { applyPatch } from "../motion3/json-patch.js";
16
+ import { deltaOf } from "../motion3/delta.js";
17
+ import { fitOutput, reportPage } from "../motion3/report.js";
18
+ import { keyObject } from "../motion3/key-edit.js";
19
+ import { expandUse } from "../motion3/skills/index.js";
20
+ import { publishDraft } from "../agent-progress.js";
21
+ import { qaSay } from "../qa-label.js";
22
+ import { carryEdit } from "../motion3/edit-carry.js";
23
+ import { normalizeCompileArgs } from "../motion3/lenient.js";
24
+ import { createCompileRunner } from "./motion3-compile.js";
25
+ import { peopleOf } from "../motion3/people.js";
26
+
27
+ const TIMELINE_FPS = 24;
28
+ const id = { type: "string", minLength: 1 };
29
+ const specId = { type: "string", pattern: "^spec-[0-9a-f]{12}$" };
30
+ const openObject = { type: "object", properties: {}, required: [], additionalProperties: true };
31
+ const input = (properties, required = Object.keys(properties)) => ({ type: "object", properties, required, additionalProperties: false });
32
+ const list = (items, maxItems) => ({ type: "array", items, minItems: 1, maxItems });
33
+ const numbers = n => ({ type: "array", items: { type: "number" }, minItems: n, maxItems: n });
34
+
35
+ // readOnly: reads the scene and the take, never changes them (C10).
36
+ const declarations = Object.freeze([
37
+ { id: "motion.compile", label: "Compile motion", kind: "job", domain: "motion", timeoutMs: 120000, description: HELP.compile,
38
+ // patch may be empty: base alone (or patch: []) re-measures the stored spec, with new probes; "never" is install: false
39
+ input: input({ characterId: id, spec: openObject, base: specId, patch: { type: "array", items: openObject, maxItems: 64 },
40
+ install: { oneOf: [{ type: "boolean" }, { const: "ifClean" }, { const: "never" }], default: true },
41
+ probes: list(openObject, 16), repair: input({ rounds: { type: "integer", minimum: 1, maximum: 8, default: 4 } }, []) }, ["characterId"]) },
42
+ { id: "motion.qa", label: "Measure motion", kind: "job", domain: "motion", timeoutMs: 120000, readOnly: true, description: HELP.qa,
43
+ input: input({ characterId: id, checks: openObject }, ["characterId"]) },
44
+ { id: "motion.sample", label: "Sample motion", kind: "job", domain: "motion", timeoutMs: 30000, readOnly: true, description: HELP.sample,
45
+ input: input({ characterId: id, frames: { type: "array", items: { type: "integer", minimum: 0 }, minItems: 1, maxItems: 64 },
46
+ bones: { type: "array", items: id, maxItems: 65 } }, ["characterId", "frames"]) },
47
+ { id: "motion.describe", label: "Describe motion spec", kind: "job", domain: "motion", timeoutMs: 10000, readOnly: true, description: HELP.describe,
48
+ input: input({ topic: { type: "string", enum: DESCRIBE_TOPICS }, name: id,
49
+ topics: list({ oneOf: [{ type: "string", enum: DESCRIBE_TOPICS }, input({ topic: { type: "string", enum: DESCRIBE_TOPICS }, name: id }, ["topic"])] }, 8) }, []) },
50
+ { id: "motion.report", label: "Read motion report", kind: "job", domain: "motion", timeoutMs: 10000, readOnly: true, description: HELP.report,
51
+ input: input({ specId, part: { type: "string", enum: ["groups", "timeline", "probes"], default: "groups" }, from: { type: "integer", minimum: 0, default: 0 } }, ["specId"]) },
52
+ { id: "motion.keyObject", label: "Key held object", kind: "job", domain: "motion", timeoutMs: 120000, description: HELP.keyObject,
53
+ input: input({ characterId: id, objectId: id, frame: { type: "integer", minimum: 0 }, pos: numbers(3), rot: numbers(4), aim: numbers(3), roll: { type: "number" },
54
+ angle: { type: "number" }, remove: { type: "boolean", default: false } }, ["characterId", "objectId", "frame"]) },
55
+ ]);
56
+ export { declarations };
57
+
58
+ const DEFAULT_BONES = ["Hips", "Head", "LeftHand", "RightHand", "LeftFoot", "RightFoot"];
59
+ // the inputs a compile fixed instead of refusing (lenient.js), kept in every answer (report.js keeps the head)
60
+ const MAX_ADJUSTED = 8;
61
+ const adjustedOf = notes => (notes.length ? { adjusted: notes.length > MAX_ADJUSTED ? [...notes.slice(0, MAX_ADJUSTED - 1), `${notes.length - MAX_ADJUSTED + 1} more`] : notes } : {});
62
+ const adjustedSay = notes => (notes.length ? `; ${notes.length} input${notes.length > 1 ? "s" : ""} adjusted (output.adjusted)` : "");
63
+ const unknownSpec = (what, value, then = "send spec") => fail("INVALID_ARGUMENT", `${what} "${value}" is not known (the editor keeps the last ${SPEC_STORE_SIZE} compiled specs this session); ${then}`);
64
+
65
+ const sameArray = (a, b) => a?.length === b?.length && Array.prototype.every.call(a, (v, i) => v === b[i]);
66
+ /** True when `installed` is the take `take` would install: the same spec, placement, bones and object tracks. */
67
+ function sameTake(installed, take) {
68
+ return installed?.schema === take.schema && installed.frameCount === take.frameCount && installed.rig === take.rig
69
+ && specIdOf(installed.spec ?? null) === specIdOf(take.spec) && JSON.stringify(installed.placement) === JSON.stringify(take.placement)
70
+ && sameArray(installed.rot, take.rot) && sameArray(installed.hips, take.hips) && JSON.stringify(installed.objects ?? {}) === JSON.stringify(take.objects);
71
+ }
72
+
73
+ /** then(value) now, or when the promise settles (a compile answers at once in Node, later from the browser's worker). */
74
+ const after = (value, then) => (typeof value?.then === "function" ? value.then(then) : then(value));
75
+ /** Runs `run`, turning a MOTION_SPEC_INVALID error into INVALID_ARGUMENT with the exact message. */
76
+ function asArgument(run) {
77
+ try {
78
+ return run();
79
+ } catch (error) {
80
+ if (/^MOTION_SPEC_INVALID/.test(error.message)) fail("INVALID_ARGUMENT", error.message.replace(/^MOTION_SPEC_INVALID:\s*/, ""));
81
+ throw error;
82
+ }
83
+ }
84
+
85
+ export function register(registry, ports) {
86
+ const store = createSpecStore();
87
+ const owner = () => ports.storeDomain("motion");
88
+ const mounted = () => Boolean(ports.storeDomain?.("motion")) || "The motion owner is not mounted.";
89
+ const always = () => true;
90
+ const rigOf = character => {
91
+ const sk = skeletonFor(character.model);
92
+ if (!sk) fail("TARGET_NOT_READY", `${character.subject || character.id} is a ${character.model} body, which motion compile cannot drive; use an X Bot or Y Bot character.`);
93
+ if (Math.abs((character.scale ?? 1) - 1) > 1e-6) fail("TARGET_NOT_READY", "Set the character's scale back to 1 before compiling motion.");
94
+ return sk;
95
+ };
96
+ // the scene a compile sees: the objects, and the other characters' installed takes as moving bodies (people.js)
97
+ const sceneFor = character => {
98
+ const state = ports.state(), people = peopleOf(state.characters, character.id, id => owner().motionFor(id));
99
+ return { ...motionScene(state.objects), ...(Object.keys(people).length ? { people } : {}) };
100
+ };
101
+ // one compile worker per editor in the browser, the calling thread elsewhere (motion3-compile.js)
102
+ const runner = createCompileRunner();
103
+ const workerWarning = () => { const note = runner.takeWarning(); return note ? { warnings: [note] } : {}; };
104
+ /**
105
+ * spec on the character's rig and the current scene (compile-job.js) -> { stored (the spec a take keeps and its specId
106
+ * names), qa, ..., take when bake is given }, at once or as a promise. opts: probes, checks, lenient, repair.
107
+ */
108
+ const compile = (character, spec, opts = {}, bake) => {
109
+ rigOf(character);
110
+ const placed = bake && { position: [character.x ?? 0, character.y ?? 0, character.z ?? 0], yaw: character.rot ?? 0, ...bake };
111
+ return runner.run({ model: character.model, scene: sceneFor(character), spec, ...opts, ...(placed ? { bake: placed } : {}) });
112
+ };
113
+ /** The whole output, kept in the store (motion.report, delta) and cut to the receipt budget (report.js). */
114
+ const report = (r, head, extra = {}) => {
115
+ const specIdValue = store.put(r.stored);
116
+ store.keepParts(specIdValue, { groups: r.qa.groups, timeline: r.timeline, probes: r.probes ?? {} });
117
+ return fitOutput({ ...head, specId: specIdValue, qa: r.qa, observations: r.observations, ...extra, ...(r.probes ? { probes: r.probes } : {}), timeline: r.timeline,
118
+ fixed: r.fixed, ...(r.search ? { search: r.search } : {}), ...(r.used.length ? { skills: r.used } : {}), ms: r.ms });
119
+ };
120
+ /**
121
+ * A compiled take on the character, installed as one undo entry; put() keeps the timing edit of the take it replaces
122
+ * (edit-carry.js) and returns what it did with it (a note for the receipt), or undefined.
123
+ */
124
+ const put = (character, take, context) => {
125
+ let note;
126
+ const apply = () => {
127
+ const id = character.id, edit = owner().layer?.(id)?.take?.edit, full = edit && owner().fullMotionFor?.(id), carried = full && carryEdit(edit, full.frames ?? full.frameCount, take.frameCount);
128
+ owner().replace(id, { ...take, frames: take.frameCount });
129
+ if (carried?.edit) { owner().writeLayer(id, { take: { ...owner().layer(id).take, edit: carried.edit } }); ports.storeDomain("cast")?.syncTimeline?.(); }
130
+ note = carried?.note;
131
+ };
132
+ if (context?.commit) context.commit(apply); else apply();
133
+ return note;
134
+ };
135
+ const installed = characterId => owner().motionFor(characterId)?.schema === "cozyclay.take.v3" ? owner().motionFor(characterId)
136
+ : fail("TARGET_NOT_READY", "This character has no compiled take; run motion.compile first.");
137
+
138
+ // normalizeArgs: other spellings of install and repair become the declared ones before the schema check (lenient.js)
139
+ registry.register({ ...declarations[0], normalizeArgs: normalizeCompileArgs, available: mounted, target: args => args.characterId, run({ characterId, spec, base, patch, install: asked = true, probes, repair }, context) {
140
+ const install = asked === "never" ? false : asked;
141
+ const character = characterOf(ports, characterId);
142
+ if ((spec === undefined) === (base === undefined)) fail("INVALID_ARGUMENT", "Send exactly one of spec or base (the specId of a compiled spec, with patch).");
143
+ if (patch !== undefined && base === undefined) fail("INVALID_ARGUMENT", "patch applies to base, the specId of a compiled spec; send base with it, or the whole spec.");
144
+ const patched = [], source = spec ?? asArgument(() => applyPatch(store.get(base) ?? unknownSpec("base", base), patch ?? [], patched));
145
+ // the take is always baked: the user watches every draft, installed or not
146
+ return after(compile(character, source, { probes, repair }, {}), r => {
147
+ const { qa, take } = r, notes = [...patched, ...(r.notes ?? [])];
148
+ // ifClean: install only a clean final result (qa.ok includes the goals; after repair when asked)
149
+ const wanted = install === "ifClean" ? qa.ok : install;
150
+ let installs = wanted, kept = false, timing;
151
+ // open ranges are stored with the values the search chose, so motion.qa re-measures this take
152
+ if (wanted) {
153
+ // ifClean of the take already installed changes nothing (no undo entry; IK edits on it stay)
154
+ kept = install === "ifClean" && sameTake(owner().motionFor(characterId), take);
155
+ installs = !kept;
156
+ if (installs) timing = put(character, take, context);
157
+ }
158
+ const delta = base === undefined ? {} : { delta: deltaOf(store.parts(base)?.groups ?? [], qa.groups, base) };
159
+ const how = kept ? "; already installed, unchanged" : install === "ifClean" ? (installs ? "; installed" : "; not installed") : "";
160
+ const output = report(r, { frames: r.frameCount, fps: TIMELINE_FPS, installed: wanted, ...adjustedOf(notes) }, { ...delta, ...(r.repair ? { repair: r.repair } : {}), ...(timing ? { timing } : {}) });
161
+ // agent-progress.js: viewport ghost, timeline beats, agent panel
162
+ publishDraft(characterId, { take, installed: installs || kept, specId: output.specId, label: r.label, qa, timeline: r.timeline });
163
+ return { affectedIds: installs ? [characterId] : [], summary: `Compiled ${r.frameCount} frames; QA ${qaSay(qa)}${how}${adjustedSay(notes)}.`, output, ...workerWarning() };
164
+ });
165
+ } });
166
+ registry.register({ ...declarations[1], available: mounted, run({ characterId, checks }) {
167
+ const character = characterOf(ports, characterId), take = installed(characterId);
168
+ return after(compile(character, take.spec, { checks }), r => ({ affectedIds: [], summary: `QA ${qaSay(r.qa)}${adjustedSay(r.notes ?? [])}.`,
169
+ output: report(r, adjustedOf(r.notes ?? [])), ...workerWarning() }));
170
+ } });
171
+ registry.register({ ...declarations[2], available: mounted, run({ characterId, frames, bones = DEFAULT_BONES }) {
172
+ const character = characterOf(ports, characterId), take = installed(characterId), sk = rigOf(character);
173
+ const yaw = ry(take.placement?.yaw ?? 0), at = take.placement?.position ?? [0, 0, 0], B = take.bones.length;
174
+ const rows = frames.map(f => {
175
+ const ff = Math.min(take.frameCount - 1, f);
176
+ const quats = Array.from({ length: B }, (_, b) => Array.from(take.rot.subarray((ff * B + b) * 4, (ff * B + b) * 4 + 4)));
177
+ const state = fk(sk, quats, Array.from(take.hips.subarray(ff * 3, ff * 3 + 3)));
178
+ return { frame: ff, bones: Object.fromEntries(bones.map(name => {
179
+ const { bone, hint } = resolveBone(sk, name);
180
+ if (bone === null) fail("INVALID_ARGUMENT", `Unknown bone ${name}${hint || "."}`);
181
+ return [name, add(at, apply(yaw, state.P[sk.index[bone]])).map(v => Math.round(v * 1e4) / 1e4)];
182
+ })) };
183
+ });
184
+ return { affectedIds: [], summary: `Sampled ${rows.length} frames.`, output: { frames: rows } };
185
+ } });
186
+ registry.register({ ...declarations[3], available: always, run({ topic, name, topics }) {
187
+ if (topic === undefined && topics === undefined) fail("INVALID_ARGUMENT", "Send topic (and name) for one answer, or topics: [{ topic, name? }] for several in one call.");
188
+ if (topics) {
189
+ // C19: a topic beside topics joins them; a name beside topics goes to the one ask that can take it
190
+ const asks = [...(topic === undefined ? [] : [{ topic }]), ...topics.map(t => (typeof t === "string" ? { topic: t } : { ...t }))];
191
+ const open = asks.filter(a => a.name === undefined && (asks.length === 1 || a.topic === "skills"));
192
+ if (name !== undefined && open.length !== 1) fail("INVALID_ARGUMENT", "name goes with one topic; inside topics give each its own: topics: [{ topic, name? }].");
193
+ if (name !== undefined) open[0].name = name;
194
+ const output = asArgument(() => describeMany(asks));
195
+ return { affectedIds: [], summary: `Described ${output.topics.map(a => (a.name ? `${a.topic} ${a.name}` : a.topic)).join(", ")}${output.more ? `; ${output.more.length} more did not fit` : ""}.`, output };
196
+ }
197
+ return { affectedIds: [], summary: `Described ${topic}${name ? ` ${name}` : ""}.`, output: asArgument(() => describe(topic, name)) };
198
+ } });
199
+ registry.register({ ...declarations[4], available: always, run({ specId: wanted, part = "groups", from = 0 }) {
200
+ const page = reportPage(store.parts(wanted) ?? unknownSpec("specId", wanted, "compile it again"), wanted, part, from);
201
+ return { affectedIds: [], summary: `${page.items.length} ${part} from ${from}.`, output: page };
202
+ } });
203
+ // motion.keyObject (C14): one key of a held object (or a hinge angle) in the installed take's spec, compiled
204
+ // and installed whatever QA says (a user edit), as one undo entry
205
+ registry.register({ ...declarations[5], available: mounted, target: args => args.characterId, run({ characterId, ...args }, context) {
206
+ const character = characterOf(ports, characterId), take = installed(characterId), scene = sceneFor(character);
207
+ const { spec, keyed } = asArgument(() => keyObject(take.spec, scene, args, expandUse(take.spec, { sk: rigOf(character), scene, lenient: true }).spec.objects));
208
+ return after(compile(character, spec, { lenient: true }, { prompt: take.prompt }), r => {
209
+ const timing = put(character, r.take, context);
210
+ return { affectedIds: [characterId], summary: `${keyed.removed ? "Removed the key of" : "Keyed"} ${keyed.objectId} at f${keyed.f}; QA ${qaSay(r.qa)}; installed.`,
211
+ output: report(r, { frames: r.frameCount, fps: TIMELINE_FPS, installed: true, keyed }, timing ? { timing } : {}), ...workerWarning() };
212
+ });
213
+ } });
214
+ }
@@ -8,6 +8,8 @@ import { expandSculptParts, normalizeSculptRecipe, patchSculptRecipe } from '../
8
8
  import { rankingRow, scoreSculptAgainstReference } from '../sculpt-compare.js';
9
9
  import { browserSculptImages, shrinkRgba } from './sculpt-images.js';
10
10
  import { clampWarning } from '../receipt-findings.js';
11
+ import { effectPresetSet } from '../effects/effect-record.js';
12
+ import { withLightWarnings } from '../light-blockers.js';
11
13
  import { STUDIO_TOOL_SCHEMAS, StudioSchemas } from '../studio-agent-protocol.js';
12
14
 
13
15
  const id = StudioSchemas.TargetGuard.properties.targetId;
@@ -80,7 +82,7 @@ export function register(registry, ports) {
80
82
  // goes to the top where it is seen, not below a long expanded group.
81
83
  const next = args.first ? [placed, ...before] : [...before, placed];
82
84
  owned().write(args.parent === undefined ? next : setSceneObjectParent(next, placed.id, args.parent));
83
- return result(before, 'Added object.');
85
+ return withLightWarnings(result(before, 'Added object.'), owned().read());
84
86
  },
85
87
  'object.sculpt': args => {
86
88
  const before = owned().read();
@@ -156,13 +158,13 @@ export function register(registry, ports) {
156
158
  },
157
159
  'objects.arrange': args => {
158
160
  const plan = owned().arrange(args);
159
- return { affectedIds: plan.affectedIds, summary: 'Arranged objects.', checks: plan.checks, warnings: plan.warnings };
161
+ return withLightWarnings({ affectedIds: plan.affectedIds, summary: 'Arranged objects.', checks: plan.checks, warnings: plan.warnings }, owned().read());
160
162
  },
161
163
  };
162
164
  // Scalar scale is a convenience at the command boundary; the registry still
163
165
  // owns all field mapping, normalization, publication and patch aliases.
164
166
  registerElementSet({ register(entry) {
165
- const expand = op => typeof op.set.scale === 'number' ? { ...op, set: { ...op.set, scale: { x: op.set.scale, y: op.set.scale, z: op.set.scale } } } : op;
167
+ const expand = op => effectPresetSet(typeof op.set.scale === 'number' ? { ...op, set: { ...op.set, scale: { x: op.set.scale, y: op.set.scale, z: op.set.scale } } } : op, ports.state().objects);
166
168
  registry.register({ ...entry, available, run: args => {
167
169
  const ops = (args.ops ?? [args]).map(expand);
168
170
  let draft = owned().read();
@@ -173,7 +175,7 @@ export function register(registry, ports) {
173
175
  if (object.attach) fail('CAPABILITY_MISSING', 'Use object.group to preserve an attached object world transform.');
174
176
  draft = setSceneObjectParent(draft, id, parent);
175
177
  }
176
- return entry.run(args.ops ? { ops } : ops[0]);
178
+ return withLightWarnings(entry.run(args.ops ? { ops } : ops[0]), owned().read());
177
179
  } });
178
180
  } }, ports, semantic[0]);
179
181
  for (const declaration of semantic.slice(1)) registry.register({ ...declaration, available, run: methods[declaration.id] });
@@ -7,6 +7,9 @@ import { track } from '../analytics.js';
7
7
  import { isImageAssetId } from '../scene-assets.js';
8
8
  import { createStableItemId, updateStableItem } from '../stable-items.js';
9
9
  import { railFollowForNewGeometry } from '../camera-rail-schedule.js';
10
+ import { buildRail } from '../camera-follow.js';
11
+ import { cameraKeyWarnings } from '../camera-direction.js';
12
+ import { TIMELINE_FPS } from '../timeline-defaults.js';
10
13
  import { studioActionDeclaration, studioActionRefusal } from '../studio-actions.js';
11
14
  import { STUDIO_TOOL_SCHEMAS, StudioSchemas } from '../studio-agent-protocol.js';
12
15
  import { elementSetSchema, registerElementSet } from './elements.js';
@@ -18,11 +21,10 @@ const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
18
21
  const number = { type: 'number' };
19
22
  const frame = { type: 'integer', minimum: 0 };
20
23
  const input = (properties, required = Object.keys(properties)) => ({ type: 'object', properties, required, additionalProperties: false });
21
- const mutation = (id, label, schema) => ({ id, label, description: label, kind: 'mutation', undoDomain: 'shot', input: schema });
22
- const existing = ['shot.create', 'shot.createStill', 'shot.setCaption', 'shot.setCastOverride', 'shot.setHold', 'shot.split', 'shot.duplicate', 'shot.remove', 'shot.setRange', 'shot.setCameraRail', 'shot.clearCameraRail', 'shot.reorder'].map(studioActionDeclaration);
24
+ const mutation = (id, label, schema, description = label) => ({ id, label, description, kind: 'mutation', undoDomain: 'shot', input: schema });
25
+ const existing = ['shot.create', 'shot.createStill', 'shot.setCaption', 'shot.setCastOverride', 'shot.setHold', 'shot.split', 'shot.duplicate', 'shot.remove', 'shot.setRange', 'shot.setCameraRail', 'shot.clearCameraRail', 'shot.setCameraMode', 'shot.rename', 'shot.reorder'].map(studioActionDeclaration);
23
26
  const extra = [
24
27
  mutation('shot.set', 'Set shot fields', elementSetSchema('shot')),
25
- mutation('shot.rename', 'Rename shot', input({ shotId: id, name: { type: 'string', maxLength: 240 } })),
26
28
  mutation('shot.setCamera', 'Set camera block', input({ shotId: id, patch: { type: 'object', properties: {}, additionalProperties: true } })),
27
29
  mutation('shot.addKey', 'Add camera key', input({ shotId: id, frame })),
28
30
  mutation('shot.moveKey', 'Move camera key', input({ shotId: id, keyId: id, frame })),
@@ -30,7 +32,8 @@ const extra = [
30
32
  mutation('shot.clearKeys', 'Clear camera keys', input({ shotId: id })),
31
33
  mutation('shot.setTimeline', 'Set shot timeline', input({ frameCount: { type: 'integer', minimum: 24, maximum: 28800 } })),
32
34
  mutation('shot.setLens', 'Set camera lens', input({ fovDeg: { ...number, minimum: 14, maximum: 90 } })),
33
- mutation('shot.frame', 'Frame the shot', { ...input({ ...STUDIO_TOOL_SCHEMAS.frame_shot.properties, preset: { type: 'string' } }, []), oneOf: [STUDIO_TOOL_SCHEMAS.frame_shot, input({ preset: { type: 'string' } })] }),
35
+ mutation('shot.frame', 'Frame the shot', { ...input({ ...STUDIO_TOOL_SCHEMAS.frame_shot.properties, preset: { type: 'string' } }, []), oneOf: [STUDIO_TOOL_SCHEMAS.frame_shot, input({ preset: { type: 'string' } })] },
36
+ 'Frame a shot on one character (the frame_shot tool): framing.intent { size, level, view, side, focalMm } or framing.exact { position, lookAt, focalMm }. Without keyAtFrame a shot that holds one framing is reframed whole: a held shot. keyAtFrame adds a camera key there; every key eases in and out, so the camera slows to a stop at each one: key a pan or zoom from one spot with two keys (its start and end), lay a moving camera as a rail (shot.setCameraRail) and cut to a new shot for a new view. On a rail shot it puts the shot back on keys. Answers checks { derivedSize, screenFraction, clipped, behindCamera, occluded, cameraSide } for the subject at that frame.'),
34
37
  { ...mutation('shot.replace', 'Replace shot authoring', input({ shots: { type: 'array', items: { type: 'object', properties: {}, additionalProperties: true } } })), exposure: 'ui-only' },
35
38
  { ...mutation('shot.captureCamera', 'Capture camera framing', input({ shotId: id }, [])), exposure: 'ui-only' },
36
39
  // The Board's Stylize result (or null to clear it). UI-only: the picture has
@@ -40,6 +43,14 @@ const extra = [
40
43
  ];
41
44
  export const declarations = Object.freeze([extra[0], ...existing, ...extra.slice(1).map(entry => entry.id === 'shot.frame' ? entry : { ...entry, exposure: 'ui-only' })]);
42
45
 
46
+ // shot.setRange also takes range: [startFrame, endFrameExclusive] or flat startFrame/endFrameExclusive
47
+ // fields; both become range: { startFrame, endFrameExclusive } before validation (registry normalizeArgs).
48
+ function lenientRange(args) {
49
+ const { range, startFrame, endFrameExclusive, ...rest } = args, flat = Object.fromEntries(Object.entries({ startFrame, endFrameExclusive }).filter(([, v]) => v !== undefined));
50
+ if (Array.isArray(range) && range.length === 2 && !Object.keys(flat).length) return { ...rest, range: { startFrame: range[0], endFrameExclusive: range[1] } };
51
+ return range === undefined && Object.keys(flat).length ? { ...rest, range: flat } : args;
52
+ }
53
+
43
54
  export function register(registry, ports) {
44
55
  const owner = () => ports.storeDomain('shot');
45
56
  const mounted = () => Boolean(ports.storeDomain?.('shot')) || 'The shots document owner is not mounted.';
@@ -97,6 +108,27 @@ export function register(registry, ports) {
97
108
  let next = 0;
98
109
  writeStills(current.map(row => row.kind === 'still' ? order[next++] : row));
99
110
  }
111
+ // A rail owns the frames it covers, so the camera keys there go. Its answer
112
+ // names the move it makes: length, speed, height, aim and lens.
113
+ function setRail({ shotId, points, height, aim, aimHeight, focalMm }) {
114
+ const shot = shotOf(shotId), cast = [...ports.state().characters, ...ports.state().objects];
115
+ for (const id of Array.isArray(aim) ? aim : []) if (!cast.some(row => row.id === id)) fail('STALE_TARGET', `Aim ${id} is not a character or object in this scene.`);
116
+ const railFollow = railFollowForNewGeometry(shot.camera?.railFollow, shot.endFrame - shot.startFrame + 1);
117
+ const span = railFollow?.mode === 'range' ? [shot.startFrame + railFollow.startFrame, shot.startFrame + railFollow.endFrame] : [shot.startFrame, shot.endFrame];
118
+ const patch = { cameraRail: points, mode: 'rail', railFollow, ...(focalMm === undefined ? {} : { focalMm }),
119
+ ...(height === undefined ? {} : { craneHeight: { points: typeof height === 'number' ? [{ t: 0, height }, { t: 1, height }] : height } }),
120
+ ...(aim === undefined && aimHeight === undefined ? {} : { aim: aimOf(aim === undefined ? createCameraBlock(shot.camera).aim : aim && (Array.isArray(aim) ? { ids: aim } : { point: aim }), aimHeight) }) };
121
+ const keys = shot.cameraKeys.filter(key => key.frame < span[0] || key.frame > span[1]);
122
+ patchShot(shotId, row => ({ ...row, cameraKeys: keys, camera: updateCameraBlock(row.camera, patch) }));
123
+ const camera = createCameraBlock(shotOf(shotId).camera), metres = buildRail(camera.cameraRail)?.length ?? 0, seconds = (span[1] - span[0] + 1) / TIMELINE_FPS;
124
+ const heights = camera.craneHeight?.points.map(point => point.height) ?? [camera.followCam.height], speed = metres / seconds;
125
+ const aimed = camera.aim?.ids ? `${camera.aim.ids.join(', ')}${camera.aim.height === undefined ? '' : ` at ${camera.aim.height.toFixed(2)} m`}` : (camera.aim ? `(${['x', 'y', 'z'].map(axis => camera.aim.point[axis].toFixed(2)).join(', ')})` : 'the follow subject');
126
+ const note = `${metres.toFixed(1)} m over ${seconds.toFixed(1)} s (${speed.toFixed(2)} m/s), height ${[...new Set([heights[0], heights.at(-1)])].map(h => h.toFixed(2)).join('-')} m, aim ${aimed}, ${camera.focalMm ? `${camera.focalMm} mm` : 'scene lens'}${shot.cameraKeys.length > keys.length ? `; ${shot.cameraKeys.length - keys.length} camera keys removed` : ''}.`;
127
+ const fast = speed > camera.followCam.maxDollySpeed ? [{ code: 'RAIL_TOO_FAST', id: shotId, message: `${speed.toFixed(1)} m/s is past the ${camera.followCam.maxDollySpeed} m/s dolly cap, so the camera lags; lengthen the shot or the rail.` }] : [];
128
+ return { note, warnings: fast };
129
+ }
130
+ // aimHeight moves a cast aim off the chest to a world height (a whole body, a face).
131
+ const aimOf = (aim, aimHeight) => (aim?.ids && aimHeight !== undefined ? { ids: aim.ids, height: aimHeight } : aim);
100
132
  const methods = {
101
133
  'shot.create': () => storyboard() ? createStill() : owner().write(current => addShotAtFrame(current, ports.state().frame, owner().state().frameCount, owner().capture())),
102
134
  'shot.createStill': args => createStill(args),
@@ -119,15 +151,18 @@ export function register(registry, ports) {
119
151
  const edges = startFrame > shot.endFrame ? [['end', endFrame], ['start', startFrame]] : [['start', startFrame], ['end', endFrame]];
120
152
  owner().write(current => edges.reduce((rows, [edge, frame]) => resizeShot(rows, shotId, edge, frame, owner().state().frameCount), current));
121
153
  },
122
- 'shot.setCameraRail': ({ shotId, points }) => patchShot(shotId, shot => ({ ...shot, camera: updateCameraBlock(shot.camera, {
123
- cameraRail: points, mode: 'rail', railFollow: railFollowForNewGeometry(shot.camera?.railFollow, shot.endFrame - shot.startFrame + 1),
124
- }) })),
154
+ 'shot.setCameraRail': args => setRail(args),
125
155
  'shot.clearCameraRail': ({ shotId }) => {
126
156
  const shot = shotOf(shotId);
127
157
  if (!createCameraBlock(shot.camera).cameraRail) fail('TARGET_NOT_READY', `${shot.name || shotId} has no camera rail.`);
128
158
  patchShot(shotId, shot => ({ ...shot, camera: removeCameraRail(shot.camera) }));
129
159
  },
130
160
  'shot.rename': ({ shotId, name }) => { shotOf(shotId); owner().write(current => renameShot(current, shotId, name)); },
161
+ 'shot.setCameraMode': ({ shotId, mode }) => {
162
+ const shot = shotOf(shotId);
163
+ if (mode === 'rail' && !createCameraBlock(shot.camera).cameraRail) fail('TARGET_NOT_READY', `${shot.name || shotId} has no camera rail; lay one with shot.setCameraRail.`);
164
+ patchShot(shotId, row => ({ ...row, camera: updateCameraBlock(row.camera, { mode }) }));
165
+ },
131
166
  'shot.setCamera': ({ shotId, patch }) => patchShot(shotId, shot => ({ ...shot, camera: updateCameraBlock(shot.camera, patch) })),
132
167
  'shot.addKey': ({ shotId, frame }) => {
133
168
  const shot = shotOf(shotId), target = Math.min(owner().state().frameCount - 1, frame);
@@ -159,14 +194,12 @@ export function register(registry, ports) {
159
194
  };
160
195
  registerElementSet(registry, ports, extra[0]);
161
196
  for (const declaration of declarations.filter(row => row.id !== 'shot.set')) registry.register({ ...declaration,
197
+ ...(declaration.id === 'shot.setRange' ? { normalizeArgs: lenientRange } : {}),
162
198
  available: state => mounted() === true ? (availability[declaration.id] ?? (existing.includes(declaration) ? hasShots : () => true))(state) : mounted(),
163
199
  run(args, context) {
164
- if (declaration.id === 'shot.frame') {
165
- const plan = owner().frame(args);
166
- return { affectedIds: plan.affectedIds, summary: 'Framed the shot.' };
167
- }
200
+ if (declaration.id === 'shot.frame') return framed(args);
168
201
  const before = owner().read(), newPanel = declaration.id === 'shot.createStill' || (declaration.id === 'shot.create' && storyboard());
169
- methods[declaration.id](args);
202
+ const answered = methods[declaration.id](args), answer = typeof answered?.note === 'string' ? answered : null;
170
203
  // Only the origin travels: never the caption, name or id of the panel.
171
204
  if (newPanel) track('storyboard:panel_created', { source: context?.origin && context.origin !== 'ui' ? 'agent' : 'manual' });
172
205
  // The owner re-normalizes every shot on write, so compare content, not
@@ -174,8 +207,17 @@ export function register(registry, ports) {
174
207
  // names (100), and the first 100 stand for the batch.
175
208
  const after = owner().read(), changed = changedIds(before.map(row => after.find(next => next.id === row.id && same(next, row)) ?? row), after).slice(0, 100);
176
209
  const affectedIds = changed.length ? changed : [args.shotId ?? ports.state().activeSceneId];
177
- return { affectedIds, summary: changed.length ? `${declaration.label}: ${changed.map(id => after.find(row => row.id === id)).filter(Boolean).map(shotLabel).join('; ')}.` : `${declaration.label}.` };
210
+ const summary = changed.length ? `${declaration.label}: ${changed.map(id => after.find(row => row.id === id)).filter(Boolean).map(shotLabel).join('; ')}.` : `${declaration.label}.`;
211
+ return { affectedIds, summary: answer?.note ? `${summary.slice(0, -1)}: ${answer.note}`.slice(0, 240) : summary, ...(answer?.warnings ? { warnings: answer.warnings } : {}) };
178
212
  },
179
213
  });
214
+ // frame_shot answers what it measured, and what its keys do to a move.
215
+ function framed(args) {
216
+ const before = owner().read(), plan = owner().frame(args), shot = owner().read().find(row => row.id === plan.details.shotId);
217
+ const wasRail = createCameraBlock(before.find(row => row.id === shot.id)?.camera).mode === 'rail';
218
+ return { affectedIds: plan.affectedIds, summary: `Framed ${shotLabel(shot)}: ${plan.checks.derivedSize} at frame ${plan.details.frame}, ${shot.cameraKeys.length} camera key${shot.cameraKeys.length === 1 ? '' : 's'}.`.slice(0, 240),
219
+ checks: plan.checks, warnings: [...plan.warnings, ...(wasRail ? [{ code: 'RAIL_REPLACED', id: shot.id, message: 'This shot is back on camera keys; its rail no longer drives it. Re-lay it with shot.setCameraRail.' }] : []),
220
+ ...cameraKeyWarnings(shot)] };
221
+ }
180
222
  if (ports.storeDomain?.('shot')) registry.registerToolAlias('frame_shot', 'shot.frame');
181
223
  }
@@ -1,17 +1,30 @@
1
1
  import { elementSetSchema, registerElementSet } from './elements.js';
2
+ import { normalizeStage } from './elements/stage.js';
3
+ import { applyLightingRecipe, LIGHTING_KEEP_PATHS, LIGHTING_PRESETS } from '../stage-lighting.js';
2
4
  export { STAGE_FIELDS, normalizeStage } from './elements/stage.js';
3
5
 
4
6
  const input = elementSetSchema('stage');
5
7
  const declaration = (id, properties = input.properties) => ({ id, label: 'Set stage', kind: 'mutation', undoDomain: 'stage',
6
8
  description: 'Update authored stage fields, normalized by scene persistence.',
7
9
  input: { ...input, properties } });
10
+ const applyLighting = { id: 'stage.applyLighting', label: 'Apply lighting', kind: 'mutation', undoDomain: 'stage',
11
+ description: 'Give the stage a whole lighting look in one undoable step: writes every stage.lighting field (sky, skyTop, ambient, hemiSky, hemiGround, fill, fog, exposure, shadowSoftness, envLight, ao) and the key light\'s colour, intensity and position from the recipe, then sets lighting.preset to its name. studio is the neutral grey default; day is a high sun under a blue sky; overcast is soft, shadowless grey daylight; golden a low warm sun with long shadows; dusk a dim warm horizon glow under a blue sky; night a dark blue-black sky, a dim cool moon key with soft shadows, low cool ambient and haze; interior a warm tungsten lamp in a dark room; stage a black void lit by the key alone. Every recipe but studio lights the set with a sky environment map (envLight 1) and darkens contacts and creases (ao 0.6 to 1); studio keeps both at 0. keep lists paths left as they are, e.g. ["keyLight.position"] to keep where the light stands; a recipe also clears an HDR environment (stage.lighting.hdr) unless keep has "lighting.hdr". Tweak any value afterwards with stage.set; check the result with verify_result checks ["lighting"].',
12
+ input: { type: 'object', properties: { preset: { type: 'string', enum: [...LIGHTING_PRESETS] },
13
+ keep: { type: 'array', items: { type: 'string', enum: [...LIGHTING_KEEP_PATHS] }, minItems: 1, maxItems: LIGHTING_KEEP_PATHS.length, uniqueItems: true } }, required: ['preset'], additionalProperties: false } };
8
14
  export const declarations = Object.freeze([
9
15
  declaration('stage.set'),
10
16
  declaration('stage.setKeyLight', { keyLight: input.properties.keyLight }),
11
17
  declaration('stage.setEnvironment', Object.fromEntries(['environment', 'environmentImage', 'hasEnvSheet'].map(key => [key, input.properties[key]]))),
12
18
  declaration('stage.setStyle', { style: input.properties.style }),
13
19
  declaration('stage.setFilmback', Object.fromEntries(['shotAspect', 'cameraPresetId', 'sensorId'].filter(key => input.properties[key]).map(key => [key, input.properties[key]]))),
20
+ applyLighting,
14
21
  ]);
15
22
  export function register(registry, ports) {
16
- for (const declaration of declarations) registerElementSet(registry, ports, declaration);
23
+ for (const declaration of declarations.filter(entry => entry !== applyLighting)) registerElementSet(registry, ports, declaration);
24
+ registry.register({ ...applyLighting, available: () => true, run({ preset, keep = [] }) {
25
+ const domain = ports.storeDomain('stage'), before = domain.read();
26
+ const next = normalizeStage({ ...before, ...applyLightingRecipe(before, preset, keep) });
27
+ if (JSON.stringify(next) !== JSON.stringify(before)) domain.write(next);
28
+ return { affectedIds: [ports.state().activeSceneId], summary: `Applied the ${preset} lighting recipe${keep.length ? `, keeping ${keep.join(', ')}` : ''}.` };
29
+ } });
17
30
  }
@@ -4,6 +4,7 @@ import { STUDIO_TOOL_SCHEMAS } from '../studio-agent-protocol.js';
4
4
  import { ko } from '../locale.js';
5
5
  import { shotAtFrame } from '../cuts.js';
6
6
  import { fail } from './shared.js';
7
+ import { describeGhosts, setGhostSettings } from '../view-ghosts.js';
7
8
 
8
9
  const fields = STUDIO_TOOL_SCHEMAS.operate_studio.properties;
9
10
  const groups = { 'view.select': ['selection', 'shotId'], 'timeline.seek': ['frame'], 'timeline.play': ['playing'], 'view.setMode': ['mode', 'view'] };
@@ -11,7 +12,7 @@ const operations = Object.entries({ ...groups, 'view.update': Object.keys(fields
11
12
  id, label: id, description: 'Change selection, timeline or viewer state without authoring history.', kind: 'transient',
12
13
  input: { type: 'object', properties: Object.fromEntries(keys.map(key => [key, fields[key]])), required: [], additionalProperties: false },
13
14
  }));
14
- export const declarations = Object.freeze([...operations, ...["view.setPartColours", "view.setGuideMode", "view.setInset"].map(studioActionDeclaration)]);
15
+ export const declarations = Object.freeze([...operations, ...["view.setPartColours", "view.setGuideMode", "view.setInset", "view.setGhosts"].map(studioActionDeclaration)]);
15
16
  // A storyboard project has no Motion department; every door says so the same way.
16
17
  export const STORYBOARD_MOTION_REFUSAL = Object.freeze({ en: 'Motion tools are not part of a Storyboard project.', ko: '모션 도구는 스토리보드 프로젝트에 없어요.' });
17
18
  export const viewCommand = args => Object.entries(groups).find(([, keys]) => Object.keys(args).every(key => keys.includes(key)))?.[0] ?? 'view.update';
@@ -45,4 +46,6 @@ export function register(registry, ports) {
45
46
  viewAction("view.setPartColours", ({ mode }) => { ports.choosePartColours(mode); return `Part colours: ${mode}.`; });
46
47
  viewAction("view.setGuideMode", ({ mode }) => { ports.setGuideMode(mode); return `Composition guide: ${mode}.`; });
47
48
  viewAction("view.setInset", ({ collapsed }) => { ports.setInsetCollapsed(collapsed); return `Top-View inset ${collapsed ? "folded" : "unfolded"}.`; });
49
+ // The ghosts' setting lives with them (view-ghosts.js), not in the App: no port to reach.
50
+ viewAction("view.setGhosts", args => describeGhosts(setGhostSettings(args)));
48
51
  }
@@ -0,0 +1,42 @@
1
+ // The active scene's shot document and stage envelope, read from the stores at
2
+ // the moment they are used. App used to rebuild both during render, so a
3
+ // synchronous command read them one render behind: the Studio binding folds
4
+ // them into the scene revision (stage and scenes documents), the receipt
5
+ // answered revision N, and the next render moved the scene to N+1 by itself
6
+ // when the mirrors caught up. An agent's next write, admitted at the receipt's
7
+ // revision.after (a motion install, a cast move, a shot edit), then met
8
+ // STALE_SCENE although nothing had changed.
9
+ import { createShotAuthoringDocument } from "./shot-authoring.js";
10
+ import { savedTakeSource } from "./domains/motion/take-source.js";
11
+
12
+ // IK edits (Pose mode, IK keys, range pins) ride into the saved stage with each character.
13
+ const ikEditsOf = row => (!row || (!row.ikKeys?.length && !row.ikPins?.length) ? null : { keys: row.ikKeys, pins: row.ikPins ?? [], pinResiduals: row.ikPinResiduals ?? [] });
14
+
15
+ /** Ref-shaped mirrors (`.current`) for the scene snapshot and the stage domain.
16
+ * `actorStageRef.current` keeps the envelope App (or the stage domain) last set
17
+ * and always answers the cast as it is now: sessionMotion is stripped (baked
18
+ * takes are session-only and far too heavy for the stage envelope); a take v3
19
+ * persists as its source (spec, placement). */
20
+ export function createDocumentMirrors(appContext) {
21
+ const cast = () => appContext.storeDomain("cast")?.projection() ?? appContext.live.characters ?? [];
22
+ const activeWaypoints = list => (list.find(entry => entry.id === appContext.storeDomain("cast")?.activeId) ?? list[0])?.layer?.waypoints ?? [];
23
+ const shotDocumentRef = {
24
+ get current() {
25
+ const shots = appContext.storeDomain("shot");
26
+ return createShotAuthoringDocument({ shots: shots?.read() ?? [], waypoints: activeWaypoints(cast()), frameCount: shots?.state().frameCount ?? null });
27
+ },
28
+ };
29
+ let envelope = null;
30
+ const stageCharacters = () => {
31
+ const layers = appContext.storeDomain("motion")?.read() ?? null;
32
+ return cast().map(({ sessionMotion, ...entry }) => {
33
+ const row = layers?.find(layer => layer.id === entry.id);
34
+ return { ...entry, ikEdits: ikEditsOf(row), motionTake: savedTakeSource(row?.take) };
35
+ });
36
+ };
37
+ const actorStageRef = {
38
+ get current() { return envelope && { ...envelope, characters: stageCharacters() }; },
39
+ set current(value) { envelope = value; },
40
+ };
41
+ return { shotDocumentRef, actorStageRef };
42
+ }
@@ -38,6 +38,10 @@ export function createDocumentStore({ owned = {}, dev = import.meta.env?.DEV ??
38
38
  run(fn) { check(); running++; try { return fn(); } finally { running--; } },
39
39
  touch(domain) { check(); requireOwner(domain); },
40
40
  update(domain, update) { return session.run(() => write(domain, update)); },
41
+ // A point inside the open transaction (a script's start in an agent turn): rollback(mark)
42
+ // restores it and the transaction stays open; rollback() restores where it began.
43
+ mark() { check(); return slices; },
44
+ rollback(mark = before) { check(); publish(mark); },
41
45
  cancel({ restore = true } = {}) {
42
46
  if (active !== session) return false;
43
47
  active = null;