cozyclay 2.1.1 → 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 (577) hide show
  1. package/CHANGELOG.md +60 -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 +3 -3
  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-msLI71g1.js → first-shot-handoff-CC4xe4HM.js} +1 -1
  33. package/dist/assets/{landing-ChUvB_zL.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/sw.js +3 -6
  40. package/dist/workflow/index.html +5 -4
  41. package/mcp/LIVE-PROTOCOL.md +7 -47
  42. package/mcp/README.md +16 -26
  43. package/mcp/live-hub.mjs +6 -10
  44. package/mcp/package.json +1 -3
  45. package/mcp/tool-handlers.mjs +4 -168
  46. package/package.json +3 -22
  47. package/src/App.jsx +150 -2624
  48. package/src/agent-progress.js +79 -0
  49. package/src/agent-turn-mark.js +30 -0
  50. package/src/analytics.js +1 -19
  51. package/src/app-context.js +5 -0
  52. package/src/app-stage.jsx +2 -34
  53. package/src/asset-pane.jsx +6 -5
  54. package/src/camera-block.js +21 -0
  55. package/src/camera-direction.js +120 -0
  56. package/src/character-kind.js +3 -9
  57. package/src/command-bus.js +8 -6
  58. package/src/commands/cast.js +5 -34
  59. package/src/commands/elements/character.js +1 -1
  60. package/src/commands/elements/motion.js +1 -6
  61. package/src/commands/elements/object.js +6 -2
  62. package/src/commands/elements/shot.js +4 -1
  63. package/src/commands/elements.js +38 -14
  64. package/src/commands/index.js +3 -1
  65. package/src/commands/kits.js +37 -0
  66. package/src/commands/motion-timing.js +57 -0
  67. package/src/commands/motion.js +9 -152
  68. package/src/commands/motion3-compile.js +65 -0
  69. package/src/commands/motion3.js +207 -0
  70. package/src/commands/objects.js +6 -4
  71. package/src/commands/shot.js +49 -11
  72. package/src/commands/stage.js +14 -1
  73. package/src/commands/view.js +4 -1
  74. package/src/document-mirrors.js +42 -0
  75. package/src/document-store.js +4 -0
  76. package/src/domains/cast.js +19 -194
  77. package/src/domains/motion/motion-range-pin-rules.js +1 -1
  78. package/src/domains/motion/take-source.js +51 -0
  79. package/src/domains/motion.js +60 -1838
  80. package/src/domains/scenes.js +12 -55
  81. package/src/domains/shots.js +2 -2
  82. package/src/domains/stage.js +4 -2
  83. package/src/draft-ghost.js +46 -0
  84. package/src/draft-ghost.jsx +129 -0
  85. package/src/dualview.jsx +4 -3
  86. package/src/effects/EffectsLayer.jsx +112 -0
  87. package/src/effects/effect-meshes.js +232 -0
  88. package/src/effects/effect-record.js +103 -0
  89. package/src/effects/emitter.js +59 -0
  90. package/src/effects/simulate.js +261 -0
  91. package/src/effects/state.js +59 -0
  92. package/src/execution-telemetry.js +2 -3
  93. package/src/first-success-guide.jsx +5 -5
  94. package/src/ghost-set.js +257 -0
  95. package/src/held-object-edit.jsx +113 -0
  96. package/src/held-object-pose.js +175 -0
  97. package/src/hierarchy-model.js +1 -1
  98. package/src/hierarchy-panel.jsx +1 -1
  99. package/src/imported-mesh.jsx +103 -0
  100. package/src/kits/held.js +84 -0
  101. package/src/kits/index.js +70 -0
  102. package/src/kits/smoking.js +57 -0
  103. package/src/light-blockers.js +93 -0
  104. package/src/motion/beat-board-model.js +84 -0
  105. package/src/motion/beat-board.css +124 -0
  106. package/src/motion/beat-board.jsx +59 -0
  107. package/src/motion/center-of-mass.js +66 -0
  108. package/src/motion/fix-collisions.js +850 -0
  109. package/src/motion/physics-review.js +118 -0
  110. package/src/{ardy → motion}/physics-support.js +1 -76
  111. package/src/motion/rig-snapshot.js +22 -0
  112. package/src/motion/take-segments.jsx +140 -0
  113. package/src/{ardy → motion}/timeline-coordinates.js +0 -21
  114. package/src/{ardy → motion}/timeline.css +1 -2
  115. package/src/{ardy → motion}/timeline.jsx +42 -413
  116. package/src/motion/waypoints.js +76 -0
  117. package/src/motion3/beats.js +83 -0
  118. package/src/motion3/body.js +158 -0
  119. package/src/motion3/bones.js +47 -0
  120. package/src/motion3/carried.js +47 -0
  121. package/src/motion3/compile-job.js +75 -0
  122. package/src/motion3/compile-worker.js +10 -0
  123. package/src/motion3/compiler/anchors.js +105 -0
  124. package/src/motion3/compiler/arm-orient.js +128 -0
  125. package/src/motion3/compiler/channels.js +125 -0
  126. package/src/motion3/compiler/compile.js +205 -0
  127. package/src/motion3/compiler/gait-rhythm.js +97 -0
  128. package/src/motion3/compiler/grip.js +171 -0
  129. package/src/motion3/compiler/head-steady.js +48 -0
  130. package/src/motion3/compiler/hold.js +134 -0
  131. package/src/motion3/compiler/idle.js +113 -0
  132. package/src/motion3/compiler/limbs.js +170 -0
  133. package/src/motion3/compiler/math.js +72 -0
  134. package/src/motion3/compiler/pose.js +162 -0
  135. package/src/motion3/compiler/quat.js +49 -0
  136. package/src/motion3/compiler/skeleton.js +74 -0
  137. package/src/motion3/compiler/validate-common.js +32 -0
  138. package/src/motion3/compiler/validate-goals.js +118 -0
  139. package/src/motion3/compiler/validate-objects.js +143 -0
  140. package/src/motion3/compiler/validate-parts.js +116 -0
  141. package/src/motion3/compiler/validate.js +122 -0
  142. package/src/motion3/compiler/vocab.js +73 -0
  143. package/src/motion3/compiler/walk-pelvis.js +275 -0
  144. package/src/motion3/compiler/walk.js +207 -0
  145. package/src/motion3/compiler/wrist-clamp.js +32 -0
  146. package/src/motion3/delta.js +47 -0
  147. package/src/motion3/describe.js +174 -0
  148. package/src/motion3/docs.js +27 -0
  149. package/src/motion3/edit-carry.js +21 -0
  150. package/src/motion3/fix/ease.js +16 -0
  151. package/src/motion3/fix/ground.js +37 -0
  152. package/src/motion3/fix/index.js +52 -0
  153. package/src/motion3/fix/push.js +71 -0
  154. package/src/motion3/fix/reach.js +46 -0
  155. package/src/motion3/fix/retime.js +79 -0
  156. package/src/motion3/fix/self.js +211 -0
  157. package/src/motion3/fix/slide.js +70 -0
  158. package/src/motion3/fix/turn.js +51 -0
  159. package/src/motion3/follow.js +39 -0
  160. package/src/motion3/free-motion.js +173 -0
  161. package/src/motion3/grip-choice.js +130 -0
  162. package/src/motion3/hold-plan.js +157 -0
  163. package/src/motion3/json-patch.js +77 -0
  164. package/src/motion3/key-aim.js +105 -0
  165. package/src/motion3/key-edit.js +82 -0
  166. package/src/motion3/lenient.js +127 -0
  167. package/src/motion3/object-keys.js +236 -0
  168. package/src/motion3/object-pose.js +182 -0
  169. package/src/motion3/object-tracks.js +12 -0
  170. package/src/motion3/pipeline.js +102 -0
  171. package/src/motion3/playback.js +51 -0
  172. package/src/motion3/probes.js +91 -0
  173. package/src/motion3/qa/body-checks.js +34 -0
  174. package/src/motion3/qa/check-names.js +14 -0
  175. package/src/motion3/qa/checks.js +121 -0
  176. package/src/motion3/qa/contact-checks.js +51 -0
  177. package/src/motion3/qa/explain.js +80 -0
  178. package/src/motion3/qa/fmt.js +5 -0
  179. package/src/motion3/qa/foot-checks.js +72 -0
  180. package/src/motion3/qa/geometry.js +77 -0
  181. package/src/motion3/qa/goals.js +191 -0
  182. package/src/motion3/qa/ground.js +60 -0
  183. package/src/motion3/qa/hints-contact.js +179 -0
  184. package/src/motion3/qa/hints-goal.js +160 -0
  185. package/src/motion3/qa/hints-knee.js +24 -0
  186. package/src/motion3/qa/hints-motion.js +209 -0
  187. package/src/motion3/qa/hints-object.js +91 -0
  188. package/src/motion3/qa/index.js +42 -0
  189. package/src/motion3/qa/key-checks.js +82 -0
  190. package/src/motion3/qa/near.js +96 -0
  191. package/src/motion3/qa/object-checks.js +135 -0
  192. package/src/motion3/qa/patch.js +161 -0
  193. package/src/motion3/qa/severity.js +38 -0
  194. package/src/motion3/qa/spec-checks.js +65 -0
  195. package/src/motion3/qa/style.js +218 -0
  196. package/src/motion3/qa/wrist-checks.js +69 -0
  197. package/src/motion3/qa/wrist-obs.js +60 -0
  198. package/src/motion3/refs.js +120 -0
  199. package/src/motion3/repair-tune.js +121 -0
  200. package/src/motion3/repair.js +83 -0
  201. package/src/motion3/report.js +67 -0
  202. package/src/motion3/rigs/index.js +14 -0
  203. package/src/motion3/rigs/x-bot-tpose-body.json +1 -0
  204. package/src/motion3/rigs/x-bot-tpose.json +1 -0
  205. package/src/motion3/rigs/y-bot-tpose-body.json +1 -0
  206. package/src/motion3/rigs/y-bot-tpose.json +1 -0
  207. package/src/motion3/scene.js +60 -0
  208. package/src/motion3/schema/brief.js +89 -0
  209. package/src/motion3/schema/fields.js +44 -0
  210. package/src/motion3/schema/spec-examples.js +46 -0
  211. package/src/motion3/schema/topics.js +111 -0
  212. package/src/motion3/schema/types.js +147 -0
  213. package/src/motion3/search.js +117 -0
  214. package/src/motion3/second-hand.js +112 -0
  215. package/src/motion3/skills/basic.js +82 -0
  216. package/src/motion3/skills/book.js +76 -0
  217. package/src/motion3/skills/car.js +145 -0
  218. package/src/motion3/skills/frames-auto.js +29 -0
  219. package/src/motion3/skills/index.js +83 -0
  220. package/src/motion3/skills/info.js +65 -0
  221. package/src/motion3/skills/smoke-kit.js +109 -0
  222. package/src/motion3/skills/smoke.js +160 -0
  223. package/src/motion3/spec-store.js +59 -0
  224. package/src/motion3/take-edit.js +155 -0
  225. package/src/motion3/take.js +63 -0
  226. package/src/motion3/timeline.js +127 -0
  227. package/src/motion3/views.js +121 -0
  228. package/src/motion3/walk-plan.js +119 -0
  229. package/src/object-catalog.jsx +7 -1
  230. package/src/object-colors.js +98 -0
  231. package/src/object-gizmo-parts.js +115 -0
  232. package/src/object-gizmo.jsx +37 -122
  233. package/src/object-glow.js +139 -0
  234. package/src/object-hidden.js +33 -0
  235. package/src/object-look.js +196 -0
  236. package/src/object-size.js +10 -0
  237. package/src/object-travel.js +71 -6
  238. package/src/panels/CameraPanel.jsx +7 -7
  239. package/src/panels/HdrEnvironmentControls.jsx +59 -0
  240. package/src/panels/LightPanel.jsx +25 -4
  241. package/src/panels/ObjectLookPanel.jsx +88 -0
  242. package/src/panels/PosePanel.jsx +2 -17
  243. package/src/panels/RigControlPanel.jsx +5 -53
  244. package/src/panels/SubjectBox.jsx +4 -1
  245. package/src/panels/details.css +14 -0
  246. package/src/posestudio.jsx +4 -27
  247. package/src/project-browser.jsx +3 -4
  248. package/src/project-resources.js +18 -31
  249. package/src/project.js +13 -104
  250. package/src/props.jsx +25 -280
  251. package/src/rail-dot-drag.js +23 -0
  252. package/src/render-passes.js +10 -0
  253. package/src/resource-status.jsx +1 -0
  254. package/src/result-modal.jsx +4 -19
  255. package/src/room.jsx +20 -12
  256. package/src/scene-assets.js +8 -6
  257. package/src/scene-light-place.js +72 -0
  258. package/src/scene-lights.jsx +148 -0
  259. package/src/scene-objects.js +31 -139
  260. package/src/scenes.js +15 -105
  261. package/src/semantic-edit.js +2 -5
  262. package/src/set-pieces.jsx +183 -0
  263. package/src/settings-menu.jsx +3 -11
  264. package/src/shell/BottomDock.jsx +9 -51
  265. package/src/shell/DetailsSlot.jsx +16 -200
  266. package/src/shell/GhostsMenuGroup.jsx +74 -0
  267. package/src/shell/LibrarySlot.jsx +1 -3
  268. package/src/shell/MenuBar.jsx +3 -3
  269. package/src/shell/PreferencesDialog.jsx +15 -264
  270. package/src/shell/PreferencesSlot.jsx +2 -8
  271. package/src/shell/TopBar.jsx +5 -84
  272. package/src/shell/ViewportToolbar.jsx +10 -70
  273. package/src/shell/agent-glass.css +19 -6
  274. package/src/shell/details-selection.js +42 -0
  275. package/src/shell/ghosts-menu.css +22 -0
  276. package/src/shell/glass-regions.css +29 -3
  277. package/src/shell/preferences.css +6 -1
  278. package/src/shot-authoring.js +4 -7
  279. package/src/soft-shadows.js +37 -0
  280. package/src/stage-attach-frames.js +12 -12
  281. package/src/stage-canvas-rigs.jsx +3 -3
  282. package/src/stage-capture.jsx +42 -8
  283. package/src/stage-characters.jsx +22 -216
  284. package/src/stage-editor-gizmos.jsx +6 -6
  285. package/src/stage-environment.js +93 -0
  286. package/src/stage-hdr-builtins.js +107 -0
  287. package/src/stage-hdr-image.js +116 -0
  288. package/src/stage-hdr-library.js +70 -0
  289. package/src/stage-hdr-map.js +94 -0
  290. package/src/stage-hdr.js +130 -0
  291. package/src/stage-lighting.js +149 -0
  292. package/src/stage-occlusion.js +210 -0
  293. package/src/stage-sky.jsx +159 -0
  294. package/src/stage-startup-defaults.js +5 -9
  295. package/src/studio-actions.js +12 -14
  296. package/src/studio-agent-commands.js +19 -16
  297. package/src/studio-agent-motion.js +9 -9
  298. package/src/studio-agent-protocol.js +41 -71
  299. package/src/studio-app-binding.js +25 -45
  300. package/src/studio-asset-list.js +17 -0
  301. package/src/studio-contact-sheet.js +37 -5
  302. package/src/studio-elements.js +27 -7
  303. package/src/studio-entity-bounds.js +18 -0
  304. package/src/studio-error-codes.js +8 -0
  305. package/src/studio-export-framing.js +152 -0
  306. package/src/studio-lighting-check.js +103 -0
  307. package/src/studio-motion-visual.js +150 -0
  308. package/src/studio-schema-errors.js +80 -0
  309. package/src/studio-support-gap.js +74 -0
  310. package/src/styles.css +1 -19
  311. package/src/timeline-defaults.js +5 -0
  312. package/src/timeline-extent.js +8 -20
  313. package/src/tutorial-walk.js +17 -0
  314. package/src/ui-scrub.js +1 -1
  315. package/src/use-shot-camera-tracks.js +12 -0
  316. package/src/view-ghosts.js +97 -0
  317. package/src/view-ghosts.jsx +49 -0
  318. package/src/workflow/AgentPanel.jsx +47 -166
  319. package/src/workflow/AgentProgressCard.jsx +107 -0
  320. package/src/workflow/AgentReplyText.jsx +37 -0
  321. package/src/workflow/AgentTurnSteps.jsx +84 -0
  322. package/src/workflow/LoginPanel.jsx +140 -0
  323. package/src/workflow/WorkflowBuilder.jsx +7 -23
  324. package/src/workflow/agent-client.js +54 -51
  325. package/src/workflow/agent-panel.css +71 -1
  326. package/src/workflow/agent-progress-card.css +167 -0
  327. package/src/workflow/agent-progress-view.js +47 -0
  328. package/src/workflow/agent-turn-steps.css +113 -0
  329. package/src/workflow/agent-turn.js +191 -0
  330. package/src/workflow/login-client.js +34 -0
  331. package/src/workflow/motion-input.js +18 -31
  332. package/src/workflow/node-schema.js +1 -1
  333. package/src/workflow/reply-markdown.js +69 -0
  334. package/src/workflow/scene-asset-sync.js +8 -8
  335. package/tools/agent-session-stats.mjs +61 -0
  336. package/tools/dev-full.mjs +5 -56
  337. package/tools/eslint-baseline.json +12 -13
  338. package/tools/knip-baseline.json +0 -179
  339. package/tools/loc-baseline.json +31 -81
  340. package/tools/motion3/extract-body.mjs +103 -0
  341. package/tools/motion3/extract-rig.mjs +85 -0
  342. package/tools/process-supervisor.mjs +0 -88
  343. package/tools/run-tests.mjs +8 -124
  344. package/bin/agent/motion-runtime.mjs +0 -76
  345. package/dist/ardy/cskel27-rest.json +0 -557
  346. package/dist/assets/analytics-C9pMpW7s.js +0 -1
  347. package/dist/assets/app-B_2XB9rw.css +0 -1
  348. package/dist/assets/app-DAuvKPP8.js +0 -4920
  349. package/dist/assets/config-Bx7pYsD9.js +0 -1
  350. package/dist/assets/config-DeoFTYzb.css +0 -1
  351. package/dist/assets/demo-CvKuBgy5.js +0 -1
  352. package/dist/assets/scene-asset-sync-DKkpjKBV.js +0 -19
  353. package/dist/assets/scene-asset-sync-DjJ_cJ2R.css +0 -1
  354. package/dist/assets/ticket-BPk2Ni95.js +0 -1
  355. package/dist/assets/workflow-jJRBR94Z.js +0 -179
  356. package/dist/demo/walk-then-stop.npz +0 -0
  357. package/mcp/ardy-prompts.mjs +0 -218
  358. package/src/ardy/auto-fix-panel.css +0 -108
  359. package/src/ardy/auto-physics.js +0 -1194
  360. package/src/ardy/client.js +0 -153
  361. package/src/ardy/convert.js +0 -313
  362. package/src/ardy/cskel27-neutral.js +0 -39
  363. package/src/ardy/cskel27.js +0 -77
  364. package/src/ardy/export.js +0 -165
  365. package/src/ardy/fix-collisions.js +0 -2257
  366. package/src/ardy/foot-lock.js +0 -230
  367. package/src/ardy/motion-calibration.js +0 -118
  368. package/src/ardy/motion-edit.js +0 -213
  369. package/src/ardy/motion-url.js +0 -55
  370. package/src/ardy/mp4-duration.js +0 -102
  371. package/src/ardy/npz.js +0 -595
  372. package/src/ardy/physics-panel.css +0 -21
  373. package/src/ardy/physics-panel.jsx +0 -89
  374. package/src/ardy/physics-refine.js +0 -111
  375. package/src/ardy/physics-review.js +0 -567
  376. package/src/ardy/physics-temporal.js +0 -54
  377. package/src/ardy/platform-fit-panel.css +0 -64
  378. package/src/ardy/platform-fit-panel.jsx +0 -41
  379. package/src/ardy/platform-fit.js +0 -326
  380. package/src/ardy/playback.js +0 -711
  381. package/src/ardy/pose-pin.js +0 -97
  382. package/src/ardy/prompt-clips.js +0 -30
  383. package/src/ardy/retime.js +0 -211
  384. package/src/ardy/root-drop.js +0 -369
  385. package/src/ardy/timeline-resize.js +0 -13
  386. package/src/ardy/to-cskel27.js +0 -225
  387. package/src/ardy/trim.js +0 -33
  388. package/src/ardy/waypoints.js +0 -369
  389. package/src/ardy-bridge-contract.js +0 -281
  390. package/src/domains/motion/motion-video-capture.js +0 -664
  391. package/src/i2v-motion-client.js +0 -77
  392. package/src/i2v-motion-studio.jsx +0 -180
  393. package/src/line-edit.js +0 -1348
  394. package/src/motion/generation.js +0 -105
  395. package/src/motion-readiness-ui.jsx +0 -67
  396. package/src/motion-readiness.js +0 -40
  397. package/src/motion-resources.js +0 -177
  398. package/src/motion-store.js +0 -65
  399. package/src/motion-trail.js +0 -601
  400. package/src/multimodel-ingest.js +0 -394
  401. package/src/panels/PromptBlocksPanel.jsx +0 -444
  402. package/src/panels/VideoCapturePanel.jsx +0 -181
  403. package/src/pose-extract/browser.js +0 -42
  404. package/src/pose-extract/detector.js +0 -100
  405. package/src/pose-extract/fit.js +0 -227
  406. package/src/pose-extract/image-frame.js +0 -51
  407. package/src/pose-extract/index.js +0 -58
  408. package/src/pose-extract/keyframes.js +0 -74
  409. package/src/pose-extract/landmarks.js +0 -162
  410. package/src/pose-extract/mirror.js +0 -153
  411. package/src/pose-extract/take.js +0 -131
  412. package/src/pose-extract/video-frames.js +0 -91
  413. package/src/take-recipe.js +0 -165
  414. package/src/trail-key-conflicts.js +0 -39
  415. package/src/trail-pick.js +0 -59
  416. package/src/use-i2v-capture-modal.js +0 -15
  417. package/tools/ardy/BRIDGE.md +0 -37
  418. package/tools/ardy/GVHMR-NPZ.md +0 -68
  419. package/tools/ardy/README.md +0 -124
  420. package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
  421. package/tools/ardy/artifacts.mjs +0 -34
  422. package/tools/ardy/bridge.mjs +0 -1732
  423. package/tools/ardy/bvh-cskel27.mjs +0 -1265
  424. package/tools/ardy/cclay_constrained_generate.py +0 -561
  425. package/tools/ardy/cclay_gvhmr_worker.py +0 -264
  426. package/tools/ardy/cclay_motion_edit.py +0 -510
  427. package/tools/ardy/cclay_sequence_generate.py +0 -705
  428. package/tools/ardy/cclay_worker.py +0 -304
  429. package/tools/ardy/dump-npz.py +0 -205
  430. package/tools/ardy/extract-rest.mjs +0 -299
  431. package/tools/ardy/extract.mjs +0 -507
  432. package/tools/ardy/footage.mjs +0 -467
  433. package/tools/ardy/gvhmr-floor.mjs +0 -102
  434. package/tools/ardy/gvhmr-to-cskel27.mjs +0 -21
  435. package/tools/ardy/gvhmr_fastpath.py +0 -168
  436. package/tools/ardy/gvhmr_trajectory.py +0 -240
  437. package/tools/ardy/merge-text-encoder.py +0 -160
  438. package/tools/ardy/mocap-metrics.mjs +0 -200
  439. package/tools/ardy/mocap-quality-gate.mjs +0 -110
  440. package/tools/ardy/motion-stabilize.mjs +0 -348
  441. package/tools/ardy/npz.mjs +0 -414
  442. package/tools/ardy/pose-to-npz.mjs +0 -106
  443. package/tools/ardy/prompt-limits.mjs +0 -5
  444. package/tools/ardy/run-edit-on-box.sh +0 -73
  445. package/tools/ardy/run-local.mjs +0 -233
  446. package/tools/ardy/run-on-box.sh +0 -593
  447. package/tools/ardy/run-sequence-on-box.sh +0 -177
  448. package/tools/ardy/runners/gvhmr-worker.mjs +0 -206
  449. package/tools/ardy/runners/index.mjs +0 -18
  450. package/tools/ardy/runners/local.mjs +0 -455
  451. package/tools/ardy/runners/proc.mjs +0 -125
  452. package/tools/ardy/runners/remote.mjs +0 -252
  453. package/tools/ardy/setup-local.mjs +0 -189
  454. package/tools/ardy/setup-text-encoder-on-box.sh +0 -93
  455. package/tools/ardy/setup-text-encoder.py +0 -306
  456. package/tools/ardy/smpl-cskel27.mjs +0 -178
  457. package/tools/ardy/visual-qa.mjs +0 -196
  458. package/tools/ardy/vq-car.mjs +0 -52
  459. package/tools/bench/EXP3.md +0 -61
  460. package/tools/bench/cclay_bench_extract_incam.py +0 -125
  461. package/tools/bench/cclay_bench_extract_obs.py +0 -204
  462. package/tools/bench/cclay_bench_runner.py +0 -42
  463. package/tools/bench/cube-contact.mjs +0 -162
  464. package/tools/bench/exp3.mjs +0 -152
  465. package/tools/bench/extract-bench-lib.mjs +0 -216
  466. package/tools/bench/extract-bench.mjs +0 -302
  467. package/tools/bench/fal-generate.mjs +0 -88
  468. package/tools/bench/fit/README.md +0 -189
  469. package/tools/bench/fit/camera.mjs +0 -59
  470. package/tools/bench/fit/contact.mjs +0 -419
  471. package/tools/bench/fit/footlock.mjs +0 -234
  472. package/tools/bench/fit/motion.mjs +0 -85
  473. package/tools/bench/fit/pin.mjs +0 -27
  474. package/tools/bench/fit/remote.mjs +0 -105
  475. package/tools/bench/fit-bench.mjs +0 -126
  476. package/tools/bench/fit-sanity.mjs +0 -45
  477. package/tools/bench/metrics.mjs +0 -313
  478. package/tools/bench/obs/depth.mjs +0 -258
  479. package/tools/bench/obs/extrinsics.mjs +0 -175
  480. package/tools/bench/obs/fit_mannequin_betas.py +0 -340
  481. package/tools/bench/obs/ground.mjs +0 -294
  482. package/tools/bench/obs/heading.mjs +0 -151
  483. package/tools/bench/obs/ladder.mjs +0 -555
  484. package/tools/bench/obs/mannequin-betas.json +0 -124
  485. package/tools/bench/obs/remote.mjs +0 -83
  486. package/tools/bench/obs/rest_joints.py +0 -52
  487. package/tools/bench/obs/ybot-targets.mjs +0 -49
  488. package/tools/bench/obs-bench.mjs +0 -520
  489. package/tools/bench/score.mjs +0 -343
  490. package/tools/bench/summarize.mjs +0 -86
  491. package/tools/demo-worker/README.md +0 -137
  492. package/tools/demo-worker/api-client.mjs +0 -233
  493. package/tools/demo-worker/generate.mjs +0 -301
  494. package/tools/demo-worker/index.mjs +0 -466
  495. package/tools/demo-worker/sign-core.mjs +0 -112
  496. package/tools/demo-worker/sign.mjs +0 -161
  497. package/tools/gt-render/browser.mjs +0 -182
  498. package/tools/gt-render/camera-math.mjs +0 -276
  499. package/tools/gt-render/page.mjs +0 -267
  500. package/tools/gt-render/render.mjs +0 -514
  501. package/tools/gt-render/scene-box.mjs +0 -31
  502. package/tools/gt-render/take-transform.mjs +0 -87
  503. package/tools/kimodo/compare-seam.mjs +0 -108
  504. package/tools/kimodo/constraints.mjs +0 -244
  505. package/tools/kimodo/edit.mjs +0 -259
  506. package/tools/kimodo/effector-constraints.mjs +0 -200
  507. package/tools/kimodo/generate.mjs +0 -646
  508. package/tools/kimodo/local-output.mjs +0 -265
  509. package/tools/kimodo/measure-pose.mjs +0 -65
  510. package/tools/kimodo/measure-preserve.mjs +0 -899
  511. package/tools/kimodo/measure-waypoints.mjs +0 -55
  512. package/tools/kimodo/mlx-generate.py +0 -111
  513. package/tools/kimodo/pose-constraints.mjs +0 -248
  514. package/tools/kimodo/preserve-mask.mjs +0 -543
  515. package/tools/kimodo/read-npz.mjs +0 -189
  516. package/tools/kimodo/run-edit-on-box.mjs +0 -155
  517. package/tools/kimodo/run-sequence-on-box.mjs +0 -178
  518. package/tools/kimodo/runner.mjs +0 -284
  519. package/tools/kimodo/seam-batch.mjs +0 -77
  520. package/tools/kimodo/setup-local.mjs +0 -30
  521. package/tools/kimodo/setup-on-box.sh +0 -339
  522. package/tools/kimodo/soma77-to-cskel27.mjs +0 -305
  523. package/tools/morphgs/assets/gen_truth.py +0 -39
  524. package/tools/morphgs/assets/mesh_ori_rig.txt +0 -27
  525. package/tools/morphgs/assets/playback-check.mjs +0 -30
  526. package/tools/morphgs/demo-gate.sh +0 -26
  527. package/tools/morphgs/fbx2morphgs.mjs +0 -68
  528. package/tools/morphgs/morphgs-to-cskel27.mjs +0 -105
  529. package/tools/morphgs/patches/preprocess_src-none-mode.patch +0 -76
  530. package/tools/morphgs/setup-on-cluster.sh +0 -126
  531. package/tools/projflow/driver.py +0 -1069
  532. package/tools/projflow/generate.mjs +0 -805
  533. package/tools/projflow/hml22-to-cskel27.mjs +0 -735
  534. package/tools/projflow/line-edit-job.mjs +0 -547
  535. package/tools/projflow/replay.mjs +0 -452
  536. package/tools/projflow/runner.mjs +0 -269
  537. package/tools/projflow/service.mjs +0 -742
  538. package/tools/track/DESIGN.md +0 -513
  539. package/tools/track/backfill-provenance.mjs +0 -116
  540. package/tools/track/budget.mjs +0 -8
  541. package/tools/track/check-rig.mjs +0 -209
  542. package/tools/track/diagnostics.schema.json +0 -60
  543. package/tools/track/export-rig.mjs +0 -256
  544. package/tools/track/fallback.mjs +0 -7
  545. package/tools/track/fk-parity-fixture.mjs +0 -161
  546. package/tools/track/gate.mjs +0 -349
  547. package/tools/track/masks.mjs +0 -318
  548. package/tools/track/metrics.mjs +0 -201
  549. package/tools/track/publish-obs.mjs +0 -108
  550. package/tools/track/py/check_env.py +0 -61
  551. package/tools/track/py/eval_lr.py +0 -229
  552. package/tools/track/py/lr_viterbi.py +0 -294
  553. package/tools/track/py/masks.py +0 -405
  554. package/tools/track/py/objective.py +0 -382
  555. package/tools/track/py/rig.py +0 -508
  556. package/tools/track/py/scene.py +0 -326
  557. package/tools/track/py/test_joint_indices.py +0 -123
  558. package/tools/track/py/test_lr_viterbi.py +0 -195
  559. package/tools/track/py/test_masks.py +0 -229
  560. package/tools/track/py/test_rig.py +0 -288
  561. package/tools/track/py/test_scene.py +0 -354
  562. package/tools/track/py/test_track.py +0 -328
  563. package/tools/track/py/track.py +0 -411
  564. package/tools/track/remote.mjs +0 -140
  565. package/tools/track/rig-dump.mjs +0 -227
  566. package/tools/track/run-box-tests.mjs +0 -14
  567. package/tools/track/run-box.mjs +0 -168
  568. package/tools/track/setup-box.sh +0 -59
  569. package/tools/track/study-2d.mjs +0 -728
  570. /package/src/{ardy → motion}/collision-blockers.js +0 -0
  571. /package/src/{ardy → motion}/ground.js +0 -0
  572. /package/src/{ardy → motion}/ik-drag.js +0 -0
  573. /package/src/{ardy → motion}/ik-key-json.js +0 -0
  574. /package/src/{ardy → motion}/ik.js +0 -0
  575. /package/src/{ardy → motion}/physics-surface.js +0 -0
  576. /package/src/{ardy → motion}/playback-clock.js +0 -0
  577. /package/src/{ardy → motion}/range-pin.js +0 -0
@@ -0,0 +1,207 @@
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 { carryEdit } from "../motion3/edit-carry.js";
22
+ import { normalizeCompileArgs } from "../motion3/lenient.js";
23
+ import { createCompileRunner } from "./motion3-compile.js";
24
+
25
+ const TIMELINE_FPS = 24;
26
+ const id = { type: "string", minLength: 1 };
27
+ const specId = { type: "string", pattern: "^spec-[0-9a-f]{12}$" };
28
+ const openObject = { type: "object", properties: {}, required: [], additionalProperties: true };
29
+ const input = (properties, required = Object.keys(properties)) => ({ type: "object", properties, required, additionalProperties: false });
30
+ const list = (items, maxItems) => ({ type: "array", items, minItems: 1, maxItems });
31
+ const numbers = n => ({ type: "array", items: { type: "number" }, minItems: n, maxItems: n });
32
+
33
+ // readOnly: reads the scene and the take, never changes them (C10).
34
+ const declarations = Object.freeze([
35
+ { id: "motion.compile", label: "Compile motion", kind: "job", domain: "motion", timeoutMs: 120000, description: HELP.compile,
36
+ // patch may be empty: base alone (or patch: []) re-measures the stored spec, with new probes; "never" is install: false
37
+ input: input({ characterId: id, spec: openObject, base: specId, patch: { type: "array", items: openObject, maxItems: 64 },
38
+ install: { oneOf: [{ type: "boolean" }, { const: "ifClean" }, { const: "never" }], default: true },
39
+ probes: list(openObject, 16), repair: input({ rounds: { type: "integer", minimum: 1, maximum: 8, default: 4 } }, []) }, ["characterId"]) },
40
+ { id: "motion.qa", label: "Measure motion", kind: "job", domain: "motion", timeoutMs: 120000, readOnly: true, description: HELP.qa,
41
+ input: input({ characterId: id, checks: openObject }, ["characterId"]) },
42
+ { id: "motion.sample", label: "Sample motion", kind: "job", domain: "motion", timeoutMs: 30000, readOnly: true, description: HELP.sample,
43
+ input: input({ characterId: id, frames: { type: "array", items: { type: "integer", minimum: 0 }, minItems: 1, maxItems: 64 },
44
+ bones: { type: "array", items: id, maxItems: 65 } }, ["characterId", "frames"]) },
45
+ { id: "motion.describe", label: "Describe motion spec", kind: "job", domain: "motion", timeoutMs: 10000, readOnly: true, description: HELP.describe,
46
+ input: input({ topic: { type: "string", enum: DESCRIBE_TOPICS }, name: id,
47
+ topics: list({ oneOf: [{ type: "string", enum: DESCRIBE_TOPICS }, input({ topic: { type: "string", enum: DESCRIBE_TOPICS }, name: id }, ["topic"])] }, 8) }, []) },
48
+ { id: "motion.report", label: "Read motion report", kind: "job", domain: "motion", timeoutMs: 10000, readOnly: true, description: HELP.report,
49
+ input: input({ specId, part: { type: "string", enum: ["groups", "timeline", "probes"], default: "groups" }, from: { type: "integer", minimum: 0, default: 0 } }, ["specId"]) },
50
+ { id: "motion.keyObject", label: "Key held object", kind: "job", domain: "motion", timeoutMs: 120000, description: HELP.keyObject,
51
+ input: input({ characterId: id, objectId: id, frame: { type: "integer", minimum: 0 }, pos: numbers(3), rot: numbers(4), aim: numbers(3), roll: { type: "number" },
52
+ angle: { type: "number" }, remove: { type: "boolean", default: false } }, ["characterId", "objectId", "frame"]) },
53
+ ]);
54
+ export { declarations };
55
+
56
+ const DEFAULT_BONES = ["Hips", "Head", "LeftHand", "RightHand", "LeftFoot", "RightFoot"];
57
+ // the inputs a compile fixed instead of refusing (lenient.js), kept in every answer (report.js keeps the head)
58
+ const MAX_ADJUSTED = 8;
59
+ const adjustedOf = notes => (notes.length ? { adjusted: notes.length > MAX_ADJUSTED ? [...notes.slice(0, MAX_ADJUSTED - 1), `${notes.length - MAX_ADJUSTED + 1} more`] : notes } : {});
60
+ const adjustedSay = notes => (notes.length ? `; ${notes.length} input${notes.length > 1 ? "s" : ""} adjusted (output.adjusted)` : "");
61
+ 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}`);
62
+
63
+ const sameArray = (a, b) => a?.length === b?.length && Array.prototype.every.call(a, (v, i) => v === b[i]);
64
+ /** True when `installed` is the take `take` would install: the same spec, placement, bones and object tracks. */
65
+ function sameTake(installed, take) {
66
+ return installed?.schema === take.schema && installed.frameCount === take.frameCount && installed.rig === take.rig
67
+ && specIdOf(installed.spec ?? null) === specIdOf(take.spec) && JSON.stringify(installed.placement) === JSON.stringify(take.placement)
68
+ && sameArray(installed.rot, take.rot) && sameArray(installed.hips, take.hips) && JSON.stringify(installed.objects ?? {}) === JSON.stringify(take.objects);
69
+ }
70
+
71
+ /** then(value) now, or when the promise settles (a compile answers at once in Node, later from the browser's worker). */
72
+ const after = (value, then) => (typeof value?.then === "function" ? value.then(then) : then(value));
73
+ /** Runs `run`, turning a MOTION_SPEC_INVALID error into INVALID_ARGUMENT with the exact message. */
74
+ function asArgument(run) {
75
+ try {
76
+ return run();
77
+ } catch (error) {
78
+ if (/^MOTION_SPEC_INVALID/.test(error.message)) fail("INVALID_ARGUMENT", error.message.replace(/^MOTION_SPEC_INVALID:\s*/, ""));
79
+ throw error;
80
+ }
81
+ }
82
+
83
+ export function register(registry, ports) {
84
+ const store = createSpecStore();
85
+ const owner = () => ports.storeDomain("motion");
86
+ const mounted = () => Boolean(ports.storeDomain?.("motion")) || "The motion owner is not mounted.";
87
+ const always = () => true;
88
+ const rigOf = character => {
89
+ const sk = skeletonFor(character.model);
90
+ 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.`);
91
+ if (Math.abs((character.scale ?? 1) - 1) > 1e-6) fail("TARGET_NOT_READY", "Set the character's scale back to 1 before compiling motion.");
92
+ return sk;
93
+ };
94
+ // one compile worker per editor in the browser, the calling thread elsewhere (motion3-compile.js)
95
+ const runner = createCompileRunner();
96
+ const workerWarning = () => { const note = runner.takeWarning(); return note ? { warnings: [note] } : {}; };
97
+ /**
98
+ * spec on the character's rig and the current scene (compile-job.js) -> { stored (the spec a take keeps and its specId
99
+ * names), qa, ..., take when bake is given }, at once or as a promise. opts: probes, checks, lenient, repair.
100
+ */
101
+ const compile = (character, spec, opts = {}, bake) => {
102
+ rigOf(character);
103
+ const placed = bake && { position: [character.x ?? 0, character.y ?? 0, character.z ?? 0], yaw: character.rot ?? 0, ...bake };
104
+ return runner.run({ model: character.model, scene: motionScene(ports.state().objects), spec, ...opts, ...(placed ? { bake: placed } : {}) });
105
+ };
106
+ /** The whole output, kept in the store (motion.report, delta) and cut to the receipt budget (report.js). */
107
+ const report = (r, head, extra = {}) => {
108
+ const specIdValue = store.put(r.stored);
109
+ store.keepParts(specIdValue, { groups: r.qa.groups, timeline: r.timeline, probes: r.probes ?? {} });
110
+ return fitOutput({ ...head, specId: specIdValue, qa: r.qa, observations: r.observations, ...extra, ...(r.probes ? { probes: r.probes } : {}), timeline: r.timeline,
111
+ fixed: r.fixed, ...(r.search ? { search: r.search } : {}), ...(r.used.length ? { skills: r.used } : {}), ms: r.ms });
112
+ };
113
+ /**
114
+ * A compiled take on the character, installed as one undo entry; put() keeps the timing edit of the take it replaces
115
+ * (edit-carry.js) and returns what it did with it (a note for the receipt), or undefined.
116
+ */
117
+ const put = (character, take, context) => {
118
+ let note;
119
+ const apply = () => {
120
+ 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);
121
+ owner().replace(id, { ...take, frames: take.frameCount });
122
+ if (carried?.edit) { owner().writeLayer(id, { take: { ...owner().layer(id).take, edit: carried.edit } }); ports.storeDomain("cast")?.syncTimeline?.(); }
123
+ note = carried?.note;
124
+ };
125
+ if (context?.commit) context.commit(apply); else apply();
126
+ return note;
127
+ };
128
+ const installed = characterId => owner().motionFor(characterId)?.schema === "cozyclay.take.v3" ? owner().motionFor(characterId)
129
+ : fail("TARGET_NOT_READY", "This character has no compiled take; run motion.compile first.");
130
+
131
+ // normalizeArgs: other spellings of install and repair become the declared ones before the schema check (lenient.js)
132
+ registry.register({ ...declarations[0], normalizeArgs: normalizeCompileArgs, available: mounted, target: args => args.characterId, run({ characterId, spec, base, patch, install: asked = true, probes, repair }, context) {
133
+ const install = asked === "never" ? false : asked;
134
+ const character = characterOf(ports, characterId);
135
+ if ((spec === undefined) === (base === undefined)) fail("INVALID_ARGUMENT", "Send exactly one of spec or base (the specId of a compiled spec, with patch).");
136
+ 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.");
137
+ const patched = [], source = spec ?? asArgument(() => applyPatch(store.get(base) ?? unknownSpec("base", base), patch ?? [], patched));
138
+ // the take is always baked: the user watches every draft, installed or not
139
+ return after(compile(character, source, { probes, repair }, {}), r => {
140
+ const { qa, take } = r, notes = [...patched, ...(r.notes ?? [])];
141
+ // ifClean: install only a clean final result (qa.ok includes the goals; after repair when asked)
142
+ const wanted = install === "ifClean" ? qa.ok : install;
143
+ let installs = wanted, kept = false, timing;
144
+ // open ranges are stored with the values the search chose, so motion.qa re-measures this take
145
+ if (wanted) {
146
+ // ifClean of the take already installed changes nothing (no undo entry; IK edits on it stay)
147
+ kept = install === "ifClean" && sameTake(owner().motionFor(characterId), take);
148
+ installs = !kept;
149
+ if (installs) timing = put(character, take, context);
150
+ }
151
+ const delta = base === undefined ? {} : { delta: deltaOf(store.parts(base)?.groups ?? [], qa.groups, base) };
152
+ const how = kept ? "; already installed, unchanged" : install === "ifClean" ? (installs ? "; installed" : "; not installed") : "";
153
+ const output = report(r, { frames: r.frameCount, fps: TIMELINE_FPS, installed: wanted, ...adjustedOf(notes) }, { ...delta, ...(r.repair ? { repair: r.repair } : {}), ...(timing ? { timing } : {}) });
154
+ // agent-progress.js: viewport ghost, timeline beats, agent panel
155
+ publishDraft(characterId, { take, installed: installs || kept, specId: output.specId, label: r.label, qa, timeline: r.timeline });
156
+ return { affectedIds: installs ? [characterId] : [], summary: `Compiled ${r.frameCount} frames; QA ${qa.ok ? "clean" : `${qa.total} issues`}${how}${adjustedSay(notes)}.`, output, ...workerWarning() };
157
+ });
158
+ } });
159
+ registry.register({ ...declarations[1], available: mounted, run({ characterId, checks }) {
160
+ const character = characterOf(ports, characterId), take = installed(characterId);
161
+ return after(compile(character, take.spec, { checks }), r => ({ affectedIds: [], summary: `QA ${r.qa.ok ? "clean" : `${r.qa.total} issues`}${adjustedSay(r.notes ?? [])}.`,
162
+ output: report(r, adjustedOf(r.notes ?? [])), ...workerWarning() }));
163
+ } });
164
+ registry.register({ ...declarations[2], available: mounted, run({ characterId, frames, bones = DEFAULT_BONES }) {
165
+ const character = characterOf(ports, characterId), take = installed(characterId), sk = rigOf(character);
166
+ const yaw = ry(take.placement?.yaw ?? 0), at = take.placement?.position ?? [0, 0, 0], B = take.bones.length;
167
+ const rows = frames.map(f => {
168
+ const ff = Math.min(take.frameCount - 1, f);
169
+ const quats = Array.from({ length: B }, (_, b) => Array.from(take.rot.subarray((ff * B + b) * 4, (ff * B + b) * 4 + 4)));
170
+ const state = fk(sk, quats, Array.from(take.hips.subarray(ff * 3, ff * 3 + 3)));
171
+ return { frame: ff, bones: Object.fromEntries(bones.map(name => {
172
+ const { bone, hint } = resolveBone(sk, name);
173
+ if (bone === null) fail("INVALID_ARGUMENT", `Unknown bone ${name}${hint || "."}`);
174
+ return [name, add(at, apply(yaw, state.P[sk.index[bone]])).map(v => Math.round(v * 1e4) / 1e4)];
175
+ })) };
176
+ });
177
+ return { affectedIds: [], summary: `Sampled ${rows.length} frames.`, output: { frames: rows } };
178
+ } });
179
+ registry.register({ ...declarations[3], available: always, run({ topic, name, topics }) {
180
+ if (topic === undefined && topics === undefined) fail("INVALID_ARGUMENT", "Send topic (and name) for one answer, or topics: [{ topic, name? }] for several in one call.");
181
+ if (topics) {
182
+ // C19: a topic beside topics joins them; a name beside topics goes to the one ask that can take it
183
+ const asks = [...(topic === undefined ? [] : [{ topic }]), ...topics.map(t => (typeof t === "string" ? { topic: t } : { ...t }))];
184
+ const open = asks.filter(a => a.name === undefined && (asks.length === 1 || a.topic === "skills"));
185
+ if (name !== undefined && open.length !== 1) fail("INVALID_ARGUMENT", "name goes with one topic; inside topics give each its own: topics: [{ topic, name? }].");
186
+ if (name !== undefined) open[0].name = name;
187
+ const output = asArgument(() => describeMany(asks));
188
+ 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 };
189
+ }
190
+ return { affectedIds: [], summary: `Described ${topic}${name ? ` ${name}` : ""}.`, output: asArgument(() => describe(topic, name)) };
191
+ } });
192
+ registry.register({ ...declarations[4], available: always, run({ specId: wanted, part = "groups", from = 0 }) {
193
+ const page = reportPage(store.parts(wanted) ?? unknownSpec("specId", wanted, "compile it again"), wanted, part, from);
194
+ return { affectedIds: [], summary: `${page.items.length} ${part} from ${from}.`, output: page };
195
+ } });
196
+ // motion.keyObject (C14): one key of a held object (or a hinge angle) in the installed take's spec, compiled
197
+ // and installed whatever QA says (a user edit), as one undo entry
198
+ registry.register({ ...declarations[5], available: mounted, target: args => args.characterId, run({ characterId, ...args }, context) {
199
+ const character = characterOf(ports, characterId), take = installed(characterId), scene = motionScene(ports.state().objects);
200
+ const { spec, keyed } = asArgument(() => keyObject(take.spec, scene, args, expandUse(take.spec, { sk: rigOf(character), scene, lenient: true }).spec.objects));
201
+ return after(compile(character, spec, { lenient: true }, { prompt: take.prompt }), r => {
202
+ const timing = put(character, r.take, context);
203
+ return { affectedIds: [characterId], summary: `${keyed.removed ? "Removed the key of" : "Keyed"} ${keyed.objectId} at f${keyed.f}; QA ${r.qa.ok ? "clean" : `${r.qa.total} issues`}; installed.`,
204
+ output: report(r, { frames: r.frameCount, fps: TIMELINE_FPS, installed: true, keyed }, timing ? { timing } : {}), ...workerWarning() };
205
+ });
206
+ } });
207
+ }
@@ -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,7 +21,7 @@ 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 });
24
+ const mutation = (id, label, schema, description = label) => ({ id, label, description, kind: 'mutation', undoDomain: 'shot', input: schema });
22
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.reorder'].map(studioActionDeclaration);
23
26
  const extra = [
24
27
  mutation('shot.set', 'Set shot fields', elementSetSchema('shot')),
@@ -30,7 +33,8 @@ const extra = [
30
33
  mutation('shot.clearKeys', 'Clear camera keys', input({ shotId: id })),
31
34
  mutation('shot.setTimeline', 'Set shot timeline', input({ frameCount: { type: 'integer', minimum: 24, maximum: 28800 } })),
32
35
  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' } })] }),
36
+ 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' } })] },
37
+ '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
38
  { ...mutation('shot.replace', 'Replace shot authoring', input({ shots: { type: 'array', items: { type: 'object', properties: {}, additionalProperties: true } } })), exposure: 'ui-only' },
35
39
  { ...mutation('shot.captureCamera', 'Capture camera framing', input({ shotId: id }, [])), exposure: 'ui-only' },
36
40
  // The Board's Stylize result (or null to clear it). UI-only: the picture has
@@ -40,6 +44,14 @@ const extra = [
40
44
  ];
41
45
  export const declarations = Object.freeze([extra[0], ...existing, ...extra.slice(1).map(entry => entry.id === 'shot.frame' ? entry : { ...entry, exposure: 'ui-only' })]);
42
46
 
47
+ // shot.setRange also takes range: [startFrame, endFrameExclusive] or flat startFrame/endFrameExclusive
48
+ // fields; both become range: { startFrame, endFrameExclusive } before validation (registry normalizeArgs).
49
+ function lenientRange(args) {
50
+ const { range, startFrame, endFrameExclusive, ...rest } = args, flat = Object.fromEntries(Object.entries({ startFrame, endFrameExclusive }).filter(([, v]) => v !== undefined));
51
+ if (Array.isArray(range) && range.length === 2 && !Object.keys(flat).length) return { ...rest, range: { startFrame: range[0], endFrameExclusive: range[1] } };
52
+ return range === undefined && Object.keys(flat).length ? { ...rest, range: flat } : args;
53
+ }
54
+
43
55
  export function register(registry, ports) {
44
56
  const owner = () => ports.storeDomain('shot');
45
57
  const mounted = () => Boolean(ports.storeDomain?.('shot')) || 'The shots document owner is not mounted.';
@@ -97,6 +109,27 @@ export function register(registry, ports) {
97
109
  let next = 0;
98
110
  writeStills(current.map(row => row.kind === 'still' ? order[next++] : row));
99
111
  }
112
+ // A rail owns the frames it covers, so the camera keys there go. Its answer
113
+ // names the move it makes: length, speed, height, aim and lens.
114
+ function setRail({ shotId, points, height, aim, aimHeight, focalMm }) {
115
+ const shot = shotOf(shotId), cast = [...ports.state().characters, ...ports.state().objects];
116
+ 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.`);
117
+ const railFollow = railFollowForNewGeometry(shot.camera?.railFollow, shot.endFrame - shot.startFrame + 1);
118
+ const span = railFollow?.mode === 'range' ? [shot.startFrame + railFollow.startFrame, shot.startFrame + railFollow.endFrame] : [shot.startFrame, shot.endFrame];
119
+ const patch = { cameraRail: points, mode: 'rail', railFollow, ...(focalMm === undefined ? {} : { focalMm }),
120
+ ...(height === undefined ? {} : { craneHeight: { points: typeof height === 'number' ? [{ t: 0, height }, { t: 1, height }] : height } }),
121
+ ...(aim === undefined && aimHeight === undefined ? {} : { aim: aimOf(aim === undefined ? createCameraBlock(shot.camera).aim : aim && (Array.isArray(aim) ? { ids: aim } : { point: aim }), aimHeight) }) };
122
+ const keys = shot.cameraKeys.filter(key => key.frame < span[0] || key.frame > span[1]);
123
+ patchShot(shotId, row => ({ ...row, cameraKeys: keys, camera: updateCameraBlock(row.camera, patch) }));
124
+ const camera = createCameraBlock(shotOf(shotId).camera), metres = buildRail(camera.cameraRail)?.length ?? 0, seconds = (span[1] - span[0] + 1) / TIMELINE_FPS;
125
+ const heights = camera.craneHeight?.points.map(point => point.height) ?? [camera.followCam.height], speed = metres / seconds;
126
+ 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');
127
+ 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` : ''}.`;
128
+ 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.` }] : [];
129
+ return { note, warnings: fast };
130
+ }
131
+ // aimHeight moves a cast aim off the chest to a world height (a whole body, a face).
132
+ const aimOf = (aim, aimHeight) => (aim?.ids && aimHeight !== undefined ? { ids: aim.ids, height: aimHeight } : aim);
100
133
  const methods = {
101
134
  'shot.create': () => storyboard() ? createStill() : owner().write(current => addShotAtFrame(current, ports.state().frame, owner().state().frameCount, owner().capture())),
102
135
  'shot.createStill': args => createStill(args),
@@ -119,9 +152,7 @@ export function register(registry, ports) {
119
152
  const edges = startFrame > shot.endFrame ? [['end', endFrame], ['start', startFrame]] : [['start', startFrame], ['end', endFrame]];
120
153
  owner().write(current => edges.reduce((rows, [edge, frame]) => resizeShot(rows, shotId, edge, frame, owner().state().frameCount), current));
121
154
  },
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
- }) })),
155
+ 'shot.setCameraRail': args => setRail(args),
125
156
  'shot.clearCameraRail': ({ shotId }) => {
126
157
  const shot = shotOf(shotId);
127
158
  if (!createCameraBlock(shot.camera).cameraRail) fail('TARGET_NOT_READY', `${shot.name || shotId} has no camera rail.`);
@@ -159,14 +190,12 @@ export function register(registry, ports) {
159
190
  };
160
191
  registerElementSet(registry, ports, extra[0]);
161
192
  for (const declaration of declarations.filter(row => row.id !== 'shot.set')) registry.register({ ...declaration,
193
+ ...(declaration.id === 'shot.setRange' ? { normalizeArgs: lenientRange } : {}),
162
194
  available: state => mounted() === true ? (availability[declaration.id] ?? (existing.includes(declaration) ? hasShots : () => true))(state) : mounted(),
163
195
  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
- }
196
+ if (declaration.id === 'shot.frame') return framed(args);
168
197
  const before = owner().read(), newPanel = declaration.id === 'shot.createStill' || (declaration.id === 'shot.create' && storyboard());
169
- methods[declaration.id](args);
198
+ const answered = methods[declaration.id](args), answer = typeof answered?.note === 'string' ? answered : null;
170
199
  // Only the origin travels: never the caption, name or id of the panel.
171
200
  if (newPanel) track('storyboard:panel_created', { source: context?.origin && context.origin !== 'ui' ? 'agent' : 'manual' });
172
201
  // The owner re-normalizes every shot on write, so compare content, not
@@ -174,8 +203,17 @@ export function register(registry, ports) {
174
203
  // names (100), and the first 100 stand for the batch.
175
204
  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
205
  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}.` };
206
+ const summary = changed.length ? `${declaration.label}: ${changed.map(id => after.find(row => row.id === id)).filter(Boolean).map(shotLabel).join('; ')}.` : `${declaration.label}.`;
207
+ return { affectedIds, summary: answer?.note ? `${summary.slice(0, -1)}: ${answer.note}`.slice(0, 240) : summary, ...(answer?.warnings ? { warnings: answer.warnings } : {}) };
178
208
  },
179
209
  });
210
+ // frame_shot answers what it measured, and what its keys do to a move.
211
+ function framed(args) {
212
+ const before = owner().read(), plan = owner().frame(args), shot = owner().read().find(row => row.id === plan.details.shotId);
213
+ const wasRail = createCameraBlock(before.find(row => row.id === shot.id)?.camera).mode === 'rail';
214
+ 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),
215
+ 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.' }] : []),
216
+ ...cameraKeyWarnings(shot)] };
217
+ }
180
218
  if (ports.storeDomain?.('shot')) registry.registerToolAlias('frame_shot', 'shot.frame');
181
219
  }
@@ -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;