cozyclay 2.1.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (583) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +12 -30
  3. package/bin/agent/agent-routes.mjs +40 -113
  4. package/bin/agent/agent-runner.mjs +9 -10
  5. package/bin/agent/cliproxy-provider.mjs +135 -0
  6. package/bin/agent/credential-store.mjs +30 -7
  7. package/bin/agent/pi-tools.mjs +4 -1
  8. package/bin/agent/provider-keys.mjs +32 -0
  9. package/bin/agent/provider-login.mjs +110 -0
  10. package/bin/agent/provider-routes.mjs +99 -0
  11. package/bin/agent/providers.mjs +19 -97
  12. package/bin/agent/script-tool.mjs +17 -4
  13. package/bin/agent/studio-motion-reference.mjs +23 -0
  14. package/bin/agent/studio-prompt.mjs +1 -1
  15. package/bin/agent/studio-rebase-text.mjs +32 -0
  16. package/bin/agent/studio-rebase.mjs +26 -39
  17. package/bin/agent/studio-tools.mjs +9 -28
  18. package/bin/agent/tool-arguments.mjs +49 -0
  19. package/bin/agent/turn-frames.mjs +66 -0
  20. package/bin/codex-auth.mjs +9 -2
  21. package/bin/cozyclay.mjs +10 -116
  22. package/bin/live/cli.mjs +22 -6
  23. package/bin/mcp-runtime.mjs +2 -12
  24. package/bin/package-signature.mjs +0 -4
  25. package/dist/app/index.html +7 -6
  26. package/dist/assets/EXRLoader-CvVIvGVE.js +1 -0
  27. package/dist/assets/analytics-C9sUnndd.js +1 -0
  28. package/dist/assets/app-CCh--1Hc.js +5087 -0
  29. package/dist/assets/app-f-fkV5uP.css +1 -0
  30. package/dist/assets/compile-worker-CkxiD6lH.js +5 -0
  31. package/dist/assets/fflate.module-KbGj5-H6.js +5 -0
  32. package/dist/assets/{first-shot-handoff-BlT33CGf.js → first-shot-handoff-CC4xe4HM.js} +1 -1
  33. package/dist/assets/{landing-BSesrCpL.js → landing-DjHD8u7k.js} +1 -1
  34. package/dist/assets/scene-asset-sync-Hx_FzdOH.js +20 -0
  35. package/dist/assets/scene-asset-sync-_S8NQl5B.css +1 -0
  36. package/dist/assets/workflow-C4yKH1p4.js +179 -0
  37. package/dist/cozyclay-package.json +1 -1
  38. package/dist/index.html +3 -3
  39. package/dist/llms.txt +14 -0
  40. package/dist/sw.js +3 -6
  41. package/dist/workflow/index.html +5 -4
  42. package/mcp/LIVE-PROTOCOL.md +7 -47
  43. package/mcp/README.md +16 -26
  44. package/mcp/live-hub.mjs +6 -10
  45. package/mcp/package.json +1 -3
  46. package/mcp/tool-handlers.mjs +4 -168
  47. package/package.json +3 -22
  48. package/src/App.jsx +174 -2639
  49. package/src/agent-progress.js +79 -0
  50. package/src/agent-turn-mark.js +30 -0
  51. package/src/analytics.js +1 -19
  52. package/src/app-context.js +5 -0
  53. package/src/app-stage.jsx +2 -34
  54. package/src/asset-pane.jsx +6 -5
  55. package/src/camera-block.js +21 -0
  56. package/src/camera-direction.js +120 -0
  57. package/src/character-kind.js +3 -9
  58. package/src/command-bus.js +8 -6
  59. package/src/commands/cast.js +5 -34
  60. package/src/commands/elements/character.js +1 -1
  61. package/src/commands/elements/motion.js +1 -6
  62. package/src/commands/elements/object.js +6 -2
  63. package/src/commands/elements/shot.js +4 -1
  64. package/src/commands/elements.js +38 -14
  65. package/src/commands/index.js +3 -1
  66. package/src/commands/kits.js +37 -0
  67. package/src/commands/motion-timing.js +57 -0
  68. package/src/commands/motion.js +9 -152
  69. package/src/commands/motion3-compile.js +65 -0
  70. package/src/commands/motion3.js +207 -0
  71. package/src/commands/objects.js +6 -4
  72. package/src/commands/shot.js +49 -11
  73. package/src/commands/stage.js +14 -1
  74. package/src/commands/view.js +4 -1
  75. package/src/document-mirrors.js +42 -0
  76. package/src/document-store.js +4 -0
  77. package/src/domains/cast.js +19 -194
  78. package/src/domains/motion/motion-range-pin-rules.js +1 -1
  79. package/src/domains/motion/take-source.js +51 -0
  80. package/src/domains/motion.js +60 -1838
  81. package/src/domains/scenes.js +12 -55
  82. package/src/domains/shots.js +2 -2
  83. package/src/domains/stage.js +4 -2
  84. package/src/draft-ghost.js +46 -0
  85. package/src/draft-ghost.jsx +129 -0
  86. package/src/dualview.jsx +4 -3
  87. package/src/effects/EffectsLayer.jsx +112 -0
  88. package/src/effects/effect-meshes.js +232 -0
  89. package/src/effects/effect-record.js +103 -0
  90. package/src/effects/emitter.js +59 -0
  91. package/src/effects/simulate.js +261 -0
  92. package/src/effects/state.js +59 -0
  93. package/src/execution-telemetry.js +2 -3
  94. package/src/first-success-guide.jsx +5 -5
  95. package/src/ghost-set.js +257 -0
  96. package/src/held-object-edit.jsx +113 -0
  97. package/src/held-object-pose.js +175 -0
  98. package/src/hierarchy-model.js +1 -1
  99. package/src/hierarchy-panel.jsx +1 -1
  100. package/src/imported-mesh.jsx +103 -0
  101. package/src/kits/held.js +84 -0
  102. package/src/kits/index.js +70 -0
  103. package/src/kits/smoking.js +57 -0
  104. package/src/light-blockers.js +93 -0
  105. package/src/motion/beat-board-model.js +84 -0
  106. package/src/motion/beat-board.css +124 -0
  107. package/src/motion/beat-board.jsx +59 -0
  108. package/src/motion/center-of-mass.js +66 -0
  109. package/src/motion/fix-collisions.js +850 -0
  110. package/src/motion/physics-review.js +118 -0
  111. package/src/{ardy → motion}/physics-support.js +1 -76
  112. package/src/motion/rig-snapshot.js +22 -0
  113. package/src/motion/take-segments.jsx +140 -0
  114. package/src/{ardy → motion}/timeline-coordinates.js +0 -21
  115. package/src/{ardy → motion}/timeline.css +1 -2
  116. package/src/{ardy → motion}/timeline.jsx +48 -419
  117. package/src/motion/waypoints.js +76 -0
  118. package/src/motion3/beats.js +83 -0
  119. package/src/motion3/body.js +158 -0
  120. package/src/motion3/bones.js +47 -0
  121. package/src/motion3/carried.js +47 -0
  122. package/src/motion3/compile-job.js +75 -0
  123. package/src/motion3/compile-worker.js +10 -0
  124. package/src/motion3/compiler/anchors.js +105 -0
  125. package/src/motion3/compiler/arm-orient.js +128 -0
  126. package/src/motion3/compiler/channels.js +125 -0
  127. package/src/motion3/compiler/compile.js +205 -0
  128. package/src/motion3/compiler/gait-rhythm.js +97 -0
  129. package/src/motion3/compiler/grip.js +171 -0
  130. package/src/motion3/compiler/head-steady.js +48 -0
  131. package/src/motion3/compiler/hold.js +134 -0
  132. package/src/motion3/compiler/idle.js +113 -0
  133. package/src/motion3/compiler/limbs.js +170 -0
  134. package/src/motion3/compiler/math.js +72 -0
  135. package/src/motion3/compiler/pose.js +162 -0
  136. package/src/motion3/compiler/quat.js +49 -0
  137. package/src/motion3/compiler/skeleton.js +74 -0
  138. package/src/motion3/compiler/validate-common.js +32 -0
  139. package/src/motion3/compiler/validate-goals.js +118 -0
  140. package/src/motion3/compiler/validate-objects.js +143 -0
  141. package/src/motion3/compiler/validate-parts.js +116 -0
  142. package/src/motion3/compiler/validate.js +122 -0
  143. package/src/motion3/compiler/vocab.js +73 -0
  144. package/src/motion3/compiler/walk-pelvis.js +275 -0
  145. package/src/motion3/compiler/walk.js +207 -0
  146. package/src/motion3/compiler/wrist-clamp.js +32 -0
  147. package/src/motion3/delta.js +47 -0
  148. package/src/motion3/describe.js +174 -0
  149. package/src/motion3/docs.js +27 -0
  150. package/src/motion3/edit-carry.js +21 -0
  151. package/src/motion3/fix/ease.js +16 -0
  152. package/src/motion3/fix/ground.js +37 -0
  153. package/src/motion3/fix/index.js +52 -0
  154. package/src/motion3/fix/push.js +71 -0
  155. package/src/motion3/fix/reach.js +46 -0
  156. package/src/motion3/fix/retime.js +79 -0
  157. package/src/motion3/fix/self.js +211 -0
  158. package/src/motion3/fix/slide.js +70 -0
  159. package/src/motion3/fix/turn.js +51 -0
  160. package/src/motion3/follow.js +39 -0
  161. package/src/motion3/free-motion.js +173 -0
  162. package/src/motion3/grip-choice.js +130 -0
  163. package/src/motion3/hold-plan.js +157 -0
  164. package/src/motion3/json-patch.js +77 -0
  165. package/src/motion3/key-aim.js +105 -0
  166. package/src/motion3/key-edit.js +82 -0
  167. package/src/motion3/lenient.js +127 -0
  168. package/src/motion3/object-keys.js +236 -0
  169. package/src/motion3/object-pose.js +182 -0
  170. package/src/motion3/object-tracks.js +12 -0
  171. package/src/motion3/pipeline.js +102 -0
  172. package/src/motion3/playback.js +51 -0
  173. package/src/motion3/probes.js +91 -0
  174. package/src/motion3/qa/body-checks.js +34 -0
  175. package/src/motion3/qa/check-names.js +14 -0
  176. package/src/motion3/qa/checks.js +121 -0
  177. package/src/motion3/qa/contact-checks.js +51 -0
  178. package/src/motion3/qa/explain.js +80 -0
  179. package/src/motion3/qa/fmt.js +5 -0
  180. package/src/motion3/qa/foot-checks.js +72 -0
  181. package/src/motion3/qa/geometry.js +77 -0
  182. package/src/motion3/qa/goals.js +191 -0
  183. package/src/motion3/qa/ground.js +60 -0
  184. package/src/motion3/qa/hints-contact.js +179 -0
  185. package/src/motion3/qa/hints-goal.js +160 -0
  186. package/src/motion3/qa/hints-knee.js +24 -0
  187. package/src/motion3/qa/hints-motion.js +209 -0
  188. package/src/motion3/qa/hints-object.js +91 -0
  189. package/src/motion3/qa/index.js +42 -0
  190. package/src/motion3/qa/key-checks.js +82 -0
  191. package/src/motion3/qa/near.js +96 -0
  192. package/src/motion3/qa/object-checks.js +135 -0
  193. package/src/motion3/qa/patch.js +161 -0
  194. package/src/motion3/qa/severity.js +38 -0
  195. package/src/motion3/qa/spec-checks.js +65 -0
  196. package/src/motion3/qa/style.js +218 -0
  197. package/src/motion3/qa/wrist-checks.js +69 -0
  198. package/src/motion3/qa/wrist-obs.js +60 -0
  199. package/src/motion3/refs.js +120 -0
  200. package/src/motion3/repair-tune.js +121 -0
  201. package/src/motion3/repair.js +83 -0
  202. package/src/motion3/report.js +67 -0
  203. package/src/motion3/rigs/index.js +14 -0
  204. package/src/motion3/rigs/x-bot-tpose-body.json +1 -0
  205. package/src/motion3/rigs/x-bot-tpose.json +1 -0
  206. package/src/motion3/rigs/y-bot-tpose-body.json +1 -0
  207. package/src/motion3/rigs/y-bot-tpose.json +1 -0
  208. package/src/motion3/scene.js +60 -0
  209. package/src/motion3/schema/brief.js +89 -0
  210. package/src/motion3/schema/fields.js +44 -0
  211. package/src/motion3/schema/spec-examples.js +46 -0
  212. package/src/motion3/schema/topics.js +111 -0
  213. package/src/motion3/schema/types.js +147 -0
  214. package/src/motion3/search.js +117 -0
  215. package/src/motion3/second-hand.js +112 -0
  216. package/src/motion3/skills/basic.js +82 -0
  217. package/src/motion3/skills/book.js +76 -0
  218. package/src/motion3/skills/car.js +145 -0
  219. package/src/motion3/skills/frames-auto.js +29 -0
  220. package/src/motion3/skills/index.js +83 -0
  221. package/src/motion3/skills/info.js +65 -0
  222. package/src/motion3/skills/smoke-kit.js +109 -0
  223. package/src/motion3/skills/smoke.js +160 -0
  224. package/src/motion3/spec-store.js +59 -0
  225. package/src/motion3/take-edit.js +155 -0
  226. package/src/motion3/take.js +63 -0
  227. package/src/motion3/timeline.js +127 -0
  228. package/src/motion3/views.js +121 -0
  229. package/src/motion3/walk-plan.js +119 -0
  230. package/src/object-catalog.jsx +7 -1
  231. package/src/object-colors.js +98 -0
  232. package/src/object-gizmo-parts.js +115 -0
  233. package/src/object-gizmo.jsx +37 -122
  234. package/src/object-glow.js +139 -0
  235. package/src/object-hidden.js +33 -0
  236. package/src/object-look.js +196 -0
  237. package/src/object-size.js +10 -0
  238. package/src/object-travel.js +71 -6
  239. package/src/panels/CameraPanel.jsx +7 -7
  240. package/src/panels/HdrEnvironmentControls.jsx +59 -0
  241. package/src/panels/LightPanel.jsx +25 -4
  242. package/src/panels/ObjectLookPanel.jsx +88 -0
  243. package/src/panels/PoseImagePanel.jsx +1 -1
  244. package/src/panels/PosePanel.jsx +2 -17
  245. package/src/panels/RigControlPanel.jsx +5 -53
  246. package/src/panels/SubjectBox.jsx +4 -1
  247. package/src/panels/details.css +14 -0
  248. package/src/posestudio.jsx +4 -27
  249. package/src/project-browser.jsx +3 -4
  250. package/src/project-resources.js +18 -31
  251. package/src/project.js +13 -104
  252. package/src/props.jsx +25 -280
  253. package/src/rail-dot-drag.js +23 -0
  254. package/src/render-passes.js +10 -0
  255. package/src/resource-status.jsx +1 -0
  256. package/src/result-modal.jsx +4 -19
  257. package/src/room.jsx +20 -12
  258. package/src/scene-assets.js +8 -6
  259. package/src/scene-light-place.js +72 -0
  260. package/src/scene-lights.jsx +148 -0
  261. package/src/scene-objects.js +31 -139
  262. package/src/scenes.js +15 -105
  263. package/src/semantic-edit.js +2 -5
  264. package/src/set-pieces.jsx +183 -0
  265. package/src/settings-menu.jsx +3 -11
  266. package/src/shell/BottomDock.jsx +11 -53
  267. package/src/shell/CompositionGuideButton.jsx +20 -0
  268. package/src/shell/DetailsSlot.jsx +16 -200
  269. package/src/shell/GhostsMenuGroup.jsx +74 -0
  270. package/src/shell/LibrarySlot.jsx +1 -3
  271. package/src/shell/MenuBar.jsx +3 -3
  272. package/src/shell/PreferencesDialog.jsx +15 -264
  273. package/src/shell/PreferencesSlot.jsx +2 -8
  274. package/src/shell/ShotPreviewHeader.jsx +17 -0
  275. package/src/shell/TopBar.jsx +5 -83
  276. package/src/shell/ViewportToolbar.jsx +23 -93
  277. package/src/shell/agent-glass.css +19 -6
  278. package/src/shell/details-selection.js +42 -0
  279. package/src/shell/ghosts-menu.css +22 -0
  280. package/src/shell/glass-regions.css +29 -3
  281. package/src/shell/preferences.css +6 -1
  282. package/src/shell/viewport.css +12 -0
  283. package/src/shot-authoring.js +4 -7
  284. package/src/soft-shadows.js +37 -0
  285. package/src/stage-attach-frames.js +12 -12
  286. package/src/stage-canvas-rigs.jsx +3 -3
  287. package/src/stage-capture.jsx +42 -8
  288. package/src/stage-characters.jsx +22 -216
  289. package/src/stage-editor-gizmos.jsx +6 -6
  290. package/src/stage-environment.js +93 -0
  291. package/src/stage-hdr-builtins.js +107 -0
  292. package/src/stage-hdr-image.js +116 -0
  293. package/src/stage-hdr-library.js +70 -0
  294. package/src/stage-hdr-map.js +94 -0
  295. package/src/stage-hdr.js +130 -0
  296. package/src/stage-lighting.js +149 -0
  297. package/src/stage-occlusion.js +210 -0
  298. package/src/stage-sky.jsx +159 -0
  299. package/src/stage-startup-defaults.js +5 -9
  300. package/src/studio-actions.js +12 -14
  301. package/src/studio-agent-commands.js +46 -16
  302. package/src/studio-agent-motion.js +9 -9
  303. package/src/studio-agent-protocol.js +46 -73
  304. package/src/studio-app-binding.js +29 -45
  305. package/src/studio-asset-list.js +17 -0
  306. package/src/studio-contact-sheet.js +37 -5
  307. package/src/studio-elements.js +27 -7
  308. package/src/studio-entity-bounds.js +18 -0
  309. package/src/studio-error-codes.js +8 -0
  310. package/src/studio-export-framing.js +152 -0
  311. package/src/studio-lighting-check.js +103 -0
  312. package/src/studio-motion-visual.js +150 -0
  313. package/src/studio-schema-errors.js +80 -0
  314. package/src/studio-support-gap.js +74 -0
  315. package/src/styles.css +1 -19
  316. package/src/timeline-defaults.js +5 -0
  317. package/src/timeline-extent.js +8 -20
  318. package/src/tutorial-walk.js +17 -0
  319. package/src/ui-scrub.js +1 -1
  320. package/src/use-shot-camera-tracks.js +12 -0
  321. package/src/view-ghosts.js +97 -0
  322. package/src/view-ghosts.jsx +49 -0
  323. package/src/workflow/AgentPanel.jsx +47 -166
  324. package/src/workflow/AgentProgressCard.jsx +107 -0
  325. package/src/workflow/AgentReplyText.jsx +37 -0
  326. package/src/workflow/AgentTurnSteps.jsx +84 -0
  327. package/src/workflow/LoginPanel.jsx +140 -0
  328. package/src/workflow/WorkflowBuilder.jsx +7 -23
  329. package/src/workflow/agent-client.js +54 -51
  330. package/src/workflow/agent-panel.css +71 -1
  331. package/src/workflow/agent-progress-card.css +167 -0
  332. package/src/workflow/agent-progress-view.js +47 -0
  333. package/src/workflow/agent-turn-steps.css +113 -0
  334. package/src/workflow/agent-turn.js +191 -0
  335. package/src/workflow/login-client.js +34 -0
  336. package/src/workflow/motion-input.js +18 -31
  337. package/src/workflow/node-schema.js +1 -1
  338. package/src/workflow/reply-markdown.js +69 -0
  339. package/src/workflow/scene-asset-sync.js +8 -8
  340. package/tools/agent-session-stats.mjs +61 -0
  341. package/tools/dev-full.mjs +5 -56
  342. package/tools/eslint-baseline.json +12 -13
  343. package/tools/headless-studio.mjs +85 -0
  344. package/tools/knip-baseline.json +0 -179
  345. package/tools/loc-baseline.json +33 -83
  346. package/tools/motion3/extract-body.mjs +103 -0
  347. package/tools/motion3/extract-rig.mjs +85 -0
  348. package/tools/process-supervisor.mjs +0 -88
  349. package/tools/run-tests.mjs +9 -124
  350. package/bin/agent/motion-runtime.mjs +0 -76
  351. package/dist/ardy/cskel27-rest.json +0 -557
  352. package/dist/assets/analytics-B41FmPYI.js +0 -1
  353. package/dist/assets/app-Do-Og5Pu.css +0 -1
  354. package/dist/assets/app-DwP-tl2x.js +0 -4920
  355. package/dist/assets/config-Bx7pYsD9.js +0 -1
  356. package/dist/assets/config-DeoFTYzb.css +0 -1
  357. package/dist/assets/demo-Dt0qZfS7.js +0 -1
  358. package/dist/assets/scene-asset-sync-BCWDJB1d.js +0 -19
  359. package/dist/assets/scene-asset-sync-DjJ_cJ2R.css +0 -1
  360. package/dist/assets/ticket-jqMhqL88.js +0 -1
  361. package/dist/assets/workflow-Br-K7Srs.js +0 -179
  362. package/dist/demo/walk-then-stop.npz +0 -0
  363. package/mcp/ardy-prompts.mjs +0 -218
  364. package/src/ardy/auto-fix-panel.css +0 -108
  365. package/src/ardy/auto-physics.js +0 -1194
  366. package/src/ardy/client.js +0 -153
  367. package/src/ardy/convert.js +0 -313
  368. package/src/ardy/cskel27-neutral.js +0 -39
  369. package/src/ardy/cskel27.js +0 -77
  370. package/src/ardy/export.js +0 -165
  371. package/src/ardy/fix-collisions.js +0 -2257
  372. package/src/ardy/foot-lock.js +0 -230
  373. package/src/ardy/motion-calibration.js +0 -118
  374. package/src/ardy/motion-edit.js +0 -213
  375. package/src/ardy/motion-url.js +0 -55
  376. package/src/ardy/mp4-duration.js +0 -102
  377. package/src/ardy/npz.js +0 -595
  378. package/src/ardy/physics-panel.css +0 -21
  379. package/src/ardy/physics-panel.jsx +0 -89
  380. package/src/ardy/physics-refine.js +0 -111
  381. package/src/ardy/physics-review.js +0 -567
  382. package/src/ardy/physics-temporal.js +0 -54
  383. package/src/ardy/platform-fit-panel.css +0 -64
  384. package/src/ardy/platform-fit-panel.jsx +0 -41
  385. package/src/ardy/platform-fit.js +0 -326
  386. package/src/ardy/playback.js +0 -711
  387. package/src/ardy/pose-pin.js +0 -97
  388. package/src/ardy/prompt-clips.js +0 -30
  389. package/src/ardy/retime.js +0 -211
  390. package/src/ardy/root-drop.js +0 -369
  391. package/src/ardy/timeline-resize.js +0 -13
  392. package/src/ardy/to-cskel27.js +0 -225
  393. package/src/ardy/trim.js +0 -33
  394. package/src/ardy/waypoints.js +0 -369
  395. package/src/ardy-bridge-contract.js +0 -281
  396. package/src/domains/motion/motion-video-capture.js +0 -664
  397. package/src/i2v-motion-client.js +0 -77
  398. package/src/i2v-motion-studio.jsx +0 -180
  399. package/src/line-edit.js +0 -1348
  400. package/src/motion/generation.js +0 -105
  401. package/src/motion-readiness-ui.jsx +0 -67
  402. package/src/motion-readiness.js +0 -40
  403. package/src/motion-resources.js +0 -177
  404. package/src/motion-store.js +0 -65
  405. package/src/motion-trail.js +0 -601
  406. package/src/multimodel-ingest.js +0 -394
  407. package/src/panels/PromptBlocksPanel.jsx +0 -444
  408. package/src/panels/VideoCapturePanel.jsx +0 -181
  409. package/src/pose-extract/browser.js +0 -42
  410. package/src/pose-extract/detector.js +0 -100
  411. package/src/pose-extract/fit.js +0 -227
  412. package/src/pose-extract/image-frame.js +0 -51
  413. package/src/pose-extract/index.js +0 -58
  414. package/src/pose-extract/keyframes.js +0 -74
  415. package/src/pose-extract/landmarks.js +0 -162
  416. package/src/pose-extract/mirror.js +0 -153
  417. package/src/pose-extract/take.js +0 -131
  418. package/src/pose-extract/video-frames.js +0 -91
  419. package/src/take-recipe.js +0 -165
  420. package/src/trail-key-conflicts.js +0 -39
  421. package/src/trail-pick.js +0 -59
  422. package/src/use-i2v-capture-modal.js +0 -15
  423. package/tools/ardy/BRIDGE.md +0 -37
  424. package/tools/ardy/GVHMR-NPZ.md +0 -68
  425. package/tools/ardy/README.md +0 -124
  426. package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
  427. package/tools/ardy/artifacts.mjs +0 -34
  428. package/tools/ardy/bridge.mjs +0 -1732
  429. package/tools/ardy/bvh-cskel27.mjs +0 -1265
  430. package/tools/ardy/cclay_constrained_generate.py +0 -561
  431. package/tools/ardy/cclay_gvhmr_worker.py +0 -264
  432. package/tools/ardy/cclay_motion_edit.py +0 -510
  433. package/tools/ardy/cclay_sequence_generate.py +0 -705
  434. package/tools/ardy/cclay_worker.py +0 -304
  435. package/tools/ardy/dump-npz.py +0 -205
  436. package/tools/ardy/extract-rest.mjs +0 -299
  437. package/tools/ardy/extract.mjs +0 -507
  438. package/tools/ardy/footage.mjs +0 -467
  439. package/tools/ardy/gvhmr-floor.mjs +0 -102
  440. package/tools/ardy/gvhmr-to-cskel27.mjs +0 -21
  441. package/tools/ardy/gvhmr_fastpath.py +0 -168
  442. package/tools/ardy/gvhmr_trajectory.py +0 -240
  443. package/tools/ardy/merge-text-encoder.py +0 -160
  444. package/tools/ardy/mocap-metrics.mjs +0 -200
  445. package/tools/ardy/mocap-quality-gate.mjs +0 -110
  446. package/tools/ardy/motion-stabilize.mjs +0 -348
  447. package/tools/ardy/npz.mjs +0 -414
  448. package/tools/ardy/pose-to-npz.mjs +0 -106
  449. package/tools/ardy/prompt-limits.mjs +0 -5
  450. package/tools/ardy/run-edit-on-box.sh +0 -73
  451. package/tools/ardy/run-local.mjs +0 -233
  452. package/tools/ardy/run-on-box.sh +0 -593
  453. package/tools/ardy/run-sequence-on-box.sh +0 -177
  454. package/tools/ardy/runners/gvhmr-worker.mjs +0 -206
  455. package/tools/ardy/runners/index.mjs +0 -18
  456. package/tools/ardy/runners/local.mjs +0 -455
  457. package/tools/ardy/runners/proc.mjs +0 -125
  458. package/tools/ardy/runners/remote.mjs +0 -252
  459. package/tools/ardy/setup-local.mjs +0 -189
  460. package/tools/ardy/setup-text-encoder-on-box.sh +0 -93
  461. package/tools/ardy/setup-text-encoder.py +0 -306
  462. package/tools/ardy/smpl-cskel27.mjs +0 -178
  463. package/tools/ardy/visual-qa.mjs +0 -196
  464. package/tools/ardy/vq-car.mjs +0 -52
  465. package/tools/bench/EXP3.md +0 -61
  466. package/tools/bench/cclay_bench_extract_incam.py +0 -125
  467. package/tools/bench/cclay_bench_extract_obs.py +0 -204
  468. package/tools/bench/cclay_bench_runner.py +0 -42
  469. package/tools/bench/cube-contact.mjs +0 -162
  470. package/tools/bench/exp3.mjs +0 -152
  471. package/tools/bench/extract-bench-lib.mjs +0 -216
  472. package/tools/bench/extract-bench.mjs +0 -302
  473. package/tools/bench/fal-generate.mjs +0 -88
  474. package/tools/bench/fit/README.md +0 -189
  475. package/tools/bench/fit/camera.mjs +0 -59
  476. package/tools/bench/fit/contact.mjs +0 -419
  477. package/tools/bench/fit/footlock.mjs +0 -234
  478. package/tools/bench/fit/motion.mjs +0 -85
  479. package/tools/bench/fit/pin.mjs +0 -27
  480. package/tools/bench/fit/remote.mjs +0 -105
  481. package/tools/bench/fit-bench.mjs +0 -126
  482. package/tools/bench/fit-sanity.mjs +0 -45
  483. package/tools/bench/metrics.mjs +0 -313
  484. package/tools/bench/obs/depth.mjs +0 -258
  485. package/tools/bench/obs/extrinsics.mjs +0 -175
  486. package/tools/bench/obs/fit_mannequin_betas.py +0 -340
  487. package/tools/bench/obs/ground.mjs +0 -294
  488. package/tools/bench/obs/heading.mjs +0 -151
  489. package/tools/bench/obs/ladder.mjs +0 -555
  490. package/tools/bench/obs/mannequin-betas.json +0 -124
  491. package/tools/bench/obs/remote.mjs +0 -83
  492. package/tools/bench/obs/rest_joints.py +0 -52
  493. package/tools/bench/obs/ybot-targets.mjs +0 -49
  494. package/tools/bench/obs-bench.mjs +0 -520
  495. package/tools/bench/score.mjs +0 -343
  496. package/tools/bench/summarize.mjs +0 -86
  497. package/tools/demo-worker/README.md +0 -137
  498. package/tools/demo-worker/api-client.mjs +0 -233
  499. package/tools/demo-worker/generate.mjs +0 -301
  500. package/tools/demo-worker/index.mjs +0 -466
  501. package/tools/demo-worker/sign-core.mjs +0 -112
  502. package/tools/demo-worker/sign.mjs +0 -161
  503. package/tools/gt-render/browser.mjs +0 -159
  504. package/tools/gt-render/camera-math.mjs +0 -276
  505. package/tools/gt-render/page.mjs +0 -267
  506. package/tools/gt-render/render.mjs +0 -514
  507. package/tools/gt-render/scene-box.mjs +0 -31
  508. package/tools/gt-render/take-transform.mjs +0 -87
  509. package/tools/kimodo/compare-seam.mjs +0 -108
  510. package/tools/kimodo/constraints.mjs +0 -244
  511. package/tools/kimodo/edit.mjs +0 -259
  512. package/tools/kimodo/effector-constraints.mjs +0 -200
  513. package/tools/kimodo/generate.mjs +0 -646
  514. package/tools/kimodo/local-output.mjs +0 -265
  515. package/tools/kimodo/measure-pose.mjs +0 -65
  516. package/tools/kimodo/measure-preserve.mjs +0 -899
  517. package/tools/kimodo/measure-waypoints.mjs +0 -55
  518. package/tools/kimodo/mlx-generate.py +0 -111
  519. package/tools/kimodo/pose-constraints.mjs +0 -248
  520. package/tools/kimodo/preserve-mask.mjs +0 -543
  521. package/tools/kimodo/read-npz.mjs +0 -189
  522. package/tools/kimodo/run-edit-on-box.mjs +0 -155
  523. package/tools/kimodo/run-sequence-on-box.mjs +0 -178
  524. package/tools/kimodo/runner.mjs +0 -284
  525. package/tools/kimodo/seam-batch.mjs +0 -77
  526. package/tools/kimodo/setup-local.mjs +0 -30
  527. package/tools/kimodo/setup-on-box.sh +0 -339
  528. package/tools/kimodo/soma77-to-cskel27.mjs +0 -305
  529. package/tools/morphgs/assets/gen_truth.py +0 -39
  530. package/tools/morphgs/assets/mesh_ori_rig.txt +0 -27
  531. package/tools/morphgs/assets/playback-check.mjs +0 -30
  532. package/tools/morphgs/demo-gate.sh +0 -26
  533. package/tools/morphgs/fbx2morphgs.mjs +0 -68
  534. package/tools/morphgs/morphgs-to-cskel27.mjs +0 -105
  535. package/tools/morphgs/patches/preprocess_src-none-mode.patch +0 -76
  536. package/tools/morphgs/setup-on-cluster.sh +0 -126
  537. package/tools/projflow/driver.py +0 -1069
  538. package/tools/projflow/generate.mjs +0 -805
  539. package/tools/projflow/hml22-to-cskel27.mjs +0 -735
  540. package/tools/projflow/line-edit-job.mjs +0 -547
  541. package/tools/projflow/replay.mjs +0 -452
  542. package/tools/projflow/runner.mjs +0 -269
  543. package/tools/projflow/service.mjs +0 -742
  544. package/tools/track/DESIGN.md +0 -513
  545. package/tools/track/backfill-provenance.mjs +0 -116
  546. package/tools/track/budget.mjs +0 -8
  547. package/tools/track/check-rig.mjs +0 -209
  548. package/tools/track/diagnostics.schema.json +0 -60
  549. package/tools/track/export-rig.mjs +0 -256
  550. package/tools/track/fallback.mjs +0 -7
  551. package/tools/track/fk-parity-fixture.mjs +0 -161
  552. package/tools/track/gate.mjs +0 -349
  553. package/tools/track/masks.mjs +0 -318
  554. package/tools/track/metrics.mjs +0 -201
  555. package/tools/track/publish-obs.mjs +0 -108
  556. package/tools/track/py/check_env.py +0 -61
  557. package/tools/track/py/eval_lr.py +0 -229
  558. package/tools/track/py/lr_viterbi.py +0 -294
  559. package/tools/track/py/masks.py +0 -405
  560. package/tools/track/py/objective.py +0 -382
  561. package/tools/track/py/rig.py +0 -508
  562. package/tools/track/py/scene.py +0 -326
  563. package/tools/track/py/test_joint_indices.py +0 -123
  564. package/tools/track/py/test_lr_viterbi.py +0 -195
  565. package/tools/track/py/test_masks.py +0 -229
  566. package/tools/track/py/test_rig.py +0 -288
  567. package/tools/track/py/test_scene.py +0 -354
  568. package/tools/track/py/test_track.py +0 -328
  569. package/tools/track/py/track.py +0 -411
  570. package/tools/track/remote.mjs +0 -140
  571. package/tools/track/rig-dump.mjs +0 -227
  572. package/tools/track/run-box-tests.mjs +0 -14
  573. package/tools/track/run-box.mjs +0 -168
  574. package/tools/track/setup-box.sh +0 -59
  575. package/tools/track/study-2d.mjs +0 -728
  576. /package/src/{ardy → motion}/collision-blockers.js +0 -0
  577. /package/src/{ardy → motion}/ground.js +0 -0
  578. /package/src/{ardy → motion}/ik-drag.js +0 -0
  579. /package/src/{ardy → motion}/ik-key-json.js +0 -0
  580. /package/src/{ardy → motion}/ik.js +0 -0
  581. /package/src/{ardy → motion}/physics-surface.js +0 -0
  582. /package/src/{ardy → motion}/playback-clock.js +0 -0
  583. /package/src/{ardy → motion}/range-pin.js +0 -0
@@ -1,4 +1,4 @@
1
- import { normalizeSceneObject } from '../../scene-objects.js';
1
+ import { normalizeSceneObject, updateSceneObject } from '../../scene-objects.js';
2
2
  import { STUDIO_ELEMENTS } from '../../studio-elements.js';
3
3
  import { registerElementKind } from '../elements.js';
4
4
 
@@ -19,4 +19,8 @@ function normalizeObjectSet(record) {
19
19
  // The owning collection consumes this lifecycle intent before persistence.
20
20
  return record.remove === true ? { ...item, remove: true } : item;
21
21
  }
22
- registerElementKind('object', { collection: true, documentKey: 'objects', elements, normalize: normalizeObjectSet });
22
+ // A set that moves or turns a record carries its descendants exactly as object.update, the
23
+ // inspector and the gizmo do (updateSceneObject): rigidly about the record's pivot, scale not carried.
24
+ const TRANSFORM = ['x', 'y', 'z', 'rotX', 'rot', 'rotZ'];
25
+ const carryDescendants = (rows, id, after) => after ? updateSceneObject(rows, id, Object.fromEntries(TRANSFORM.map(key => [key, after[key]]))) : rows;
26
+ registerElementKind('object', { collection: true, documentKey: 'objects', elements, normalize: normalizeObjectSet, carry: carryDescendants });
@@ -1,3 +1,4 @@
1
+ import { cameraKeyWarnings } from '../../camera-direction.js';
1
2
  import { createShotAuthoringDocument } from '../../shot-authoring.js';
2
3
  import { STUDIO_ELEMENTS } from '../../studio-elements.js';
3
4
  import { registerElementKind } from '../elements.js';
@@ -5,5 +6,7 @@ import { registerElementKind } from '../elements.js';
5
6
  function normalizeShot(record) {
6
7
  return createShotAuthoringDocument({ shots: [record], frameCount: Math.max(24, record.endFrame + 1) }).shots[0];
7
8
  }
9
+ // A shot.set that writes camera keys hears when they halt the move, like frame_shot does.
8
10
  registerElementKind('shot', { collection: true, documentKey: 'shots',
9
- elements: STUDIO_ELEMENTS.filter(row => row.path.startsWith('shot.')), normalize: normalizeShot });
11
+ elements: STUDIO_ELEMENTS.filter(row => row.path.startsWith('shot.')), normalize: normalizeShot,
12
+ warn: (shot, set) => (Object.hasOwn(set, 'cameraKeys') ? cameraKeyWarnings(shot) : []) });
@@ -97,24 +97,34 @@ export function elementPatchArgs(kind, args) {
97
97
  });
98
98
  return collection ? ops.length === 1 ? ops[0] : { ops } : ops.reduce((patch, op) => mergeElementSet(patch, op.set), {});
99
99
  }
100
+ // Unasked, an optional structured value (a glow, a light) is listed only where
101
+ // the item has one, so every plain object's receipt stays as short as before.
102
+ const listed = (element, item, paths) => paths ? paths.includes(element.path) : element.type !== 'object' || readElement(item, element.path) != null;
100
103
  export function elementReadback(kind, item, paths, document) {
101
- return patchElements(projectionKind(kind)).filter(element => !paths || paths.includes(element.path)).map(element => {
104
+ return patchElements(projectionKind(kind)).filter(element => listed(element, item, paths)).map(element => {
102
105
  const path = element.path;
103
106
  if (element.readback) return { path, ...element.readback(item, document) };
104
107
  const value = readElement(item, path);
105
108
  if (value === null || value === undefined || value === '') return { path, text: null };
106
109
  if (element.type === 'image') return { path, bytes: utf8ByteLength(value) };
110
+ // A small structured value (a light, a glow) reads back whole, as compact JSON.
111
+ if (element.type === 'object') return { path, text: JSON.stringify(value).slice(0, 512) };
107
112
  if (element.type === 'vec3') return { path, vec: value };
108
113
  if (element.type === 'array') return { path, count: Array.isArray(value) ? value.length : value.points?.length ?? 0 };
109
114
  return { path, [typeof value === 'number' ? 'number' : typeof value === 'boolean' ? 'flag' : 'text']: value };
110
115
  });
111
116
  }
112
117
  // Normalizer-added defaults are allowed; changed/clamped requested members
113
- // are reported as dropped, just like the native patch planners.
114
- function survives(requested, actual) {
118
+ // are reported as dropped, just like the native patch planners. A number that
119
+ // landed within float noise (a wrap leaves -17.529999999999973 for -17.53),
120
+ // or an angle that landed one or more whole turns away (190 kept as -170), survives.
121
+ const turn = (delta, angle) => angle ? delta - 360 * Math.round(delta / 360) : delta;
122
+ function survives(requested, actual, angle = false) {
123
+ if (typeof requested === 'number' && typeof actual === 'number') return requested === actual
124
+ || Math.abs(turn(requested - actual, angle)) <= 1e-9 * Math.max(1, Math.abs(requested), Math.abs(actual));
115
125
  if (requested === null || typeof requested !== 'object') return requested === actual;
116
- if (Array.isArray(requested)) return Array.isArray(actual) && requested.length === actual.length && requested.every((value, index) => survives(value, actual[index]));
117
- return actual !== null && typeof actual === 'object' && !Array.isArray(actual) && Object.entries(requested).every(([key, value]) => survives(value, actual[key]));
126
+ if (Array.isArray(requested)) return Array.isArray(actual) && requested.length === actual.length && requested.every((value, index) => survives(value, actual[index], angle));
127
+ return actual !== null && typeof actual === 'object' && !Array.isArray(actual) && Object.entries(requested).every(([key, value]) => survives(value, actual[key], angle));
118
128
  }
119
129
  export function elementPatchReceipt(receipt, request, projection) {
120
130
  const kind = request?.args.ops[0].target.kind;
@@ -124,32 +134,46 @@ export function elementPatchReceipt(receipt, request, projection) {
124
134
  const item = elementTarget(kind, value, target.id, receipt.host.sceneId);
125
135
  const droppedPaths = Object.entries(set).filter(([key, requested]) => {
126
136
  const element = patchElements(kind).find(element => element.path === `${kind}.${key}`);
127
- return !element.readback && !survives(requested, readElement(item, element.path));
137
+ return !element.readback && !survives(requested, readElement(item, element.path), element.angle === true);
128
138
  }).map(([key]) => `${kind}.${key}`);
129
139
  return { index, status: droppedPaths.length ? 'partial' : receipt.authored ? 'applied' : 'noop', ...(droppedPaths.length ? { droppedPaths } : {}) };
130
140
  });
131
- const partial = receipt.authored && ops.some(op => op.status === 'partial');
132
- // The protocol's partial variant is a patch receipt, not an action receipt.
133
- const { action, summary, ...base } = receipt;
141
+ // A dropped path makes the receipt "partial" whenever it published one revision: an applied
142
+ // patch, or one inside an agent turn (which the bus answers "completed"). A composite that moved
143
+ // the revision further stays "completed", and its ops name the dropped paths.
144
+ const partial = receipt.authored && ops.some(op => op.status === 'partial') && receipt.revision.after === receipt.revision.before + 1 && !receipt.nextHost;
145
+ // The protocol's partial variant is a patch receipt, not an action or composite receipt.
146
+ const { action, summary, kind: composite, ...base } = receipt;
147
+ // A row the edit carried along (a moved group's part) reads back where it went: its vec3 transform paths.
148
+ const targets = new Set(request.args.ops.map(({ target }) => target.id));
149
+ const moved = patchElements(kind).filter(element => element.type === 'vec3').map(element => element.path);
150
+ const carried = receipt.affectedIds.filter(id => !targets.has(id) && elementTarget(kind, value, id, receipt.host.sceneId)).map(id => ({ id, paths: moved }));
134
151
  return { ...(partial ? base : receipt), ops, status: partial ? 'partial' : receipt.status,
135
- delta: !receipt.authored ? [] : request.args.ops.slice(0, 8).map(({ target, set }) => ({ id: target.id, after: {
136
- patched: elementReadback(kind, elementTarget(kind, value, target.id, receipt.host.sceneId), Object.keys(set).map(key => `${kind}.${key}`), value),
137
- } })) };
152
+ delta: !receipt.authored ? [] : [...request.args.ops.map(({ target, set }) => ({ id: target.id, paths: Object.keys(set).map(key => `${kind}.${key}`) })), ...carried]
153
+ .slice(0, 8).map(({ id, paths }) => ({ id, after: { patched: elementReadback(kind, elementTarget(kind, value, id, receipt.host.sceneId), paths, value) } })) };
138
154
  }
139
155
  export function registerElementSet(registry, ports, declaration) {
140
156
  const kind = declaration.id.slice(0, declaration.id.indexOf('.'));
141
- const { normalize, collection } = kinds.get(kind);
157
+ const { normalize, collection, carry, warn } = kinds.get(kind);
142
158
  registry.register({ ...declaration, available: () => true, run(args) {
143
159
  const domain = ports.storeDomain(declaration.undoDomain ?? kind);
144
160
  let next = domain.read();
145
161
  const ops = collection ? args.ops ?? [args] : [{ id: ports.state().activeSceneId, set: args }];
162
+ const carried = new Set(), warnings = [];
146
163
  for (const { id, set } of ops) {
147
164
  const before = collection ? next.find(row => row.id === id) : next;
148
165
  if (!before) throw new StudioProtocolError('TARGET_NOT_READY', `Unknown ${kind} id: ${id}`);
149
166
  const after = normalize(mergeElementSet(before, storedElementSet(kind, set)));
167
+ if (warn) warnings.push(...warn({ ...after, id }, set));
168
+ // A kind whose rows hang off each other (a group's parts) moves the rows its target carries.
169
+ if (collection && carry) {
170
+ const rows = carry(next, id, after);
171
+ rows.forEach((row, index) => { if (row !== next[index] && row.id !== id) carried.add(row.id); });
172
+ next = rows;
173
+ }
150
174
  next = collection ? next.map(row => row.id === id ? { ...after, id } : row) : after;
151
175
  }
152
176
  if (JSON.stringify(next) !== JSON.stringify(domain.read())) domain.write(next);
153
- return { affectedIds: [...new Set(ops.map(op => op.id))], summary: `Updated ${kind}.` };
177
+ return { affectedIds: [...new Set([...ops.map(op => op.id), ...carried])], summary: `Updated ${kind}.`, ...(warnings.length ? { warnings } : {}) };
154
178
  } });
155
179
  }
@@ -8,6 +8,7 @@ import { createStudioActionRegistry, STUDIO_ACTIONS } from "../studio-actions.js
8
8
  import * as shot from "./shot.js";
9
9
  import * as cast from "./cast.js";
10
10
  import * as motion from "./motion.js";
11
+ import * as motion3 from "./motion3.js";
11
12
  import * as objects from "./objects.js";
12
13
  import * as view from "./view.js";
13
14
  import * as scene from "./scene.js";
@@ -15,8 +16,9 @@ import * as project from "./project.js";
15
16
  import * as exporting from "./export.js";
16
17
  import * as ai from "./ai.js";
17
18
  import * as stage from "./stage.js";
19
+ import * as kits from "./kits.js";
18
20
 
19
- export const COMMAND_MODULES = Object.freeze({ shot, cast, motion, objects, view, scene, project, export: exporting, ai, stage });
21
+ export const COMMAND_MODULES = Object.freeze({ shot, cast, motion, motion3, objects, kits, view, scene, project, export: exporting, ai, stage });
20
22
 
21
23
  export function commandDeclarations(modules = COMMAND_MODULES) {
22
24
  return Object.values(modules).flatMap(module => module.declarations.map(declaration => ({
@@ -0,0 +1,37 @@
1
+ // object.createKit: a ready-made prop (src/kits/) built as one group, an Empty root at the grip
2
+ // plus its parts, written as one undo entry. It is a job so the answer carries its output:
3
+ // { rootId, parts: { role: id }, bind, props?, skill? }. bind is the spec.objects snippet a take
4
+ // pastes: the root's carry (at/along pin it to the prop origin) and the switch entries of the
5
+ // parts that light up (docs/motion-api-v3.1.md C15).
6
+ import { buildKit, KIT_NAMES } from "../kits/index.js";
7
+ import { fail } from "./shared.js";
8
+
9
+ const KIT_HELP = `Create a ready-made, correctly scaled prop as one group (an Empty root at the grip plus its parts), one undo entry. kit: ${KIT_NAMES.join(", ")} (inspect_studio scope catalogue lists each with its parts). `
10
+ + "position { x, y, z, rot? } places the root (world m, yaw degrees; default the origin, lifted so nothing is under the floor); name names the root. "
11
+ + "Answers rootId, parts { role: id } and bind: the spec.objects snippet to paste into a motion spec (the root's carry: prop, at, along, plus switch entries { visible, level } that show the lit parts). "
12
+ + "Kits without a skill also answer props (the grip prop to paste into spec.props). The cigarette, lighter and exhale need no paste: smokeCigarette args.kit { cig, lighter, exhale? } adds the entries itself "
13
+ + "(exhale: a puff that follows the mouth, no placing). "
14
+ + "Example: { kit: \"cigarette\" } -> { rootId: \"empty-3\", parts: { body: \"sculpt-2\", ember: \"sphere\", light: \"point-light\", smoke: \"effect\" }, bind: { \"empty-3\": { prop: \"cig\", at: [0, 0, 0], along: [0, 0, 1], visible: \"cig_vis\" }, "
15
+ + "sphere: { visible: \"ember_vis\", level: \"ember\" }, \"point-light\": { visible: \"ember_vis\", level: \"ember\" }, effect: { visible: \"ember_vis\" } } }; "
16
+ + "then motion.compile use [{ skill: \"smokeCigarette\", f0: 0, args: { kit: { cig: \"empty-3\", lighter: \"<lighter rootId>\" } } }].";
17
+
18
+ const number = { type: "number" };
19
+ const input = (properties, required = Object.keys(properties)) => ({ type: "object", properties, required, additionalProperties: false });
20
+
21
+ export const declarations = Object.freeze([
22
+ { id: "object.createKit", label: "Create a prop kit", kind: "job", domain: "objects", description: KIT_HELP,
23
+ input: input({ kit: { type: "string", enum: KIT_NAMES }, name: { type: "string", minLength: 1, maxLength: 120 }, position: input({ x: number, y: number, z: number, rot: number }, []) }, ["kit"]) },
24
+ ]);
25
+
26
+ export function register(registry, ports) {
27
+ const owned = () => ports.storeDomain("objects");
28
+ const available = () => Boolean(ports.storeDomain?.("objects")) || "The objects document owner is not mounted.";
29
+ registry.register({ ...declarations[0], available, run({ kit, name, position }, context) {
30
+ const before = owned().read(), built = buildKit(kit, before, { name, position });
31
+ if (built.error) fail("INVALID_ARGUMENT", built.error);
32
+ const write = () => owned().write([...before, ...built.records]);
33
+ if (context?.commit) context.commit(write); else write();
34
+ const { records, ...output } = built, ids = records.map(row => row.id);
35
+ return { affectedIds: ids, output, summary: `Created the ${kit} kit ${built.rootId} (${Object.entries(built.parts).map(([role, id]) => `${role} ${id}`).join(", ")}).` };
36
+ } });
37
+ }
@@ -0,0 +1,57 @@
1
+ // Timing edits on an installed take (take-edit.js): cut it into segments, play a segment faster
2
+ // or slower, drop one, trim the ends, or restore the full take. The compiled take stays as it
3
+ // is; the edit rides its descriptor, so Undo, save/load and export all see the same timing.
4
+ // IK correction keys pinned to timeline frames move with the poses they address.
5
+ import { fail, characterOf } from "./shared.js";
6
+ import { createTakeEdit, isIdentityEdit, remapFrameKeys, removeSegment, setSegmentSpeed, splitTakeEdit, takeEditDuration, takeEditLayout, trimTakeEdit } from "../motion3/take-edit.js";
7
+
8
+ const id = { type: "string", minLength: 1 }, frame = { type: "integer", minimum: 0 };
9
+ const timing = (actionId, label, description, properties) => ({ id: actionId, label, description, kind: "mutation", undoDomain: "motion",
10
+ input: { type: "object", properties: { characterId: id, ...properties }, required: ["characterId", ...Object.keys(properties)], additionalProperties: false } });
11
+ const SEGMENTS = "The receipt output lists the segments ({ id, sourceStart, sourceEnd, speed, timelineStart, timelineEnd }) and the new frame count.";
12
+
13
+ export const timingDeclarations = [
14
+ timing("motion.cut", "Cut motion segment", `Cut the installed take into two segments at a timeline frame, so each part can get its own speed. ${SEGMENTS}`, { frame }),
15
+ timing("motion.setSegmentSpeed", "Set segment speed", `Play one segment faster or slower: 0.1-4 in 0.1 steps (0.5 = half-speed slow motion, 2 = double speed). The timeline grows or shrinks with it. ${SEGMENTS}`,
16
+ { segmentId: id, speed: { type: "number", minimum: 0.1, maximum: 4 } }),
17
+ timing("motion.removeSegment", "Remove motion segment", `Drop one segment from the take; the rest close the gap. ${SEGMENTS}`, { segmentId: id }),
18
+ timing("motion.trim", "Trim motion", `Keep timeline frames start..end (inclusive) of the take. ${SEGMENTS}`, { start: frame, end: frame }),
19
+ timing("motion.resetTiming", "Restore full take timing", `Undo every cut, speed change and trim: the take plays as compiled. ${SEGMENTS}`, {}),
20
+ ];
21
+
22
+ /** The take descriptor with this edit; an identity edit leaves no key (authored intent stays plain). */
23
+ function withEdit(take, edit, frames) {
24
+ const next = { ...take };
25
+ delete next.edit;
26
+ return isIdentityEdit(edit, frames) ? next : { ...next, edit };
27
+ }
28
+
29
+ function nextEdit(action, edit, args, frames) {
30
+ const known = () => edit.some(segment => segment.id === args.segmentId) || fail("INVALID_ARGUMENT", `No segment ${args.segmentId}; segments: ${edit.map(segment => segment.id).join(", ")}.`);
31
+ if (action === "motion.cut") {
32
+ const next = splitTakeEdit(edit, args.frame);
33
+ return next !== edit ? next : fail("INVALID_RANGE", `Frame ${args.frame} is not inside a segment (0 < frame < ${takeEditDuration(edit)}, not on a segment start).`);
34
+ }
35
+ if (action === "motion.setSegmentSpeed") { known(); return setSegmentSpeed(edit, args.segmentId, args.speed); }
36
+ if (action === "motion.removeSegment") { known(); return edit.length > 1 ? removeSegment(edit, args.segmentId) : fail("INVALID_ARGUMENT", "Use motion.clear to remove the last segment."); }
37
+ if (action === "motion.trim") {
38
+ try { return trimTakeEdit(edit, args.start, args.end); } catch (error) { return fail("INVALID_RANGE", error.message); }
39
+ }
40
+ return createTakeEdit(frames);
41
+ }
42
+
43
+ export function registerTiming(registry, ports, available) {
44
+ const owner = () => ports.storeDomain("motion");
45
+ for (const declaration of timingDeclarations) registry.register({ ...declaration, available, run(args) {
46
+ const { characterId } = args;
47
+ characterOf(ports, characterId);
48
+ const row = owner().layer(characterId), full = owner().fullMotionFor(characterId);
49
+ if (!row.take || !full) fail("TARGET_NOT_READY", "Load a take for this character first.");
50
+ const previous = row.take.edit ?? createTakeEdit(full.frames), edit = nextEdit(declaration.id, previous, args, full.frames);
51
+ if (edit !== previous) {
52
+ owner().writeLayer(characterId, { take: withEdit(row.take, edit, full.frames), ikKeys: remapFrameKeys(row.ikKeys, previous, edit) });
53
+ ports.storeDomain("cast")?.syncTimeline?.();
54
+ }
55
+ return { affectedIds: [characterId], summary: declaration.label, output: { frames: takeEditDuration(edit), segments: takeEditLayout(edit) } };
56
+ } });
57
+ }
@@ -2,16 +2,11 @@
2
2
  import { STUDIO_IK_CHAIN_TRACKS, studioActionDeclaration } from "../studio-actions.js";
3
3
  import { fail, characterOf } from "./shared.js";
4
4
  import { elementSetSchema, registerElementSet } from './elements.js';
5
+ import { registerTiming, timingDeclarations } from './motion-timing.js';
5
6
  import './elements/motion.js';
6
7
  import './elements/character.js';
7
- import { generationArgs } from '../motion/generation.js';
8
- import { applyRootDrop, normalizeRootDrop } from '../ardy/root-drop.js';
9
- import { characterScaleFor } from '../ardy/npz.js';
10
- import { I2V_MOTION_DURATIONS } from '../i2v-motion-client.js';
11
- import { createMotionEdit, trimMotionEdit, splitMotionEdit, setMotionSegmentSpeed, removeMotionSegment } from '../ardy/motion-edit.js';
12
8
  const id = { type: 'string', minLength: 1 }, frame = { type: 'integer', minimum: 0 };
13
9
  const input = (properties, required = Object.keys(properties)) => ({ type: 'object', properties, required, additionalProperties: false });
14
- const drop = { oneOf: [{ type: 'null' }, input({ from_s: { type: 'number', minimum: 0 }, to_s: { type: 'number', exclusiveMinimum: 0 }, meters: { type: 'number', exclusiveMinimum: 0, maximum: 30 } })] };
15
10
  const mutation = (id, label, properties, required) => ({ id, label, description: label, kind: 'mutation', undoDomain: 'motion', input: input(properties, required) });
16
11
  const setInput = elementSetSchema('motion');
17
12
  // A collection transaction has no single native character target. Normalize
@@ -26,108 +21,28 @@ const rangePins = [
26
21
  mutation('motion.rangePin.apply', 'Apply range pin', { characterId: id, pin: rangePin, replaceExisting: { type: 'boolean', default: false } }, ['characterId', 'pin']),
27
22
  mutation('motion.rangePin.remove', 'Remove range pin', { characterId: id, pinId: id }),
28
23
  ];
29
- const platformFit = [
30
- { id: 'motion.platformFit.run', label: 'Fit motion to platforms', description: 'Analyse scene platforms and prepare or apply an IK correction.', kind: 'job', domain: 'motion', timeoutMs: 300000, input: input({ characterId: id, preview: { type: 'boolean', default: false } }, ['characterId']) },
31
- { id: 'motion.platformFit.remove', label: 'Remove platform fit', description: 'Apply or remove the reviewed platform-fit correction.', kind: 'job', domain: 'motion', timeoutMs: 300000, input: input({ characterId: id, apply: { type: 'boolean', default: false } }, ['characterId']) },
32
- ];
33
- const edits = [
34
- { ...mutation('motion.set', 'Set take fields', {}), input: setInput },
35
- mutation('motion.trim', 'Trim motion', { characterId: id, start: frame, end: frame }),
36
- mutation('motion.resetTrim', 'Restore full take', { characterId: id }),
37
- mutation('motion.cut', 'Cut motion segment', { characterId: id, frame }),
38
- mutation('motion.setSegmentSpeed', 'Set segment speed', { characterId: id, id, speed: { type: 'number', minimum: 0.05, maximum: 8 } }),
39
- mutation('motion.removeSegment', 'Remove motion segment', { characterId: id, id }),
40
- mutation('motion.fixCollisions', 'Fix body collisions', { characterId: id, scope: { type: 'string', enum: ['frame', 'clip'], default: 'frame' } }, ['characterId']),
41
- ];
42
- const generate = { id: 'motion.generate', label: 'Generate motion', description: 'Generate the named character through the editor pipeline, following its prompt blocks, root path, pose pins and take lineage.',
43
- kind: 'job', domain: 'motion', generation: 'motion', background: true, timeoutMs: 1000,
44
- input: input({ characterId: id, drop, blocks: elementSetSchema('character').properties.set.properties.layer.properties.promptClips,
45
- durationSeconds: { type: 'number', minimum: 1, maximum: 1200 }, seed: { type: 'integer', minimum: 0, maximum: 2147483647 } }, ['characterId']) };
46
- const prepared = { ...mutation('motion.applyPrepared', 'Apply prepared motion edit', { characterId: id, token: id }), exposure: 'ui-only' };
47
- const loads = [
48
- { id: 'motion.replace', label: 'Replace take', input: input({ characterId: id, url: id, prompt: { type: 'string', default: '' },
49
- blocks: { type: 'array', maxItems: 120, items: input({ startFrame: frame, endFrame: { ...frame, minimum: 1 }, prompt: { type: 'string' } }) },
50
- drop,
51
- }, ['characterId', 'url']) },
52
- { id: 'motion.loadVersion', label: 'Restore take version', input: input({ characterId: id, motionUrl: id }) },
53
- ].map(entry => ({ ...entry, description: entry.label, kind: 'job', domain: 'motion' }));
24
+ const edits = [{ ...mutation('motion.set', 'Set take fields', {}), input: setInput }];
54
25
  const tools = [
55
- mutation('motion.applyPhysics', 'Apply reviewed physics', { characterId: id }),
56
- mutation('motion.editTrail', 'Edit motion trail', { characterId: id, track: { type: 'string', enum: ['hips', 'leftFoot', 'rightFoot', 'leftHand', 'rightHand', 'head'], default: 'hips' }, grabFrame: frame, radiusFrames: { ...frame, minimum: 1 }, delta: input({ x: { type: 'number' }, y: { type: 'number' }, z: { type: 'number' } }) }, ['characterId', 'grabFrame', 'radiusFrames', 'delta']),
57
26
  mutation('ik.applyPose', 'Key full-body pose', { characterId: id, frame, pose: { type: 'object', properties: { bones: { type: 'object', properties: {}, required: [], additionalProperties: true }, rootY: { type: 'number' } }, required: ['bones'], additionalProperties: true } }),
58
27
  ];
59
- const physics = { id: 'motion.autoPhysics', label: 'Review motion physics', description: 'Analyse real rig motion and optionally apply one retained correction.', kind: 'job', domain: 'motion',
60
- input: input({ characterId: id, apply: { type: 'boolean', default: true }, strength: { type: 'number', minimum: 0, maximum: 1, default: 1 },
61
- protectedFrames: { type: 'array', items: frame, default: [] }, overrides: { type: 'array', default: [], items: input({
62
- site: { type: 'string', enum: ['leftFoot', 'rightFoot', 'leftHand', 'rightHand', 'leftKnee', 'rightKnee'] }, start: frame, end: frame,
63
- mode: { type: 'string', enum: ['plant', 'free'] },
64
- }) },
65
- }, ['characterId']) };
66
28
  // These only queue the existing editor producers. Their eventual take
67
29
  // publication is owned above; generation-pipeline unification remains #444.
68
- const queued = ['motion.commitLineEdit', 'motion.regenerateTrail'].map(id => ({ id, label: id === 'motion.commitLineEdit' ? 'Commit line edit' : 'Regenerate trail edit',
69
- description: 'Queue the current editor draft through the existing generation producer.', kind: 'transient', exposure: 'ui-only', input: input({ characterId: { type: 'string' } }) }));
70
30
  const legacyIk = ['character.setIkKey', 'character.removeIkKey', 'character.clearIkKeys'].map(studioActionDeclaration);
71
31
  const ik = legacyIk.map((entry, index) => ({ ...entry, id: ['ik.setKey', 'ik.removeKey', 'ik.clearKeys'][index] }));
72
32
 
73
- const clear = { id: 'motion.clear', label: 'Clear motion', description: 'Clear the active take, its corrections, prompt blocks and take-owned cast fields.',
33
+ const clear = { id: 'motion.clear', label: 'Clear motion', description: 'Clear the character\'s compiled take and its IK corrections.',
74
34
  kind: 'mutation', undoDomain: 'motion', input: { type: 'object', properties: { characterId: { type: 'string' } }, required: ['characterId'], additionalProperties: false } };
75
- const videoDraft = { id: 'motion.setVideoDraft', label: 'Set video motion draft', description: 'Set the uncommitted AI-video form without changing the project or its history.', kind: 'transient',
76
- input: input({ instruction: { oneOf: [{ const: '' }, { type: 'string', maxLength: 3700 }] }, promptOverride: { oneOf: [{ const: '' }, { type: 'string', maxLength: 3700 }] }, duration: { oneOf: I2V_MOTION_DURATIONS.map(value => ({ const: value })) } }, []) };
77
- export const declarations = Object.freeze([videoDraft, generate, ...queued, prepared, ...loads, physics, ...tools, ...rangePins, ...platformFit, ...edits, ...legacyIk, ...ik, clear, ...["motion.generateAllBlocks", "motion.generateFromVideo"].map(studioActionDeclaration)]);
35
+ export const declarations = Object.freeze([...tools, ...rangePins, ...edits, ...timingDeclarations, ...legacyIk, ...ik, clear]);
78
36
 
79
37
  export function register(registry, ports) {
80
38
  const owner = () => ports.storeDomain('motion');
81
39
  const mounted = () => Boolean(ports.storeDomain?.('motion')) || 'The motion owner is not mounted.';
82
40
  const take = characterId => { characterOf(ports, characterId); return owner().motionFor(characterId) ?? fail('TARGET_NOT_READY', 'Load a take for this character first.'); };
83
- registry.register({ ...videoDraft, available: mounted, run(args) {
84
- owner().setVideoDraft(args);
85
- return { affectedIds: [], summary: videoDraft.label };
86
- } });
87
- registry.registerToolAlias('generate_motion', generate.id, generationArgs);
88
- registry.register({ ...generate, available: mounted, target: args => args.characterId, async run(args, context) {
89
- characterOf(ports, args.characterId);
90
- if (owner().isGenerating()) fail('TARGET_BUSY', 'A motion generation is already running.');
91
- if (args.drop && !normalizeRootDrop(args.drop)) fail('INVALID_ARGUMENT', 'Invalid drop.');
92
- if (args.blocks) context.run('character.setPromptBlocks', { characterId: args.characterId, blocks: args.blocks });
93
- const generationContext = !args.drop ? context : { ...context, commit: apply => context.commit(() => {
94
- const result = apply(), take = owner().motionFor(args.characterId), layer = owner().layer(args.characterId);
95
- owner().replace(args.characterId, applyRootDrop(take, args.drop, { worldScale: characterScaleFor(take) }), {
96
- recipe: layer.takeRecipe, versions: layer.takeVersions, ikKeys: layer.ikKeys,
97
- });
98
- owner().writeLayer(args.characterId, { committedIkEdits: layer.committedIkEdits });
99
- return result;
100
- }) };
101
- await owner().generate(args, generationContext);
102
- return { affectedIds: [args.characterId], summary: 'Generated motion.' };
103
- } });
104
- for (const declaration of queued) registry.register({ ...declaration, available: mounted, run({ characterId }) {
105
- characterOf(ports, characterId);
106
- if (ports.state().activeCharacterId !== characterId) fail('TARGET_NOT_READY', 'Select the character that owns this editor draft.');
107
- if (declaration.id === 'motion.commitLineEdit') owner().requestLineEdit(); else owner().requestTrailRegeneration();
108
- return { affectedIds: [], summary: declaration.label };
109
- } });
110
- registry.register({ ...prepared, available: mounted, run({ characterId, token }) {
111
- characterOf(ports, characterId); const result = owner().applyPrepared(token);
112
- return { affectedIds: result?.affectedIds ?? [characterId], summary: prepared.label };
113
- } });
114
- for (const declaration of loads) registry.register({ ...declaration, available: mounted, target: args => args.characterId, async run(args, context) {
115
- characterOf(ports, args.characterId); await owner().loadRemote(args, context);
116
- return { affectedIds: [args.characterId], summary: declaration.label };
117
- } });
118
- registry.register({ ...physics, available: mounted, target: args => args.characterId, async run(args, context) {
119
- characterOf(ports, args.characterId);
120
- return { affectedIds: [args.characterId], summary: physics.label, output: await owner().autoPhysics(args.characterId, args, context) };
121
- } });
122
41
  for (const declaration of tools) registry.register({ ...declaration, available: mounted, run(args) {
123
42
  characterOf(ports, args.characterId);
124
- if (declaration.id === 'motion.applyPhysics') owner().applyPhysics(args.characterId);
125
- else if (declaration.id === 'motion.editTrail') owner().editTrail(args.characterId, args);
126
- else {
127
- if (args.frame >= ports.state().frameCount) fail('INVALID_RANGE', 'IK frame is outside the timeline.');
128
- for (const angles of Object.values(args.pose.bones)) if (!Array.isArray(angles) || angles.length !== 3 || !angles.every(Number.isFinite)) fail('INVALID_ARGUMENT', 'Pose bones require three finite rotation angles.');
129
- owner().keyPose(args.characterId, args.frame, args.pose);
130
- }
43
+ if (args.frame >= ports.state().frameCount) fail('INVALID_RANGE', 'IK frame is outside the timeline.');
44
+ for (const angles of Object.values(args.pose.bones)) if (!Array.isArray(angles) || angles.length !== 3 || !angles.every(Number.isFinite)) fail('INVALID_ARGUMENT', 'Pose bones require three finite rotation angles.');
45
+ owner().keyPose(args.characterId, args.frame, args.pose);
131
46
  return { affectedIds: [args.characterId], summary: declaration.label };
132
47
  } });
133
48
  for (const declaration of rangePins) registry.register({ ...declaration, available: mounted, run(args) {
@@ -137,37 +52,11 @@ export function register(registry, ports) {
137
52
  : owner().removePin(args.characterId, args.pinId);
138
53
  return { affectedIds: [args.characterId], summary: declaration.label, ...(output ? { output } : {}) };
139
54
  } });
140
- registry.register({ ...platformFit[0], available: mounted, target: args => args.characterId, async run(args, context) {
141
- characterOf(ports, args.characterId);
142
- const output = await owner().platformFitRun(args.characterId, context, { preview: args.preview === true });
143
- return { affectedIds: [args.characterId], summary: platformFit[0].label, output };
144
- } });
145
- registry.register({ ...platformFit[1], available: mounted, target: args => args.characterId, run(args, context) {
146
- characterOf(ports, args.characterId);
147
- const output = owner().platformFitRemove(args.characterId, context, { apply: args.apply === true });
148
- return { affectedIds: [args.characterId], summary: platformFit[1].label, ...(output ? { output } : {}) };
149
- } });
150
55
  registerElementSet({ register(entry) { registry.register({ ...entry, available: mounted, run(args) {
151
56
  for (const op of args.ops ?? [args]) take(op.id);
152
57
  return entry.run(args);
153
58
  } }); } }, ports, edits[0]);
154
- for (const declaration of edits.slice(1)) registry.register({ ...declaration, available: mounted, run(args) {
155
- const { characterId } = args; characterOf(ports, characterId);
156
- if (declaration.id === 'motion.fixCollisions') owner().fix(characterId, args.scope);
157
- else {
158
- const current = take(characterId), full = owner().fullMotionFor(characterId);
159
- let segments = current.editSegments;
160
- if (declaration.id === 'motion.trim') {
161
- if (args.start > args.end || args.end >= current.frames) fail('INVALID_RANGE', 'Trim range is outside the take.');
162
- segments = trimMotionEdit(segments, args.start, args.end);
163
- } else if (declaration.id === 'motion.resetTrim') segments = createMotionEdit(full.frames);
164
- else if (declaration.id === 'motion.cut') segments = splitMotionEdit(segments, args.frame);
165
- else if (declaration.id === 'motion.setSegmentSpeed') segments = setMotionSegmentSpeed(segments, args.id, args.speed);
166
- else { if (segments.length <= 1) fail('INVALID_ARGUMENT', 'Use motion.clear to remove the final segment.'); segments = removeMotionSegment(segments, args.id); }
167
- if (segments !== current.editSegments) owner().editSegments(characterId, segments);
168
- }
169
- return { affectedIds: [characterId], summary: declaration.label };
170
- } });
59
+ registerTiming(registry, ports, mounted);
171
60
  for (const [index, declaration] of [...legacyIk, ...ik].entries()) registry.register({ ...declaration,
172
61
  available: state => state.characters.length > 0 || 'There are no characters in this scene.', run(args) {
173
62
  const character = characterOf(ports, args.characterId), kind = index % 3;
@@ -194,38 +83,6 @@ export function register(registry, ports) {
194
83
  if (ports.state().activeCharacterId !== characterId) fail('TARGET_NOT_READY', 'Select this character before clearing its take.');
195
84
  ports.clearMotionNative();
196
85
  }
197
- return { affectedIds: [characterId], summary: 'Cleared motion and prompt blocks.' };
198
- } });
199
- registry.register({ ...studioActionDeclaration("motion.generateAllBlocks"), target: () => ports.state().activeCharacterId,
200
- available: state => state.generating ? "A motion generation is already running."
201
- : !state.motionReady ? "The motion backend is not ready."
202
- : state.promptBlockCount === 0 ? "The active character has no prompt block with text; write them with patch_elements character.promptBlocks." : true,
203
- run: (_args, context) => {
204
- const { activeCharacterId, promptBlockCount } = ports.state();
205
- const shown = ports.runAllPromptBlocks(context) ?? [];
206
- if (shown?.then) return shown.then(() => ({ affectedIds: activeCharacterId ? [activeCharacterId] : [], summary: `Generated motion from ${promptBlockCount} prompt blocks.` }));
207
- // The generation queues synchronously or not at all; when it does not,
208
- // the editor's last toast names the refusal (rig not loaded, a root
209
- // waypoint outside the clip, an over-long block, a line-edit draft).
210
- if (!ports.state().generating) fail("TARGET_NOT_READY", shown.length ? `Generation not started: ${shown.at(-1)}` : "The editor did not start the generation; check the active character's rig and prompt blocks.");
211
- return { affectedIds: activeCharacterId ? [activeCharacterId] : [], summary: `Started generating the active character's motion from ${promptBlockCount} prompt block${promptBlockCount === 1 ? "" : "s"}.` };
212
- } });
213
- // AI-video motion: the agent panel's Generate motion (generateI2vMotion
214
- // "act"), awaited to its clip. The AI motion card shows every failure it meets, so
215
- // a refusal is silent in the UI and tells the model the reason in English.
216
- registry.register({ ...studioActionDeclaration("motion.generateFromVideo"), domain: "motion", target: () => ports.state().activeCharacterId,
217
- available: ({ i2vMotion }) => !i2vMotion.enabled ? "AI video motion (Fal) is not enabled for this account."
218
- : !["idle", "done", "error", "failed"].includes(i2vMotion.status) ? "An AI video motion generation is already running; wait for it to finish."
219
- : i2vMotion.dailyRemaining === 0 ? "The account's daily AI video generations are used up." : true,
220
- run: async ({ instruction }, context) => {
221
- const outcome = await ports.generateI2vMotion("act", instruction, context);
222
- if (outcome.failed) fail("TARGET_NOT_READY", outcome.failed);
223
- const { job, footage, dailyRemaining } = outcome;
224
- if (!job.video?.url) fail("TARGET_NOT_READY", "The AI video model finished without returning a video.");
225
- return { affectedIds: [], output: { videoUrl: job.video.url, resolution: job.resolution ?? null, durationSeconds: job.resultDuration ?? job.duration ?? null,
226
- ingested: Boolean(footage), frames: footage?.frames ?? null, fps: footage?.fps ?? null, dailyRemaining },
227
- summary: footage
228
- ? `The AI video (${job.resolution}, ${footage.frames} frames at ${footage.fps} fps) is ingested as Video capture footage and the timeline now spans it; its motion becomes a take once GVHMR extraction runs in the Video capture panel.`
229
- : `The AI video is ready at ${job.video.url}, but ingesting it as footage failed; the Video capture panel shows why.` };
86
+ return { affectedIds: [characterId], summary: 'Cleared motion.' };
230
87
  } });
231
88
  }
@@ -0,0 +1,65 @@
1
+ // Where a motion compile runs. In the browser: one module Web Worker (motion3/compile-worker.js), started
2
+ // on the first compile and reused, jobs answered in arrival order, so a compile costs the worker's time
3
+ // and never blocks the viewport (the main thread runs the compiler ~4x slower and freezes while it does).
4
+ // Where there is no Worker (Node, tests) or the worker fails: the same job on the calling thread,
5
+ // synchronously, so `registry.run("motion.compile", ...)` there still answers at once. The answer is the
6
+ // same either way (compile-job.js runs the same code on the same plain inputs).
7
+ import { runCompileJob } from "../motion3/compile-job.js";
8
+ import { StudioProtocolError } from "../studio-agent-protocol.js";
9
+
10
+ /** A job error (or a worker's serialized one) as the Studio's protocol error when it carries a code. */
11
+ function rethrow(error) {
12
+ if (error?.code && !(error instanceof StudioProtocolError)) throw new StudioProtocolError(error.code, error.message);
13
+ if (error instanceof Error) throw error;
14
+ throw Object.assign(new Error(error?.message ?? String(error)), { name: error?.name ?? "Error" });
15
+ }
16
+ const local = job => { try { return runCompileJob(job); } catch (error) { return rethrow(error); } };
17
+
18
+ /** The browser's module worker, or null where there is none. */
19
+ function browserWorker() {
20
+ if (typeof Worker !== "function") return null;
21
+ return new Worker(new URL("../motion3/compile-worker.js", import.meta.url), { type: "module", name: "motion-compile" });
22
+ }
23
+
24
+ /**
25
+ * createCompileRunner({ spawn }) -> { run(job) -> answer | Promise<answer>, takeWarning() -> string | null }
26
+ * spawn() -> a Worker-like object (postMessage, onmessage, onerror, terminate) or null for the calling thread.
27
+ */
28
+ export function createCompileRunner({ spawn = browserWorker } = {}) {
29
+ let worker, dead = false, warning = null, next = 0;
30
+ const pending = new Map();
31
+ /** The worker failed (would not start, or crashed): every job from now on, and those in flight, run here. */
32
+ const fallBack = reason => {
33
+ dead = true;
34
+ worker?.terminate?.();
35
+ worker = null;
36
+ warning ??= `The motion compile worker stopped (${reason}); compiling on the main thread, which is slower and blocks the editor while it runs.`;
37
+ for (const [id, { job, resolve, reject }] of pending) { pending.delete(id); try { resolve(local(job)); } catch (error) { reject(error); } }
38
+ };
39
+ const started = () => {
40
+ if (worker || dead) return worker;
41
+ try { worker = spawn(); } catch (error) { fallBack(error?.message ?? String(error)); return null; }
42
+ if (!worker) { dead = true; return null; }
43
+ worker.onmessage = ({ data: { id, value, error } }) => {
44
+ const job = pending.get(id);
45
+ if (!job) return;
46
+ pending.delete(id);
47
+ if (error) { try { rethrow(error); } catch (thrown) { job.reject(thrown); } } else job.resolve(value);
48
+ };
49
+ worker.onerror = event => { event?.preventDefault?.(); fallBack(event?.message || "worker error"); };
50
+ worker.onmessageerror = () => fallBack("an answer could not be read");
51
+ return worker;
52
+ };
53
+ return {
54
+ run(job) {
55
+ if (!started()) return local(job);
56
+ return new Promise((resolve, reject) => {
57
+ const id = ++next;
58
+ pending.set(id, { job, resolve, reject });
59
+ try { worker.postMessage({ id, job }); } catch (error) { fallBack(error?.message ?? String(error)); }
60
+ });
61
+ },
62
+ /** The fallback note, once (a receipt warning), or null. */
63
+ takeWarning() { const note = warning; if (note) warning = ""; return note || null; },
64
+ };
65
+ }