cozyclay 2.1.1 → 3.0.0

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