cozyclay 2.1.1 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (585) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/README.md +12 -30
  3. package/bin/agent/agent-routes.mjs +43 -116
  4. package/bin/agent/agent-runner.mjs +14 -15
  5. package/bin/agent/cliproxy-provider.mjs +135 -0
  6. package/bin/agent/credential-store.mjs +30 -7
  7. package/bin/agent/pi-tools.mjs +4 -1
  8. package/bin/agent/provider-keys.mjs +32 -0
  9. package/bin/agent/provider-login.mjs +110 -0
  10. package/bin/agent/provider-routes.mjs +99 -0
  11. package/bin/agent/providers.mjs +19 -97
  12. package/bin/agent/script-tool.mjs +17 -4
  13. package/bin/agent/session-store.mjs +2 -2
  14. package/bin/agent/studio-context-budget.mjs +93 -0
  15. package/bin/agent/studio-motion-reference.mjs +23 -0
  16. package/bin/agent/studio-prompt.mjs +1 -1
  17. package/bin/agent/studio-rebase-text.mjs +32 -0
  18. package/bin/agent/studio-rebase.mjs +26 -39
  19. package/bin/agent/studio-tools.mjs +9 -28
  20. package/bin/agent/tool-arguments.mjs +49 -0
  21. package/bin/agent/turn-frames.mjs +66 -0
  22. package/bin/codex-auth.mjs +9 -2
  23. package/bin/cozyclay.mjs +10 -116
  24. package/bin/live/cli.mjs +3 -3
  25. package/bin/mcp-runtime.mjs +2 -12
  26. package/bin/package-signature.mjs +0 -4
  27. package/dist/app/index.html +7 -6
  28. package/dist/assets/EXRLoader-CvVIvGVE.js +1 -0
  29. package/dist/assets/analytics-BWCqdgWA.js +1 -0
  30. package/dist/assets/app-DfTCxaKk.js +5087 -0
  31. package/dist/assets/app-f-fkV5uP.css +1 -0
  32. package/dist/assets/compile-worker-BxKXh07s.js +5 -0
  33. package/dist/assets/fflate.module-KbGj5-H6.js +5 -0
  34. package/dist/assets/{first-shot-handoff-msLI71g1.js → first-shot-handoff-DNO8kv_N.js} +1 -1
  35. package/dist/assets/{landing-ChUvB_zL.js → landing-yfZaJkWc.js} +1 -1
  36. package/dist/assets/scene-asset-sync-U3kS2rvK.js +20 -0
  37. package/dist/assets/scene-asset-sync-_S8NQl5B.css +1 -0
  38. package/dist/assets/workflow-D78eiKTa.js +179 -0
  39. package/dist/cozyclay-package.json +1 -1
  40. package/dist/index.html +3 -3
  41. package/dist/sw.js +3 -6
  42. package/dist/workflow/index.html +5 -4
  43. package/mcp/LIVE-PROTOCOL.md +7 -47
  44. package/mcp/README.md +16 -26
  45. package/mcp/live-hub.mjs +6 -10
  46. package/mcp/package.json +1 -3
  47. package/mcp/tool-handlers.mjs +4 -168
  48. package/package.json +3 -22
  49. package/src/App.jsx +151 -2625
  50. package/src/agent-progress.js +80 -0
  51. package/src/agent-turn-mark.js +30 -0
  52. package/src/analytics.js +1 -19
  53. package/src/app-context.js +12 -2
  54. package/src/app-stage.jsx +2 -34
  55. package/src/asset-pane.jsx +6 -5
  56. package/src/camera-block.js +21 -0
  57. package/src/camera-direction.js +120 -0
  58. package/src/character-kind.js +3 -9
  59. package/src/command-bus.js +10 -8
  60. package/src/commands/cast.js +5 -34
  61. package/src/commands/elements/character.js +1 -1
  62. package/src/commands/elements/motion.js +1 -6
  63. package/src/commands/elements/object.js +6 -2
  64. package/src/commands/elements/shot.js +4 -1
  65. package/src/commands/elements.js +38 -14
  66. package/src/commands/index.js +3 -1
  67. package/src/commands/kits.js +37 -0
  68. package/src/commands/motion-timing.js +57 -0
  69. package/src/commands/motion.js +9 -152
  70. package/src/commands/motion3-compile.js +65 -0
  71. package/src/commands/motion3.js +214 -0
  72. package/src/commands/objects.js +6 -4
  73. package/src/commands/shot.js +55 -13
  74. package/src/commands/stage.js +14 -1
  75. package/src/commands/view.js +4 -1
  76. package/src/document-mirrors.js +42 -0
  77. package/src/document-store.js +4 -0
  78. package/src/domains/cast.js +19 -194
  79. package/src/domains/motion/motion-range-pin-rules.js +1 -1
  80. package/src/domains/motion/take-source.js +53 -0
  81. package/src/domains/motion.js +60 -1838
  82. package/src/domains/scenes.js +12 -55
  83. package/src/domains/shots.js +2 -2
  84. package/src/domains/stage.js +4 -2
  85. package/src/draft-ghost.js +47 -0
  86. package/src/draft-ghost.jsx +129 -0
  87. package/src/dualview.jsx +4 -3
  88. package/src/effects/EffectsLayer.jsx +112 -0
  89. package/src/effects/effect-meshes.js +232 -0
  90. package/src/effects/effect-record.js +103 -0
  91. package/src/effects/emitter.js +59 -0
  92. package/src/effects/simulate.js +261 -0
  93. package/src/effects/state.js +59 -0
  94. package/src/execution-telemetry.js +2 -3
  95. package/src/first-success-guide.jsx +5 -5
  96. package/src/ghost-set.js +257 -0
  97. package/src/held-object-edit.jsx +113 -0
  98. package/src/held-object-pose.js +175 -0
  99. package/src/hierarchy-model.js +1 -1
  100. package/src/hierarchy-panel.jsx +1 -1
  101. package/src/imported-mesh.jsx +103 -0
  102. package/src/kits/held.js +84 -0
  103. package/src/kits/index.js +70 -0
  104. package/src/kits/smoking.js +57 -0
  105. package/src/light-blockers.js +93 -0
  106. package/src/motion/beat-board-model.js +84 -0
  107. package/src/motion/beat-board.css +124 -0
  108. package/src/motion/beat-board.jsx +60 -0
  109. package/src/motion/center-of-mass.js +66 -0
  110. package/src/motion/fix-collisions.js +850 -0
  111. package/src/motion/physics-review.js +118 -0
  112. package/src/{ardy → motion}/physics-support.js +1 -76
  113. package/src/motion/rig-snapshot.js +22 -0
  114. package/src/motion/take-segments.jsx +140 -0
  115. package/src/{ardy → motion}/timeline-coordinates.js +0 -21
  116. package/src/{ardy → motion}/timeline.css +1 -2
  117. package/src/{ardy → motion}/timeline.jsx +42 -413
  118. package/src/motion/waypoints.js +76 -0
  119. package/src/motion3/beats.js +83 -0
  120. package/src/motion3/body.js +158 -0
  121. package/src/motion3/bones.js +47 -0
  122. package/src/motion3/carried.js +47 -0
  123. package/src/motion3/compile-job.js +79 -0
  124. package/src/motion3/compile-worker.js +10 -0
  125. package/src/motion3/compiler/anchors.js +113 -0
  126. package/src/motion3/compiler/arm-orient.js +128 -0
  127. package/src/motion3/compiler/channels.js +125 -0
  128. package/src/motion3/compiler/compile.js +205 -0
  129. package/src/motion3/compiler/gait-rhythm.js +97 -0
  130. package/src/motion3/compiler/grip.js +171 -0
  131. package/src/motion3/compiler/head-steady.js +48 -0
  132. package/src/motion3/compiler/hold.js +134 -0
  133. package/src/motion3/compiler/idle.js +113 -0
  134. package/src/motion3/compiler/limbs.js +197 -0
  135. package/src/motion3/compiler/math.js +72 -0
  136. package/src/motion3/compiler/pose.js +177 -0
  137. package/src/motion3/compiler/quat.js +49 -0
  138. package/src/motion3/compiler/skeleton.js +74 -0
  139. package/src/motion3/compiler/validate-common.js +32 -0
  140. package/src/motion3/compiler/validate-goals.js +118 -0
  141. package/src/motion3/compiler/validate-objects.js +144 -0
  142. package/src/motion3/compiler/validate-parts.js +118 -0
  143. package/src/motion3/compiler/validate.js +122 -0
  144. package/src/motion3/compiler/vocab.js +74 -0
  145. package/src/motion3/compiler/walk-pelvis.js +275 -0
  146. package/src/motion3/compiler/walk.js +207 -0
  147. package/src/motion3/compiler/wrist-clamp.js +32 -0
  148. package/src/motion3/delta.js +47 -0
  149. package/src/motion3/describe.js +177 -0
  150. package/src/motion3/docs.js +27 -0
  151. package/src/motion3/edit-carry.js +21 -0
  152. package/src/motion3/fix/ease.js +16 -0
  153. package/src/motion3/fix/ground.js +37 -0
  154. package/src/motion3/fix/index.js +52 -0
  155. package/src/motion3/fix/push.js +71 -0
  156. package/src/motion3/fix/reach.js +46 -0
  157. package/src/motion3/fix/retime.js +79 -0
  158. package/src/motion3/fix/self.js +211 -0
  159. package/src/motion3/fix/slide.js +70 -0
  160. package/src/motion3/fix/turn.js +51 -0
  161. package/src/motion3/follow.js +39 -0
  162. package/src/motion3/free-motion.js +204 -0
  163. package/src/motion3/grip-choice.js +130 -0
  164. package/src/motion3/hold-plan.js +157 -0
  165. package/src/motion3/json-patch.js +77 -0
  166. package/src/motion3/key-aim.js +105 -0
  167. package/src/motion3/key-edit.js +82 -0
  168. package/src/motion3/lenient.js +127 -0
  169. package/src/motion3/object-keys.js +236 -0
  170. package/src/motion3/object-pose.js +182 -0
  171. package/src/motion3/object-tracks.js +12 -0
  172. package/src/motion3/people.js +82 -0
  173. package/src/motion3/pipeline.js +102 -0
  174. package/src/motion3/playback.js +51 -0
  175. package/src/motion3/probes.js +91 -0
  176. package/src/motion3/qa/body-checks.js +34 -0
  177. package/src/motion3/qa/check-names.js +14 -0
  178. package/src/motion3/qa/checks.js +125 -0
  179. package/src/motion3/qa/contact-checks.js +51 -0
  180. package/src/motion3/qa/explain.js +80 -0
  181. package/src/motion3/qa/fmt.js +5 -0
  182. package/src/motion3/qa/foot-checks.js +72 -0
  183. package/src/motion3/qa/geometry.js +77 -0
  184. package/src/motion3/qa/goals.js +191 -0
  185. package/src/motion3/qa/ground.js +60 -0
  186. package/src/motion3/qa/hints-contact.js +195 -0
  187. package/src/motion3/qa/hints-goal.js +160 -0
  188. package/src/motion3/qa/hints-knee.js +24 -0
  189. package/src/motion3/qa/hints-motion.js +209 -0
  190. package/src/motion3/qa/hints-object.js +91 -0
  191. package/src/motion3/qa/index.js +44 -0
  192. package/src/motion3/qa/key-checks.js +82 -0
  193. package/src/motion3/qa/near.js +96 -0
  194. package/src/motion3/qa/object-checks.js +135 -0
  195. package/src/motion3/qa/patch.js +161 -0
  196. package/src/motion3/qa/people-checks.js +37 -0
  197. package/src/motion3/qa/severity.js +38 -0
  198. package/src/motion3/qa/spec-checks.js +65 -0
  199. package/src/motion3/qa/style.js +228 -0
  200. package/src/motion3/qa/wrist-checks.js +69 -0
  201. package/src/motion3/qa/wrist-obs.js +60 -0
  202. package/src/motion3/refs.js +129 -0
  203. package/src/motion3/repair-tune.js +121 -0
  204. package/src/motion3/repair.js +83 -0
  205. package/src/motion3/report.js +67 -0
  206. package/src/motion3/rigs/index.js +14 -0
  207. package/src/motion3/rigs/x-bot-tpose-body.json +1 -0
  208. package/src/motion3/rigs/x-bot-tpose.json +1 -0
  209. package/src/motion3/rigs/y-bot-tpose-body.json +1 -0
  210. package/src/motion3/rigs/y-bot-tpose.json +1 -0
  211. package/src/motion3/scene.js +60 -0
  212. package/src/motion3/schema/brief.js +89 -0
  213. package/src/motion3/schema/fields.js +44 -0
  214. package/src/motion3/schema/spec-examples.js +46 -0
  215. package/src/motion3/schema/topics.js +113 -0
  216. package/src/motion3/schema/types.js +149 -0
  217. package/src/motion3/search.js +117 -0
  218. package/src/motion3/second-hand.js +112 -0
  219. package/src/motion3/settle.js +136 -0
  220. package/src/motion3/skills/basic.js +83 -0
  221. package/src/motion3/skills/book.js +76 -0
  222. package/src/motion3/skills/car.js +145 -0
  223. package/src/motion3/skills/flip.js +200 -0
  224. package/src/motion3/skills/frames-auto.js +29 -0
  225. package/src/motion3/skills/impact.js +109 -0
  226. package/src/motion3/skills/index.js +85 -0
  227. package/src/motion3/skills/info.js +80 -0
  228. package/src/motion3/skills/smoke-kit.js +109 -0
  229. package/src/motion3/skills/smoke.js +160 -0
  230. package/src/motion3/spec-store.js +59 -0
  231. package/src/motion3/take-edit.js +155 -0
  232. package/src/motion3/take.js +63 -0
  233. package/src/motion3/timeline.js +127 -0
  234. package/src/motion3/views.js +121 -0
  235. package/src/motion3/walk-plan.js +119 -0
  236. package/src/object-catalog.jsx +7 -1
  237. package/src/object-colors.js +98 -0
  238. package/src/object-gizmo-parts.js +115 -0
  239. package/src/object-gizmo.jsx +37 -122
  240. package/src/object-glow.js +139 -0
  241. package/src/object-hidden.js +33 -0
  242. package/src/object-look.js +196 -0
  243. package/src/object-size.js +10 -0
  244. package/src/object-travel.js +71 -6
  245. package/src/panels/CameraPanel.jsx +7 -7
  246. package/src/panels/HdrEnvironmentControls.jsx +59 -0
  247. package/src/panels/LightPanel.jsx +25 -4
  248. package/src/panels/ObjectLookPanel.jsx +88 -0
  249. package/src/panels/PosePanel.jsx +2 -17
  250. package/src/panels/RigControlPanel.jsx +5 -53
  251. package/src/panels/SubjectBox.jsx +4 -1
  252. package/src/panels/details.css +14 -0
  253. package/src/posestudio.jsx +4 -27
  254. package/src/project-browser.jsx +3 -4
  255. package/src/project-resources.js +18 -31
  256. package/src/project.js +13 -104
  257. package/src/props.jsx +25 -280
  258. package/src/qa-label.js +25 -0
  259. package/src/rail-dot-drag.js +23 -0
  260. package/src/render-passes.js +10 -0
  261. package/src/resource-status.jsx +1 -0
  262. package/src/result-modal.jsx +4 -19
  263. package/src/room.jsx +20 -12
  264. package/src/scene-assets.js +8 -6
  265. package/src/scene-light-place.js +72 -0
  266. package/src/scene-lights.jsx +148 -0
  267. package/src/scene-objects.js +31 -139
  268. package/src/scenes.js +15 -105
  269. package/src/semantic-edit.js +2 -5
  270. package/src/set-pieces.jsx +183 -0
  271. package/src/settings-menu.jsx +3 -11
  272. package/src/shell/BottomDock.jsx +9 -51
  273. package/src/shell/DetailsSlot.jsx +16 -200
  274. package/src/shell/GhostsMenuGroup.jsx +74 -0
  275. package/src/shell/LibrarySlot.jsx +1 -3
  276. package/src/shell/MenuBar.jsx +3 -3
  277. package/src/shell/PreferencesDialog.jsx +15 -264
  278. package/src/shell/PreferencesSlot.jsx +2 -8
  279. package/src/shell/TopBar.jsx +5 -84
  280. package/src/shell/ViewportToolbar.jsx +10 -70
  281. package/src/shell/agent-glass.css +19 -6
  282. package/src/shell/details-selection.js +42 -0
  283. package/src/shell/ghosts-menu.css +22 -0
  284. package/src/shell/glass-regions.css +29 -3
  285. package/src/shell/preferences.css +6 -1
  286. package/src/shot-authoring.js +4 -7
  287. package/src/soft-shadows.js +37 -0
  288. package/src/stage-attach-frames.js +12 -12
  289. package/src/stage-canvas-rigs.jsx +3 -3
  290. package/src/stage-capture.jsx +42 -8
  291. package/src/stage-characters.jsx +22 -216
  292. package/src/stage-editor-gizmos.jsx +6 -6
  293. package/src/stage-environment.js +93 -0
  294. package/src/stage-hdr-builtins.js +107 -0
  295. package/src/stage-hdr-image.js +116 -0
  296. package/src/stage-hdr-library.js +70 -0
  297. package/src/stage-hdr-map.js +94 -0
  298. package/src/stage-hdr.js +130 -0
  299. package/src/stage-lighting.js +149 -0
  300. package/src/stage-occlusion.js +210 -0
  301. package/src/stage-sky.jsx +159 -0
  302. package/src/stage-startup-defaults.js +5 -9
  303. package/src/studio-actions.js +16 -14
  304. package/src/studio-agent-commands.js +19 -16
  305. package/src/studio-agent-motion.js +9 -9
  306. package/src/studio-agent-protocol.js +47 -77
  307. package/src/studio-app-binding.js +27 -47
  308. package/src/studio-asset-list.js +17 -0
  309. package/src/studio-contact-sheet.js +37 -5
  310. package/src/studio-elements.js +27 -7
  311. package/src/studio-entity-bounds.js +18 -0
  312. package/src/studio-error-codes.js +15 -0
  313. package/src/studio-export-framing.js +152 -0
  314. package/src/studio-lighting-check.js +103 -0
  315. package/src/studio-motion-visual.js +150 -0
  316. package/src/studio-schema-errors.js +80 -0
  317. package/src/studio-support-gap.js +74 -0
  318. package/src/styles.css +1 -19
  319. package/src/timeline-defaults.js +5 -0
  320. package/src/timeline-extent.js +8 -20
  321. package/src/tutorial-walk.js +17 -0
  322. package/src/ui-scrub.js +1 -1
  323. package/src/use-shot-camera-tracks.js +12 -0
  324. package/src/view-ghosts.js +97 -0
  325. package/src/view-ghosts.jsx +49 -0
  326. package/src/workflow/AgentPanel.jsx +47 -166
  327. package/src/workflow/AgentProgressCard.jsx +109 -0
  328. package/src/workflow/AgentReplyText.jsx +37 -0
  329. package/src/workflow/AgentTurnSteps.jsx +84 -0
  330. package/src/workflow/LoginPanel.jsx +140 -0
  331. package/src/workflow/WorkflowBuilder.jsx +7 -23
  332. package/src/workflow/agent-client.js +54 -51
  333. package/src/workflow/agent-panel.css +71 -1
  334. package/src/workflow/agent-progress-card.css +167 -0
  335. package/src/workflow/agent-progress-view.js +47 -0
  336. package/src/workflow/agent-turn-steps.css +113 -0
  337. package/src/workflow/agent-turn.js +191 -0
  338. package/src/workflow/login-client.js +34 -0
  339. package/src/workflow/motion-input.js +18 -31
  340. package/src/workflow/node-schema.js +1 -1
  341. package/src/workflow/reply-markdown.js +69 -0
  342. package/src/workflow/scene-asset-sync.js +8 -8
  343. package/tools/agent-session-stats.mjs +61 -0
  344. package/tools/dev-full.mjs +5 -56
  345. package/tools/eslint-baseline.json +12 -13
  346. package/tools/knip-baseline.json +0 -179
  347. package/tools/loc-baseline.json +31 -81
  348. package/tools/motion3/extract-body.mjs +103 -0
  349. package/tools/motion3/extract-rig.mjs +85 -0
  350. package/tools/process-supervisor.mjs +0 -88
  351. package/tools/run-tests.mjs +9 -125
  352. package/bin/agent/motion-runtime.mjs +0 -76
  353. package/dist/ardy/cskel27-rest.json +0 -557
  354. package/dist/assets/analytics-C9pMpW7s.js +0 -1
  355. package/dist/assets/app-B_2XB9rw.css +0 -1
  356. package/dist/assets/app-DAuvKPP8.js +0 -4920
  357. package/dist/assets/config-Bx7pYsD9.js +0 -1
  358. package/dist/assets/config-DeoFTYzb.css +0 -1
  359. package/dist/assets/demo-CvKuBgy5.js +0 -1
  360. package/dist/assets/scene-asset-sync-DKkpjKBV.js +0 -19
  361. package/dist/assets/scene-asset-sync-DjJ_cJ2R.css +0 -1
  362. package/dist/assets/ticket-BPk2Ni95.js +0 -1
  363. package/dist/assets/workflow-jJRBR94Z.js +0 -179
  364. package/dist/demo/walk-then-stop.npz +0 -0
  365. package/mcp/ardy-prompts.mjs +0 -218
  366. package/src/ardy/auto-fix-panel.css +0 -108
  367. package/src/ardy/auto-physics.js +0 -1194
  368. package/src/ardy/client.js +0 -153
  369. package/src/ardy/convert.js +0 -313
  370. package/src/ardy/cskel27-neutral.js +0 -39
  371. package/src/ardy/cskel27.js +0 -77
  372. package/src/ardy/export.js +0 -165
  373. package/src/ardy/fix-collisions.js +0 -2257
  374. package/src/ardy/foot-lock.js +0 -230
  375. package/src/ardy/motion-calibration.js +0 -118
  376. package/src/ardy/motion-edit.js +0 -213
  377. package/src/ardy/motion-url.js +0 -55
  378. package/src/ardy/mp4-duration.js +0 -102
  379. package/src/ardy/npz.js +0 -595
  380. package/src/ardy/physics-panel.css +0 -21
  381. package/src/ardy/physics-panel.jsx +0 -89
  382. package/src/ardy/physics-refine.js +0 -111
  383. package/src/ardy/physics-review.js +0 -567
  384. package/src/ardy/physics-temporal.js +0 -54
  385. package/src/ardy/platform-fit-panel.css +0 -64
  386. package/src/ardy/platform-fit-panel.jsx +0 -41
  387. package/src/ardy/platform-fit.js +0 -326
  388. package/src/ardy/playback.js +0 -711
  389. package/src/ardy/pose-pin.js +0 -97
  390. package/src/ardy/prompt-clips.js +0 -30
  391. package/src/ardy/retime.js +0 -211
  392. package/src/ardy/root-drop.js +0 -369
  393. package/src/ardy/timeline-resize.js +0 -13
  394. package/src/ardy/to-cskel27.js +0 -225
  395. package/src/ardy/trim.js +0 -33
  396. package/src/ardy/waypoints.js +0 -369
  397. package/src/ardy-bridge-contract.js +0 -281
  398. package/src/domains/motion/motion-video-capture.js +0 -664
  399. package/src/i2v-motion-client.js +0 -77
  400. package/src/i2v-motion-studio.jsx +0 -180
  401. package/src/line-edit.js +0 -1348
  402. package/src/motion/generation.js +0 -105
  403. package/src/motion-readiness-ui.jsx +0 -67
  404. package/src/motion-readiness.js +0 -40
  405. package/src/motion-resources.js +0 -177
  406. package/src/motion-store.js +0 -65
  407. package/src/motion-trail.js +0 -601
  408. package/src/multimodel-ingest.js +0 -394
  409. package/src/panels/PromptBlocksPanel.jsx +0 -444
  410. package/src/panels/VideoCapturePanel.jsx +0 -181
  411. package/src/pose-extract/browser.js +0 -42
  412. package/src/pose-extract/detector.js +0 -100
  413. package/src/pose-extract/fit.js +0 -227
  414. package/src/pose-extract/image-frame.js +0 -51
  415. package/src/pose-extract/index.js +0 -58
  416. package/src/pose-extract/keyframes.js +0 -74
  417. package/src/pose-extract/landmarks.js +0 -162
  418. package/src/pose-extract/mirror.js +0 -153
  419. package/src/pose-extract/take.js +0 -131
  420. package/src/pose-extract/video-frames.js +0 -91
  421. package/src/take-recipe.js +0 -165
  422. package/src/trail-key-conflicts.js +0 -39
  423. package/src/trail-pick.js +0 -59
  424. package/src/use-i2v-capture-modal.js +0 -15
  425. package/tools/ardy/BRIDGE.md +0 -37
  426. package/tools/ardy/GVHMR-NPZ.md +0 -68
  427. package/tools/ardy/README.md +0 -124
  428. package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
  429. package/tools/ardy/artifacts.mjs +0 -34
  430. package/tools/ardy/bridge.mjs +0 -1732
  431. package/tools/ardy/bvh-cskel27.mjs +0 -1265
  432. package/tools/ardy/cclay_constrained_generate.py +0 -561
  433. package/tools/ardy/cclay_gvhmr_worker.py +0 -264
  434. package/tools/ardy/cclay_motion_edit.py +0 -510
  435. package/tools/ardy/cclay_sequence_generate.py +0 -705
  436. package/tools/ardy/cclay_worker.py +0 -304
  437. package/tools/ardy/dump-npz.py +0 -205
  438. package/tools/ardy/extract-rest.mjs +0 -299
  439. package/tools/ardy/extract.mjs +0 -507
  440. package/tools/ardy/footage.mjs +0 -467
  441. package/tools/ardy/gvhmr-floor.mjs +0 -102
  442. package/tools/ardy/gvhmr-to-cskel27.mjs +0 -21
  443. package/tools/ardy/gvhmr_fastpath.py +0 -168
  444. package/tools/ardy/gvhmr_trajectory.py +0 -240
  445. package/tools/ardy/merge-text-encoder.py +0 -160
  446. package/tools/ardy/mocap-metrics.mjs +0 -200
  447. package/tools/ardy/mocap-quality-gate.mjs +0 -110
  448. package/tools/ardy/motion-stabilize.mjs +0 -348
  449. package/tools/ardy/npz.mjs +0 -414
  450. package/tools/ardy/pose-to-npz.mjs +0 -106
  451. package/tools/ardy/prompt-limits.mjs +0 -5
  452. package/tools/ardy/run-edit-on-box.sh +0 -73
  453. package/tools/ardy/run-local.mjs +0 -233
  454. package/tools/ardy/run-on-box.sh +0 -593
  455. package/tools/ardy/run-sequence-on-box.sh +0 -177
  456. package/tools/ardy/runners/gvhmr-worker.mjs +0 -206
  457. package/tools/ardy/runners/index.mjs +0 -18
  458. package/tools/ardy/runners/local.mjs +0 -455
  459. package/tools/ardy/runners/proc.mjs +0 -125
  460. package/tools/ardy/runners/remote.mjs +0 -252
  461. package/tools/ardy/setup-local.mjs +0 -189
  462. package/tools/ardy/setup-text-encoder-on-box.sh +0 -93
  463. package/tools/ardy/setup-text-encoder.py +0 -306
  464. package/tools/ardy/smpl-cskel27.mjs +0 -178
  465. package/tools/ardy/visual-qa.mjs +0 -196
  466. package/tools/ardy/vq-car.mjs +0 -52
  467. package/tools/bench/EXP3.md +0 -61
  468. package/tools/bench/cclay_bench_extract_incam.py +0 -125
  469. package/tools/bench/cclay_bench_extract_obs.py +0 -204
  470. package/tools/bench/cclay_bench_runner.py +0 -42
  471. package/tools/bench/cube-contact.mjs +0 -162
  472. package/tools/bench/exp3.mjs +0 -152
  473. package/tools/bench/extract-bench-lib.mjs +0 -216
  474. package/tools/bench/extract-bench.mjs +0 -302
  475. package/tools/bench/fal-generate.mjs +0 -88
  476. package/tools/bench/fit/README.md +0 -189
  477. package/tools/bench/fit/camera.mjs +0 -59
  478. package/tools/bench/fit/contact.mjs +0 -419
  479. package/tools/bench/fit/footlock.mjs +0 -234
  480. package/tools/bench/fit/motion.mjs +0 -85
  481. package/tools/bench/fit/pin.mjs +0 -27
  482. package/tools/bench/fit/remote.mjs +0 -105
  483. package/tools/bench/fit-bench.mjs +0 -126
  484. package/tools/bench/fit-sanity.mjs +0 -45
  485. package/tools/bench/metrics.mjs +0 -313
  486. package/tools/bench/obs/depth.mjs +0 -258
  487. package/tools/bench/obs/extrinsics.mjs +0 -175
  488. package/tools/bench/obs/fit_mannequin_betas.py +0 -340
  489. package/tools/bench/obs/ground.mjs +0 -294
  490. package/tools/bench/obs/heading.mjs +0 -151
  491. package/tools/bench/obs/ladder.mjs +0 -555
  492. package/tools/bench/obs/mannequin-betas.json +0 -124
  493. package/tools/bench/obs/remote.mjs +0 -83
  494. package/tools/bench/obs/rest_joints.py +0 -52
  495. package/tools/bench/obs/ybot-targets.mjs +0 -49
  496. package/tools/bench/obs-bench.mjs +0 -520
  497. package/tools/bench/score.mjs +0 -343
  498. package/tools/bench/summarize.mjs +0 -86
  499. package/tools/demo-worker/README.md +0 -137
  500. package/tools/demo-worker/api-client.mjs +0 -233
  501. package/tools/demo-worker/generate.mjs +0 -301
  502. package/tools/demo-worker/index.mjs +0 -466
  503. package/tools/demo-worker/sign-core.mjs +0 -112
  504. package/tools/demo-worker/sign.mjs +0 -161
  505. package/tools/gt-render/browser.mjs +0 -182
  506. package/tools/gt-render/camera-math.mjs +0 -276
  507. package/tools/gt-render/page.mjs +0 -267
  508. package/tools/gt-render/render.mjs +0 -514
  509. package/tools/gt-render/scene-box.mjs +0 -31
  510. package/tools/gt-render/take-transform.mjs +0 -87
  511. package/tools/kimodo/compare-seam.mjs +0 -108
  512. package/tools/kimodo/constraints.mjs +0 -244
  513. package/tools/kimodo/edit.mjs +0 -259
  514. package/tools/kimodo/effector-constraints.mjs +0 -200
  515. package/tools/kimodo/generate.mjs +0 -646
  516. package/tools/kimodo/local-output.mjs +0 -265
  517. package/tools/kimodo/measure-pose.mjs +0 -65
  518. package/tools/kimodo/measure-preserve.mjs +0 -899
  519. package/tools/kimodo/measure-waypoints.mjs +0 -55
  520. package/tools/kimodo/mlx-generate.py +0 -111
  521. package/tools/kimodo/pose-constraints.mjs +0 -248
  522. package/tools/kimodo/preserve-mask.mjs +0 -543
  523. package/tools/kimodo/read-npz.mjs +0 -189
  524. package/tools/kimodo/run-edit-on-box.mjs +0 -155
  525. package/tools/kimodo/run-sequence-on-box.mjs +0 -178
  526. package/tools/kimodo/runner.mjs +0 -284
  527. package/tools/kimodo/seam-batch.mjs +0 -77
  528. package/tools/kimodo/setup-local.mjs +0 -30
  529. package/tools/kimodo/setup-on-box.sh +0 -339
  530. package/tools/kimodo/soma77-to-cskel27.mjs +0 -305
  531. package/tools/morphgs/assets/gen_truth.py +0 -39
  532. package/tools/morphgs/assets/mesh_ori_rig.txt +0 -27
  533. package/tools/morphgs/assets/playback-check.mjs +0 -30
  534. package/tools/morphgs/demo-gate.sh +0 -26
  535. package/tools/morphgs/fbx2morphgs.mjs +0 -68
  536. package/tools/morphgs/morphgs-to-cskel27.mjs +0 -105
  537. package/tools/morphgs/patches/preprocess_src-none-mode.patch +0 -76
  538. package/tools/morphgs/setup-on-cluster.sh +0 -126
  539. package/tools/projflow/driver.py +0 -1069
  540. package/tools/projflow/generate.mjs +0 -805
  541. package/tools/projflow/hml22-to-cskel27.mjs +0 -735
  542. package/tools/projflow/line-edit-job.mjs +0 -547
  543. package/tools/projflow/replay.mjs +0 -452
  544. package/tools/projflow/runner.mjs +0 -269
  545. package/tools/projflow/service.mjs +0 -742
  546. package/tools/track/DESIGN.md +0 -513
  547. package/tools/track/backfill-provenance.mjs +0 -116
  548. package/tools/track/budget.mjs +0 -8
  549. package/tools/track/check-rig.mjs +0 -209
  550. package/tools/track/diagnostics.schema.json +0 -60
  551. package/tools/track/export-rig.mjs +0 -256
  552. package/tools/track/fallback.mjs +0 -7
  553. package/tools/track/fk-parity-fixture.mjs +0 -161
  554. package/tools/track/gate.mjs +0 -349
  555. package/tools/track/masks.mjs +0 -318
  556. package/tools/track/metrics.mjs +0 -201
  557. package/tools/track/publish-obs.mjs +0 -108
  558. package/tools/track/py/check_env.py +0 -61
  559. package/tools/track/py/eval_lr.py +0 -229
  560. package/tools/track/py/lr_viterbi.py +0 -294
  561. package/tools/track/py/masks.py +0 -405
  562. package/tools/track/py/objective.py +0 -382
  563. package/tools/track/py/rig.py +0 -508
  564. package/tools/track/py/scene.py +0 -326
  565. package/tools/track/py/test_joint_indices.py +0 -123
  566. package/tools/track/py/test_lr_viterbi.py +0 -195
  567. package/tools/track/py/test_masks.py +0 -229
  568. package/tools/track/py/test_rig.py +0 -288
  569. package/tools/track/py/test_scene.py +0 -354
  570. package/tools/track/py/test_track.py +0 -328
  571. package/tools/track/py/track.py +0 -411
  572. package/tools/track/remote.mjs +0 -140
  573. package/tools/track/rig-dump.mjs +0 -227
  574. package/tools/track/run-box-tests.mjs +0 -14
  575. package/tools/track/run-box.mjs +0 -168
  576. package/tools/track/setup-box.sh +0 -59
  577. package/tools/track/study-2d.mjs +0 -728
  578. /package/src/{ardy → motion}/collision-blockers.js +0 -0
  579. /package/src/{ardy → motion}/ground.js +0 -0
  580. /package/src/{ardy → motion}/ik-drag.js +0 -0
  581. /package/src/{ardy → motion}/ik-key-json.js +0 -0
  582. /package/src/{ardy → motion}/ik.js +0 -0
  583. /package/src/{ardy → motion}/physics-surface.js +0 -0
  584. /package/src/{ardy → motion}/playback-clock.js +0 -0
  585. /package/src/{ardy → motion}/range-pin.js +0 -0
@@ -1,14 +1,9 @@
1
1
  import { registerElementKind } from '../elements.js';
2
- import { wrapAngle } from '../../scene-objects.js';
3
2
 
4
3
  const elements = [
5
- ...['anchorX', 'anchorZ', 'rotationDeg'].map(field => ({ path: `motion.take.${field}`, type: 'number' })),
6
4
  { path: 'motion.take.prompt', type: 'string' },
7
5
  { path: 'motion.ikKeys', type: 'array', readOnly: true },
8
6
  { path: 'motion.takeRecipe', type: 'object', readOnly: true },
9
7
  { path: 'motion.takeVersions', type: 'array', readOnly: true },
10
8
  ];
11
- registerElementKind('motion', { collection: true, elements, normalize: row => ({ ...row, take: row.take && { ...row.take,
12
- anchorX: Math.max(-240, Math.min(240, row.take.anchorX ?? 0)), anchorZ: Math.max(-240, Math.min(240, row.take.anchorZ ?? 0)),
13
- rotationDeg: wrapAngle(row.take.rotationDeg ?? 0),
14
- } }) });
9
+ registerElementKind('motion', { collection: true, elements, normalize: row => ({ ...row, take: row.take && { ...row.take } }) });
@@ -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
+ }