cozyclay 2.1.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (583) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +12 -30
  3. package/bin/agent/agent-routes.mjs +40 -113
  4. package/bin/agent/agent-runner.mjs +9 -10
  5. package/bin/agent/cliproxy-provider.mjs +135 -0
  6. package/bin/agent/credential-store.mjs +30 -7
  7. package/bin/agent/pi-tools.mjs +4 -1
  8. package/bin/agent/provider-keys.mjs +32 -0
  9. package/bin/agent/provider-login.mjs +110 -0
  10. package/bin/agent/provider-routes.mjs +99 -0
  11. package/bin/agent/providers.mjs +19 -97
  12. package/bin/agent/script-tool.mjs +17 -4
  13. package/bin/agent/studio-motion-reference.mjs +23 -0
  14. package/bin/agent/studio-prompt.mjs +1 -1
  15. package/bin/agent/studio-rebase-text.mjs +32 -0
  16. package/bin/agent/studio-rebase.mjs +26 -39
  17. package/bin/agent/studio-tools.mjs +9 -28
  18. package/bin/agent/tool-arguments.mjs +49 -0
  19. package/bin/agent/turn-frames.mjs +66 -0
  20. package/bin/codex-auth.mjs +9 -2
  21. package/bin/cozyclay.mjs +10 -116
  22. package/bin/live/cli.mjs +22 -6
  23. package/bin/mcp-runtime.mjs +2 -12
  24. package/bin/package-signature.mjs +0 -4
  25. package/dist/app/index.html +7 -6
  26. package/dist/assets/EXRLoader-CvVIvGVE.js +1 -0
  27. package/dist/assets/analytics-C9sUnndd.js +1 -0
  28. package/dist/assets/app-CCh--1Hc.js +5087 -0
  29. package/dist/assets/app-f-fkV5uP.css +1 -0
  30. package/dist/assets/compile-worker-CkxiD6lH.js +5 -0
  31. package/dist/assets/fflate.module-KbGj5-H6.js +5 -0
  32. package/dist/assets/{first-shot-handoff-BlT33CGf.js → first-shot-handoff-CC4xe4HM.js} +1 -1
  33. package/dist/assets/{landing-BSesrCpL.js → landing-DjHD8u7k.js} +1 -1
  34. package/dist/assets/scene-asset-sync-Hx_FzdOH.js +20 -0
  35. package/dist/assets/scene-asset-sync-_S8NQl5B.css +1 -0
  36. package/dist/assets/workflow-C4yKH1p4.js +179 -0
  37. package/dist/cozyclay-package.json +1 -1
  38. package/dist/index.html +3 -3
  39. package/dist/llms.txt +14 -0
  40. package/dist/sw.js +3 -6
  41. package/dist/workflow/index.html +5 -4
  42. package/mcp/LIVE-PROTOCOL.md +7 -47
  43. package/mcp/README.md +16 -26
  44. package/mcp/live-hub.mjs +6 -10
  45. package/mcp/package.json +1 -3
  46. package/mcp/tool-handlers.mjs +4 -168
  47. package/package.json +3 -22
  48. package/src/App.jsx +174 -2639
  49. package/src/agent-progress.js +79 -0
  50. package/src/agent-turn-mark.js +30 -0
  51. package/src/analytics.js +1 -19
  52. package/src/app-context.js +5 -0
  53. package/src/app-stage.jsx +2 -34
  54. package/src/asset-pane.jsx +6 -5
  55. package/src/camera-block.js +21 -0
  56. package/src/camera-direction.js +120 -0
  57. package/src/character-kind.js +3 -9
  58. package/src/command-bus.js +8 -6
  59. package/src/commands/cast.js +5 -34
  60. package/src/commands/elements/character.js +1 -1
  61. package/src/commands/elements/motion.js +1 -6
  62. package/src/commands/elements/object.js +6 -2
  63. package/src/commands/elements/shot.js +4 -1
  64. package/src/commands/elements.js +38 -14
  65. package/src/commands/index.js +3 -1
  66. package/src/commands/kits.js +37 -0
  67. package/src/commands/motion-timing.js +57 -0
  68. package/src/commands/motion.js +9 -152
  69. package/src/commands/motion3-compile.js +65 -0
  70. package/src/commands/motion3.js +207 -0
  71. package/src/commands/objects.js +6 -4
  72. package/src/commands/shot.js +49 -11
  73. package/src/commands/stage.js +14 -1
  74. package/src/commands/view.js +4 -1
  75. package/src/document-mirrors.js +42 -0
  76. package/src/document-store.js +4 -0
  77. package/src/domains/cast.js +19 -194
  78. package/src/domains/motion/motion-range-pin-rules.js +1 -1
  79. package/src/domains/motion/take-source.js +51 -0
  80. package/src/domains/motion.js +60 -1838
  81. package/src/domains/scenes.js +12 -55
  82. package/src/domains/shots.js +2 -2
  83. package/src/domains/stage.js +4 -2
  84. package/src/draft-ghost.js +46 -0
  85. package/src/draft-ghost.jsx +129 -0
  86. package/src/dualview.jsx +4 -3
  87. package/src/effects/EffectsLayer.jsx +112 -0
  88. package/src/effects/effect-meshes.js +232 -0
  89. package/src/effects/effect-record.js +103 -0
  90. package/src/effects/emitter.js +59 -0
  91. package/src/effects/simulate.js +261 -0
  92. package/src/effects/state.js +59 -0
  93. package/src/execution-telemetry.js +2 -3
  94. package/src/first-success-guide.jsx +5 -5
  95. package/src/ghost-set.js +257 -0
  96. package/src/held-object-edit.jsx +113 -0
  97. package/src/held-object-pose.js +175 -0
  98. package/src/hierarchy-model.js +1 -1
  99. package/src/hierarchy-panel.jsx +1 -1
  100. package/src/imported-mesh.jsx +103 -0
  101. package/src/kits/held.js +84 -0
  102. package/src/kits/index.js +70 -0
  103. package/src/kits/smoking.js +57 -0
  104. package/src/light-blockers.js +93 -0
  105. package/src/motion/beat-board-model.js +84 -0
  106. package/src/motion/beat-board.css +124 -0
  107. package/src/motion/beat-board.jsx +59 -0
  108. package/src/motion/center-of-mass.js +66 -0
  109. package/src/motion/fix-collisions.js +850 -0
  110. package/src/motion/physics-review.js +118 -0
  111. package/src/{ardy → motion}/physics-support.js +1 -76
  112. package/src/motion/rig-snapshot.js +22 -0
  113. package/src/motion/take-segments.jsx +140 -0
  114. package/src/{ardy → motion}/timeline-coordinates.js +0 -21
  115. package/src/{ardy → motion}/timeline.css +1 -2
  116. package/src/{ardy → motion}/timeline.jsx +48 -419
  117. package/src/motion/waypoints.js +76 -0
  118. package/src/motion3/beats.js +83 -0
  119. package/src/motion3/body.js +158 -0
  120. package/src/motion3/bones.js +47 -0
  121. package/src/motion3/carried.js +47 -0
  122. package/src/motion3/compile-job.js +75 -0
  123. package/src/motion3/compile-worker.js +10 -0
  124. package/src/motion3/compiler/anchors.js +105 -0
  125. package/src/motion3/compiler/arm-orient.js +128 -0
  126. package/src/motion3/compiler/channels.js +125 -0
  127. package/src/motion3/compiler/compile.js +205 -0
  128. package/src/motion3/compiler/gait-rhythm.js +97 -0
  129. package/src/motion3/compiler/grip.js +171 -0
  130. package/src/motion3/compiler/head-steady.js +48 -0
  131. package/src/motion3/compiler/hold.js +134 -0
  132. package/src/motion3/compiler/idle.js +113 -0
  133. package/src/motion3/compiler/limbs.js +170 -0
  134. package/src/motion3/compiler/math.js +72 -0
  135. package/src/motion3/compiler/pose.js +162 -0
  136. package/src/motion3/compiler/quat.js +49 -0
  137. package/src/motion3/compiler/skeleton.js +74 -0
  138. package/src/motion3/compiler/validate-common.js +32 -0
  139. package/src/motion3/compiler/validate-goals.js +118 -0
  140. package/src/motion3/compiler/validate-objects.js +143 -0
  141. package/src/motion3/compiler/validate-parts.js +116 -0
  142. package/src/motion3/compiler/validate.js +122 -0
  143. package/src/motion3/compiler/vocab.js +73 -0
  144. package/src/motion3/compiler/walk-pelvis.js +275 -0
  145. package/src/motion3/compiler/walk.js +207 -0
  146. package/src/motion3/compiler/wrist-clamp.js +32 -0
  147. package/src/motion3/delta.js +47 -0
  148. package/src/motion3/describe.js +174 -0
  149. package/src/motion3/docs.js +27 -0
  150. package/src/motion3/edit-carry.js +21 -0
  151. package/src/motion3/fix/ease.js +16 -0
  152. package/src/motion3/fix/ground.js +37 -0
  153. package/src/motion3/fix/index.js +52 -0
  154. package/src/motion3/fix/push.js +71 -0
  155. package/src/motion3/fix/reach.js +46 -0
  156. package/src/motion3/fix/retime.js +79 -0
  157. package/src/motion3/fix/self.js +211 -0
  158. package/src/motion3/fix/slide.js +70 -0
  159. package/src/motion3/fix/turn.js +51 -0
  160. package/src/motion3/follow.js +39 -0
  161. package/src/motion3/free-motion.js +173 -0
  162. package/src/motion3/grip-choice.js +130 -0
  163. package/src/motion3/hold-plan.js +157 -0
  164. package/src/motion3/json-patch.js +77 -0
  165. package/src/motion3/key-aim.js +105 -0
  166. package/src/motion3/key-edit.js +82 -0
  167. package/src/motion3/lenient.js +127 -0
  168. package/src/motion3/object-keys.js +236 -0
  169. package/src/motion3/object-pose.js +182 -0
  170. package/src/motion3/object-tracks.js +12 -0
  171. package/src/motion3/pipeline.js +102 -0
  172. package/src/motion3/playback.js +51 -0
  173. package/src/motion3/probes.js +91 -0
  174. package/src/motion3/qa/body-checks.js +34 -0
  175. package/src/motion3/qa/check-names.js +14 -0
  176. package/src/motion3/qa/checks.js +121 -0
  177. package/src/motion3/qa/contact-checks.js +51 -0
  178. package/src/motion3/qa/explain.js +80 -0
  179. package/src/motion3/qa/fmt.js +5 -0
  180. package/src/motion3/qa/foot-checks.js +72 -0
  181. package/src/motion3/qa/geometry.js +77 -0
  182. package/src/motion3/qa/goals.js +191 -0
  183. package/src/motion3/qa/ground.js +60 -0
  184. package/src/motion3/qa/hints-contact.js +179 -0
  185. package/src/motion3/qa/hints-goal.js +160 -0
  186. package/src/motion3/qa/hints-knee.js +24 -0
  187. package/src/motion3/qa/hints-motion.js +209 -0
  188. package/src/motion3/qa/hints-object.js +91 -0
  189. package/src/motion3/qa/index.js +42 -0
  190. package/src/motion3/qa/key-checks.js +82 -0
  191. package/src/motion3/qa/near.js +96 -0
  192. package/src/motion3/qa/object-checks.js +135 -0
  193. package/src/motion3/qa/patch.js +161 -0
  194. package/src/motion3/qa/severity.js +38 -0
  195. package/src/motion3/qa/spec-checks.js +65 -0
  196. package/src/motion3/qa/style.js +218 -0
  197. package/src/motion3/qa/wrist-checks.js +69 -0
  198. package/src/motion3/qa/wrist-obs.js +60 -0
  199. package/src/motion3/refs.js +120 -0
  200. package/src/motion3/repair-tune.js +121 -0
  201. package/src/motion3/repair.js +83 -0
  202. package/src/motion3/report.js +67 -0
  203. package/src/motion3/rigs/index.js +14 -0
  204. package/src/motion3/rigs/x-bot-tpose-body.json +1 -0
  205. package/src/motion3/rigs/x-bot-tpose.json +1 -0
  206. package/src/motion3/rigs/y-bot-tpose-body.json +1 -0
  207. package/src/motion3/rigs/y-bot-tpose.json +1 -0
  208. package/src/motion3/scene.js +60 -0
  209. package/src/motion3/schema/brief.js +89 -0
  210. package/src/motion3/schema/fields.js +44 -0
  211. package/src/motion3/schema/spec-examples.js +46 -0
  212. package/src/motion3/schema/topics.js +111 -0
  213. package/src/motion3/schema/types.js +147 -0
  214. package/src/motion3/search.js +117 -0
  215. package/src/motion3/second-hand.js +112 -0
  216. package/src/motion3/skills/basic.js +82 -0
  217. package/src/motion3/skills/book.js +76 -0
  218. package/src/motion3/skills/car.js +145 -0
  219. package/src/motion3/skills/frames-auto.js +29 -0
  220. package/src/motion3/skills/index.js +83 -0
  221. package/src/motion3/skills/info.js +65 -0
  222. package/src/motion3/skills/smoke-kit.js +109 -0
  223. package/src/motion3/skills/smoke.js +160 -0
  224. package/src/motion3/spec-store.js +59 -0
  225. package/src/motion3/take-edit.js +155 -0
  226. package/src/motion3/take.js +63 -0
  227. package/src/motion3/timeline.js +127 -0
  228. package/src/motion3/views.js +121 -0
  229. package/src/motion3/walk-plan.js +119 -0
  230. package/src/object-catalog.jsx +7 -1
  231. package/src/object-colors.js +98 -0
  232. package/src/object-gizmo-parts.js +115 -0
  233. package/src/object-gizmo.jsx +37 -122
  234. package/src/object-glow.js +139 -0
  235. package/src/object-hidden.js +33 -0
  236. package/src/object-look.js +196 -0
  237. package/src/object-size.js +10 -0
  238. package/src/object-travel.js +71 -6
  239. package/src/panels/CameraPanel.jsx +7 -7
  240. package/src/panels/HdrEnvironmentControls.jsx +59 -0
  241. package/src/panels/LightPanel.jsx +25 -4
  242. package/src/panels/ObjectLookPanel.jsx +88 -0
  243. package/src/panels/PoseImagePanel.jsx +1 -1
  244. package/src/panels/PosePanel.jsx +2 -17
  245. package/src/panels/RigControlPanel.jsx +5 -53
  246. package/src/panels/SubjectBox.jsx +4 -1
  247. package/src/panels/details.css +14 -0
  248. package/src/posestudio.jsx +4 -27
  249. package/src/project-browser.jsx +3 -4
  250. package/src/project-resources.js +18 -31
  251. package/src/project.js +13 -104
  252. package/src/props.jsx +25 -280
  253. package/src/rail-dot-drag.js +23 -0
  254. package/src/render-passes.js +10 -0
  255. package/src/resource-status.jsx +1 -0
  256. package/src/result-modal.jsx +4 -19
  257. package/src/room.jsx +20 -12
  258. package/src/scene-assets.js +8 -6
  259. package/src/scene-light-place.js +72 -0
  260. package/src/scene-lights.jsx +148 -0
  261. package/src/scene-objects.js +31 -139
  262. package/src/scenes.js +15 -105
  263. package/src/semantic-edit.js +2 -5
  264. package/src/set-pieces.jsx +183 -0
  265. package/src/settings-menu.jsx +3 -11
  266. package/src/shell/BottomDock.jsx +11 -53
  267. package/src/shell/CompositionGuideButton.jsx +20 -0
  268. package/src/shell/DetailsSlot.jsx +16 -200
  269. package/src/shell/GhostsMenuGroup.jsx +74 -0
  270. package/src/shell/LibrarySlot.jsx +1 -3
  271. package/src/shell/MenuBar.jsx +3 -3
  272. package/src/shell/PreferencesDialog.jsx +15 -264
  273. package/src/shell/PreferencesSlot.jsx +2 -8
  274. package/src/shell/ShotPreviewHeader.jsx +17 -0
  275. package/src/shell/TopBar.jsx +5 -83
  276. package/src/shell/ViewportToolbar.jsx +23 -93
  277. package/src/shell/agent-glass.css +19 -6
  278. package/src/shell/details-selection.js +42 -0
  279. package/src/shell/ghosts-menu.css +22 -0
  280. package/src/shell/glass-regions.css +29 -3
  281. package/src/shell/preferences.css +6 -1
  282. package/src/shell/viewport.css +12 -0
  283. package/src/shot-authoring.js +4 -7
  284. package/src/soft-shadows.js +37 -0
  285. package/src/stage-attach-frames.js +12 -12
  286. package/src/stage-canvas-rigs.jsx +3 -3
  287. package/src/stage-capture.jsx +42 -8
  288. package/src/stage-characters.jsx +22 -216
  289. package/src/stage-editor-gizmos.jsx +6 -6
  290. package/src/stage-environment.js +93 -0
  291. package/src/stage-hdr-builtins.js +107 -0
  292. package/src/stage-hdr-image.js +116 -0
  293. package/src/stage-hdr-library.js +70 -0
  294. package/src/stage-hdr-map.js +94 -0
  295. package/src/stage-hdr.js +130 -0
  296. package/src/stage-lighting.js +149 -0
  297. package/src/stage-occlusion.js +210 -0
  298. package/src/stage-sky.jsx +159 -0
  299. package/src/stage-startup-defaults.js +5 -9
  300. package/src/studio-actions.js +12 -14
  301. package/src/studio-agent-commands.js +46 -16
  302. package/src/studio-agent-motion.js +9 -9
  303. package/src/studio-agent-protocol.js +46 -73
  304. package/src/studio-app-binding.js +29 -45
  305. package/src/studio-asset-list.js +17 -0
  306. package/src/studio-contact-sheet.js +37 -5
  307. package/src/studio-elements.js +27 -7
  308. package/src/studio-entity-bounds.js +18 -0
  309. package/src/studio-error-codes.js +8 -0
  310. package/src/studio-export-framing.js +152 -0
  311. package/src/studio-lighting-check.js +103 -0
  312. package/src/studio-motion-visual.js +150 -0
  313. package/src/studio-schema-errors.js +80 -0
  314. package/src/studio-support-gap.js +74 -0
  315. package/src/styles.css +1 -19
  316. package/src/timeline-defaults.js +5 -0
  317. package/src/timeline-extent.js +8 -20
  318. package/src/tutorial-walk.js +17 -0
  319. package/src/ui-scrub.js +1 -1
  320. package/src/use-shot-camera-tracks.js +12 -0
  321. package/src/view-ghosts.js +97 -0
  322. package/src/view-ghosts.jsx +49 -0
  323. package/src/workflow/AgentPanel.jsx +47 -166
  324. package/src/workflow/AgentProgressCard.jsx +107 -0
  325. package/src/workflow/AgentReplyText.jsx +37 -0
  326. package/src/workflow/AgentTurnSteps.jsx +84 -0
  327. package/src/workflow/LoginPanel.jsx +140 -0
  328. package/src/workflow/WorkflowBuilder.jsx +7 -23
  329. package/src/workflow/agent-client.js +54 -51
  330. package/src/workflow/agent-panel.css +71 -1
  331. package/src/workflow/agent-progress-card.css +167 -0
  332. package/src/workflow/agent-progress-view.js +47 -0
  333. package/src/workflow/agent-turn-steps.css +113 -0
  334. package/src/workflow/agent-turn.js +191 -0
  335. package/src/workflow/login-client.js +34 -0
  336. package/src/workflow/motion-input.js +18 -31
  337. package/src/workflow/node-schema.js +1 -1
  338. package/src/workflow/reply-markdown.js +69 -0
  339. package/src/workflow/scene-asset-sync.js +8 -8
  340. package/tools/agent-session-stats.mjs +61 -0
  341. package/tools/dev-full.mjs +5 -56
  342. package/tools/eslint-baseline.json +12 -13
  343. package/tools/headless-studio.mjs +85 -0
  344. package/tools/knip-baseline.json +0 -179
  345. package/tools/loc-baseline.json +33 -83
  346. package/tools/motion3/extract-body.mjs +103 -0
  347. package/tools/motion3/extract-rig.mjs +85 -0
  348. package/tools/process-supervisor.mjs +0 -88
  349. package/tools/run-tests.mjs +9 -124
  350. package/bin/agent/motion-runtime.mjs +0 -76
  351. package/dist/ardy/cskel27-rest.json +0 -557
  352. package/dist/assets/analytics-B41FmPYI.js +0 -1
  353. package/dist/assets/app-Do-Og5Pu.css +0 -1
  354. package/dist/assets/app-DwP-tl2x.js +0 -4920
  355. package/dist/assets/config-Bx7pYsD9.js +0 -1
  356. package/dist/assets/config-DeoFTYzb.css +0 -1
  357. package/dist/assets/demo-Dt0qZfS7.js +0 -1
  358. package/dist/assets/scene-asset-sync-BCWDJB1d.js +0 -19
  359. package/dist/assets/scene-asset-sync-DjJ_cJ2R.css +0 -1
  360. package/dist/assets/ticket-jqMhqL88.js +0 -1
  361. package/dist/assets/workflow-Br-K7Srs.js +0 -179
  362. package/dist/demo/walk-then-stop.npz +0 -0
  363. package/mcp/ardy-prompts.mjs +0 -218
  364. package/src/ardy/auto-fix-panel.css +0 -108
  365. package/src/ardy/auto-physics.js +0 -1194
  366. package/src/ardy/client.js +0 -153
  367. package/src/ardy/convert.js +0 -313
  368. package/src/ardy/cskel27-neutral.js +0 -39
  369. package/src/ardy/cskel27.js +0 -77
  370. package/src/ardy/export.js +0 -165
  371. package/src/ardy/fix-collisions.js +0 -2257
  372. package/src/ardy/foot-lock.js +0 -230
  373. package/src/ardy/motion-calibration.js +0 -118
  374. package/src/ardy/motion-edit.js +0 -213
  375. package/src/ardy/motion-url.js +0 -55
  376. package/src/ardy/mp4-duration.js +0 -102
  377. package/src/ardy/npz.js +0 -595
  378. package/src/ardy/physics-panel.css +0 -21
  379. package/src/ardy/physics-panel.jsx +0 -89
  380. package/src/ardy/physics-refine.js +0 -111
  381. package/src/ardy/physics-review.js +0 -567
  382. package/src/ardy/physics-temporal.js +0 -54
  383. package/src/ardy/platform-fit-panel.css +0 -64
  384. package/src/ardy/platform-fit-panel.jsx +0 -41
  385. package/src/ardy/platform-fit.js +0 -326
  386. package/src/ardy/playback.js +0 -711
  387. package/src/ardy/pose-pin.js +0 -97
  388. package/src/ardy/prompt-clips.js +0 -30
  389. package/src/ardy/retime.js +0 -211
  390. package/src/ardy/root-drop.js +0 -369
  391. package/src/ardy/timeline-resize.js +0 -13
  392. package/src/ardy/to-cskel27.js +0 -225
  393. package/src/ardy/trim.js +0 -33
  394. package/src/ardy/waypoints.js +0 -369
  395. package/src/ardy-bridge-contract.js +0 -281
  396. package/src/domains/motion/motion-video-capture.js +0 -664
  397. package/src/i2v-motion-client.js +0 -77
  398. package/src/i2v-motion-studio.jsx +0 -180
  399. package/src/line-edit.js +0 -1348
  400. package/src/motion/generation.js +0 -105
  401. package/src/motion-readiness-ui.jsx +0 -67
  402. package/src/motion-readiness.js +0 -40
  403. package/src/motion-resources.js +0 -177
  404. package/src/motion-store.js +0 -65
  405. package/src/motion-trail.js +0 -601
  406. package/src/multimodel-ingest.js +0 -394
  407. package/src/panels/PromptBlocksPanel.jsx +0 -444
  408. package/src/panels/VideoCapturePanel.jsx +0 -181
  409. package/src/pose-extract/browser.js +0 -42
  410. package/src/pose-extract/detector.js +0 -100
  411. package/src/pose-extract/fit.js +0 -227
  412. package/src/pose-extract/image-frame.js +0 -51
  413. package/src/pose-extract/index.js +0 -58
  414. package/src/pose-extract/keyframes.js +0 -74
  415. package/src/pose-extract/landmarks.js +0 -162
  416. package/src/pose-extract/mirror.js +0 -153
  417. package/src/pose-extract/take.js +0 -131
  418. package/src/pose-extract/video-frames.js +0 -91
  419. package/src/take-recipe.js +0 -165
  420. package/src/trail-key-conflicts.js +0 -39
  421. package/src/trail-pick.js +0 -59
  422. package/src/use-i2v-capture-modal.js +0 -15
  423. package/tools/ardy/BRIDGE.md +0 -37
  424. package/tools/ardy/GVHMR-NPZ.md +0 -68
  425. package/tools/ardy/README.md +0 -124
  426. package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
  427. package/tools/ardy/artifacts.mjs +0 -34
  428. package/tools/ardy/bridge.mjs +0 -1732
  429. package/tools/ardy/bvh-cskel27.mjs +0 -1265
  430. package/tools/ardy/cclay_constrained_generate.py +0 -561
  431. package/tools/ardy/cclay_gvhmr_worker.py +0 -264
  432. package/tools/ardy/cclay_motion_edit.py +0 -510
  433. package/tools/ardy/cclay_sequence_generate.py +0 -705
  434. package/tools/ardy/cclay_worker.py +0 -304
  435. package/tools/ardy/dump-npz.py +0 -205
  436. package/tools/ardy/extract-rest.mjs +0 -299
  437. package/tools/ardy/extract.mjs +0 -507
  438. package/tools/ardy/footage.mjs +0 -467
  439. package/tools/ardy/gvhmr-floor.mjs +0 -102
  440. package/tools/ardy/gvhmr-to-cskel27.mjs +0 -21
  441. package/tools/ardy/gvhmr_fastpath.py +0 -168
  442. package/tools/ardy/gvhmr_trajectory.py +0 -240
  443. package/tools/ardy/merge-text-encoder.py +0 -160
  444. package/tools/ardy/mocap-metrics.mjs +0 -200
  445. package/tools/ardy/mocap-quality-gate.mjs +0 -110
  446. package/tools/ardy/motion-stabilize.mjs +0 -348
  447. package/tools/ardy/npz.mjs +0 -414
  448. package/tools/ardy/pose-to-npz.mjs +0 -106
  449. package/tools/ardy/prompt-limits.mjs +0 -5
  450. package/tools/ardy/run-edit-on-box.sh +0 -73
  451. package/tools/ardy/run-local.mjs +0 -233
  452. package/tools/ardy/run-on-box.sh +0 -593
  453. package/tools/ardy/run-sequence-on-box.sh +0 -177
  454. package/tools/ardy/runners/gvhmr-worker.mjs +0 -206
  455. package/tools/ardy/runners/index.mjs +0 -18
  456. package/tools/ardy/runners/local.mjs +0 -455
  457. package/tools/ardy/runners/proc.mjs +0 -125
  458. package/tools/ardy/runners/remote.mjs +0 -252
  459. package/tools/ardy/setup-local.mjs +0 -189
  460. package/tools/ardy/setup-text-encoder-on-box.sh +0 -93
  461. package/tools/ardy/setup-text-encoder.py +0 -306
  462. package/tools/ardy/smpl-cskel27.mjs +0 -178
  463. package/tools/ardy/visual-qa.mjs +0 -196
  464. package/tools/ardy/vq-car.mjs +0 -52
  465. package/tools/bench/EXP3.md +0 -61
  466. package/tools/bench/cclay_bench_extract_incam.py +0 -125
  467. package/tools/bench/cclay_bench_extract_obs.py +0 -204
  468. package/tools/bench/cclay_bench_runner.py +0 -42
  469. package/tools/bench/cube-contact.mjs +0 -162
  470. package/tools/bench/exp3.mjs +0 -152
  471. package/tools/bench/extract-bench-lib.mjs +0 -216
  472. package/tools/bench/extract-bench.mjs +0 -302
  473. package/tools/bench/fal-generate.mjs +0 -88
  474. package/tools/bench/fit/README.md +0 -189
  475. package/tools/bench/fit/camera.mjs +0 -59
  476. package/tools/bench/fit/contact.mjs +0 -419
  477. package/tools/bench/fit/footlock.mjs +0 -234
  478. package/tools/bench/fit/motion.mjs +0 -85
  479. package/tools/bench/fit/pin.mjs +0 -27
  480. package/tools/bench/fit/remote.mjs +0 -105
  481. package/tools/bench/fit-bench.mjs +0 -126
  482. package/tools/bench/fit-sanity.mjs +0 -45
  483. package/tools/bench/metrics.mjs +0 -313
  484. package/tools/bench/obs/depth.mjs +0 -258
  485. package/tools/bench/obs/extrinsics.mjs +0 -175
  486. package/tools/bench/obs/fit_mannequin_betas.py +0 -340
  487. package/tools/bench/obs/ground.mjs +0 -294
  488. package/tools/bench/obs/heading.mjs +0 -151
  489. package/tools/bench/obs/ladder.mjs +0 -555
  490. package/tools/bench/obs/mannequin-betas.json +0 -124
  491. package/tools/bench/obs/remote.mjs +0 -83
  492. package/tools/bench/obs/rest_joints.py +0 -52
  493. package/tools/bench/obs/ybot-targets.mjs +0 -49
  494. package/tools/bench/obs-bench.mjs +0 -520
  495. package/tools/bench/score.mjs +0 -343
  496. package/tools/bench/summarize.mjs +0 -86
  497. package/tools/demo-worker/README.md +0 -137
  498. package/tools/demo-worker/api-client.mjs +0 -233
  499. package/tools/demo-worker/generate.mjs +0 -301
  500. package/tools/demo-worker/index.mjs +0 -466
  501. package/tools/demo-worker/sign-core.mjs +0 -112
  502. package/tools/demo-worker/sign.mjs +0 -161
  503. package/tools/gt-render/browser.mjs +0 -159
  504. package/tools/gt-render/camera-math.mjs +0 -276
  505. package/tools/gt-render/page.mjs +0 -267
  506. package/tools/gt-render/render.mjs +0 -514
  507. package/tools/gt-render/scene-box.mjs +0 -31
  508. package/tools/gt-render/take-transform.mjs +0 -87
  509. package/tools/kimodo/compare-seam.mjs +0 -108
  510. package/tools/kimodo/constraints.mjs +0 -244
  511. package/tools/kimodo/edit.mjs +0 -259
  512. package/tools/kimodo/effector-constraints.mjs +0 -200
  513. package/tools/kimodo/generate.mjs +0 -646
  514. package/tools/kimodo/local-output.mjs +0 -265
  515. package/tools/kimodo/measure-pose.mjs +0 -65
  516. package/tools/kimodo/measure-preserve.mjs +0 -899
  517. package/tools/kimodo/measure-waypoints.mjs +0 -55
  518. package/tools/kimodo/mlx-generate.py +0 -111
  519. package/tools/kimodo/pose-constraints.mjs +0 -248
  520. package/tools/kimodo/preserve-mask.mjs +0 -543
  521. package/tools/kimodo/read-npz.mjs +0 -189
  522. package/tools/kimodo/run-edit-on-box.mjs +0 -155
  523. package/tools/kimodo/run-sequence-on-box.mjs +0 -178
  524. package/tools/kimodo/runner.mjs +0 -284
  525. package/tools/kimodo/seam-batch.mjs +0 -77
  526. package/tools/kimodo/setup-local.mjs +0 -30
  527. package/tools/kimodo/setup-on-box.sh +0 -339
  528. package/tools/kimodo/soma77-to-cskel27.mjs +0 -305
  529. package/tools/morphgs/assets/gen_truth.py +0 -39
  530. package/tools/morphgs/assets/mesh_ori_rig.txt +0 -27
  531. package/tools/morphgs/assets/playback-check.mjs +0 -30
  532. package/tools/morphgs/demo-gate.sh +0 -26
  533. package/tools/morphgs/fbx2morphgs.mjs +0 -68
  534. package/tools/morphgs/morphgs-to-cskel27.mjs +0 -105
  535. package/tools/morphgs/patches/preprocess_src-none-mode.patch +0 -76
  536. package/tools/morphgs/setup-on-cluster.sh +0 -126
  537. package/tools/projflow/driver.py +0 -1069
  538. package/tools/projflow/generate.mjs +0 -805
  539. package/tools/projflow/hml22-to-cskel27.mjs +0 -735
  540. package/tools/projflow/line-edit-job.mjs +0 -547
  541. package/tools/projflow/replay.mjs +0 -452
  542. package/tools/projflow/runner.mjs +0 -269
  543. package/tools/projflow/service.mjs +0 -742
  544. package/tools/track/DESIGN.md +0 -513
  545. package/tools/track/backfill-provenance.mjs +0 -116
  546. package/tools/track/budget.mjs +0 -8
  547. package/tools/track/check-rig.mjs +0 -209
  548. package/tools/track/diagnostics.schema.json +0 -60
  549. package/tools/track/export-rig.mjs +0 -256
  550. package/tools/track/fallback.mjs +0 -7
  551. package/tools/track/fk-parity-fixture.mjs +0 -161
  552. package/tools/track/gate.mjs +0 -349
  553. package/tools/track/masks.mjs +0 -318
  554. package/tools/track/metrics.mjs +0 -201
  555. package/tools/track/publish-obs.mjs +0 -108
  556. package/tools/track/py/check_env.py +0 -61
  557. package/tools/track/py/eval_lr.py +0 -229
  558. package/tools/track/py/lr_viterbi.py +0 -294
  559. package/tools/track/py/masks.py +0 -405
  560. package/tools/track/py/objective.py +0 -382
  561. package/tools/track/py/rig.py +0 -508
  562. package/tools/track/py/scene.py +0 -326
  563. package/tools/track/py/test_joint_indices.py +0 -123
  564. package/tools/track/py/test_lr_viterbi.py +0 -195
  565. package/tools/track/py/test_masks.py +0 -229
  566. package/tools/track/py/test_rig.py +0 -288
  567. package/tools/track/py/test_scene.py +0 -354
  568. package/tools/track/py/test_track.py +0 -328
  569. package/tools/track/py/track.py +0 -411
  570. package/tools/track/remote.mjs +0 -140
  571. package/tools/track/rig-dump.mjs +0 -227
  572. package/tools/track/run-box-tests.mjs +0 -14
  573. package/tools/track/run-box.mjs +0 -168
  574. package/tools/track/setup-box.sh +0 -59
  575. package/tools/track/study-2d.mjs +0 -728
  576. /package/src/{ardy → motion}/collision-blockers.js +0 -0
  577. /package/src/{ardy → motion}/ground.js +0 -0
  578. /package/src/{ardy → motion}/ik-drag.js +0 -0
  579. /package/src/{ardy → motion}/ik-key-json.js +0 -0
  580. /package/src/{ardy → motion}/ik.js +0 -0
  581. /package/src/{ardy → motion}/physics-surface.js +0 -0
  582. /package/src/{ardy → motion}/playback-clock.js +0 -0
  583. /package/src/{ardy → motion}/range-pin.js +0 -0
@@ -1,547 +0,0 @@
1
- /**
2
- * line-edit-job.mjs — the composition layer for a ProjFlow line edit.
3
- *
4
- * Wave 1 landed four independent pieces: a runner that can drive the box
5
- * (runner.mjs), a wrapper that runs one edit and returns raw hml22 positions
6
- * (generate.mjs `lineEditOnBox`), a skeleton converter both ways
7
- * (hml22-to-cskel27.mjs), and a draw UI. NONE of them touches a cclay take.
8
- * This file is the only place where a drawn line becomes an edited npz, and it
9
- * is deliberately a pure async function rather than another CLI: the bridge
10
- * imports it the way it already imports handleExtract, so there is no second
11
- * process, no second argv contract, and no second copy of the frame arithmetic.
12
- *
13
- * THE PIPELINE, in one place because every step's rounding depends on the one
14
- * before it:
15
- *
16
- * take.npz (cskel27, 24 fps)
17
- * -> cskel27ToHml22Positions pure index gather, lossless
18
- * -> resample 24 -> 20 fps linear, positions only
19
- * -> source.npy (T20,22,3) what driver.py derives its T from
20
- * -> lineEditOnBox the GPU round trip
21
- * -> hml22ToCskel27Motion rotation lift, 20 fps
22
- * -> retimeMotion(.., 24) the same call Kimodo's 30 fps output makes
23
- * -> replaceMotionSegment outside the range: the ORIGINAL bytes
24
- * -> out.npz
25
- *
26
- * THE CLOCK RULE, stated once and used everywhere: every 24 -> 20 conversion is
27
- * `Math.round(frames24 * 20 / 24)` — the same half-up rounding for the clip
28
- * length and for BOTH ends of frameRange, so the range can never scale to a
29
- * span the resampled source does not contain. (Kimodo's cliGenFrames truncates
30
- * instead, because it must agree with a python `int()` on the box. Nothing
31
- * truncates here: driver.py derives T from the source npy THIS file writes, so
32
- * the source length is authoritative and the only requirement is that one rule
33
- * is used consistently. Rounding is the one that keeps the resampled clip the
34
- * same DURATION as the take, which is what the artist sees.)
35
- *
36
- * WHY LINEAR AND NOT NEAREST for the 24 -> 20 source resample: driver.py turns
37
- * those frames into hard identity rows (`build_preserve_rows`), so a resampled
38
- * frame is not a hint, it is an observation the sampler must satisfy exactly.
39
- * Nearest-neighbour would alias — every 6th frame duplicated — and the sampler
40
- * would faithfully reproduce a stutter that is not in the take.
41
- *
42
- * SPLICING IS A HARD CUT, ON PURPOSE. Outside the edit range the output is the
43
- * source take byte for byte (replaceMotionSegment copies the original arrays);
44
- * inside it, the converted result. No crossfade is applied. Gate GP2 asks
45
- * whether the seams pop, and a crossfade applied up front would make that
46
- * question unanswerable — so the job MEASURES the seams instead
47
- * (`seamStartDelta` / `seamEndDelta` against the take's own median frame delta)
48
- * and hands the numbers to the caller. A blend window is a follow-up knob to be
49
- * turned on evidence, not a silent default.
50
- */
51
-
52
- import { mkdtemp, rm } from "node:fs/promises";
53
- import { tmpdir } from "node:os";
54
- import { join } from "node:path";
55
-
56
- import { motionArraysToNpzMembers, replaceMotionSegment, writeNpz } from "../ardy/npz.mjs";
57
- import { readNpz } from "../kimodo/read-npz.mjs";
58
- import { retimeMotion } from "../../src/ardy/retime.js";
59
- import { cskel27ToHml22Positions, hml22ToCskel27Motion } from "./hml22-to-cskel27.mjs";
60
- import { GEN_FPS, NUM_JOINTS, lineEditOnBox, writeNpyFloat32 } from "./generate.mjs";
61
- import { nativeMotionPath } from "./runner.mjs";
62
-
63
- /** The app/timeline/bridge clock. Kept as a parameter everywhere below so the
64
- * tests can pin the arithmetic without a 24 fps fixture, but there is exactly
65
- * one production value and the bridge passes it explicitly. */
66
- export const APP_FPS = 24;
67
-
68
- const CSKEL_JOINTS = 27;
69
-
70
- /* ------------------------------------------------------------------------- */
71
- /* the clock */
72
- /* ------------------------------------------------------------------------- */
73
-
74
- /**
75
- * App frames (24 fps) -> generation frames (20 fps). THE rounding rule.
76
- *
77
- * Used for the clip length and for both ends of frameRange. Exported so the
78
- * verify file can pin it directly instead of inferring it from a spliced take.
79
- */
80
- export function toGenFrames(appFrames, { appFps = APP_FPS, genFps = GEN_FPS } = {}) {
81
- if (!Number.isFinite(appFrames)) throw new Error(`toGenFrames: ${JSON.stringify(appFrames)} is not a frame count`);
82
- return Math.round(appFrames * (genFps / appFps));
83
- }
84
-
85
- /**
86
- * The generation-clock frame range driver.py receives, from the app-clock
87
- * half-open range the artist selected.
88
- *
89
- * Two conversions happen here and both are worth naming:
90
- * 1. HALF-OPEN -> INCLUSIVE. Every range that crosses the bridge is
91
- * [start, end) — preserve.editRanges, the prompt schedule, C6 itself.
92
- * driver.py's is INCLUSIVE (`0 <= start <= end < frames`, span =
93
- * end - start + 1), so the exclusive end becomes `end - 1` here, once.
94
- * 2. The clamp. `end` must stay strictly inside the source's T (the driver
95
- * indexes it), and the span must stay >= 2 frames or the "line" is one
96
- * pose. A range ending exactly at the clip length is the common case —
97
- * the artist selected to the end of the take — and it lands on T20, which
98
- * is one past the last row.
99
- */
100
- export function scaleFrameRange({ startFrame, endFrame }, genFrames, { appFps = APP_FPS, genFps = GEN_FPS } = {}) {
101
- if (!Number.isInteger(startFrame) || !Number.isInteger(endFrame) || startFrame < 0 || endFrame <= startFrame) {
102
- throw new Error(`scaleFrameRange: ${startFrame}..${endFrame} is not a non-empty half-open range`);
103
- }
104
- if (!Number.isInteger(genFrames) || genFrames < 2) {
105
- throw new Error(`scaleFrameRange: the generation clip needs at least 2 frames, got ${genFrames}`);
106
- }
107
- const scale = (frame) => toGenFrames(frame, { appFps, genFps });
108
- // start can never sit on the last two rows: the span below needs somewhere
109
- // to end, and a two-frame edit at the very tail is still a two-frame edit.
110
- const start = Math.min(Math.max(scale(startFrame), 0), genFrames - 2);
111
- const endExclusive = Math.min(Math.max(scale(endFrame), start + 2), genFrames);
112
- return { start, end: endExclusive - 1 };
113
- }
114
-
115
- /**
116
- * `pins3d` on the app clock -> `pins3d` on the generation clock.
117
- *
118
- * ONE APP PIN BECOMES THE GENERATION FRAMES THAT APP FRAME IS REBUILT FROM,
119
- * which is usually two, and that is the whole subtlety of this function.
120
- *
121
- * The naive version rounded to a single generation frame and it was MEASURED
122
- * wrong end to end: the box satisfied the pin exactly (9.5e-7 m at generation
123
- * frame 83) and the finished take still missed it by 27 cm. Nothing was broken
124
- * in between — retimeMotion rebuilds app frame f from generation frames
125
- * `floor(f/ratio)` and `floor(f/ratio)+1`, blended by the fraction, and app
126
- * frame 100 sits at generation 83.33. So the take at frame 100 is two thirds of
127
- * an exactly-pinned frame and one third of its UNPINNED neighbour, and that
128
- * neighbour is 0.2 m away, because a lone hard observation with free
129
- * neighbours is a spike (the paper's own Plain Masking ablation, which it
130
- * reports as the worst configuration).
131
- *
132
- * Constraining BOTH bracketing frames makes the authored statement survive the
133
- * clock change: whatever the blend weight is, it is a blend of two frames that
134
- * both hold the pinned position, so the app frame the artist pinned lands on
135
- * the value they placed. It is not padding — it is the smallest set of
136
- * generation frames whose values determine the app frame that was authored.
137
- * Measured after the change: 27.1 cm -> 0.3 cm at the pinned frame.
138
- *
139
- * The exact-frame case (one app frame in six lands on a generation frame with
140
- * no remainder) emits a single frame, because there is no second frame in the
141
- * blend to hold.
142
- *
143
- * POSITIONS ARE NOT TOUCHED. Resampling the source is a time operation; a pin
144
- * is a point in space that happens to name a moment, and the only thing the
145
- * clock change can do to it is move which frames it names.
146
- *
147
- * Two app pins can claim one generation frame (they are 1.2 app frames apart at
148
- * most when that happens), and two rows on one frame would be two contradictory
149
- * answers for the sampler to average. The LAST pin wins, deliberately: pins
150
- * arrive in ascending frame order, so "the later one wins" is the same rule the
151
- * app's own place-a-pin-twice behaviour follows.
152
- */
153
- export function scalePins(pins, genFrames, { appFps = APP_FPS, genFps = GEN_FPS } = {}) {
154
- if (!Array.isArray(pins) || pins.length === 0) {
155
- throw new Error("scalePins: pins3d must be a non-empty array");
156
- }
157
- if (!Number.isInteger(genFrames) || genFrames < 1) {
158
- throw new Error(`scalePins: the generation clip needs at least 1 frame, got ${genFrames}`);
159
- }
160
- const byFrame = new Map();
161
- const clamp = (frame) => Math.min(genFrames - 1, Math.max(0, frame));
162
- for (const pin of pins) {
163
- if (!pin || !Number.isInteger(pin.frame) || !Array.isArray(pin.position) || pin.position.length !== 3) {
164
- throw new Error(`scalePins: ${JSON.stringify(pin)} is not a { frame, position:[x,y,z] } pin`);
165
- }
166
- const position = pin.position.map(Number);
167
- // retimeMotion's own walk, spelled the same way: s = f / (genFps/appFps).
168
- const exact = (pin.frame * genFps) / appFps;
169
- const low = clamp(Math.floor(exact));
170
- const high = clamp(exact === low ? low : low + 1);
171
- byFrame.set(low, { frame: low, position });
172
- if (high !== low) byFrame.set(high, { frame: high, position });
173
- }
174
- return [...byFrame.values()].sort((a, b) => a.frame - b.frame);
175
- }
176
-
177
- /**
178
- * Resample a flat [T, J, 3] position buffer onto `outFrames` rows by linear
179
- * interpolation.
180
- *
181
- * The walk is retimeMotion's own (`s = f / ratio`, floor, clamp, lerp) rather
182
- * than an arc-length or endpoint-matched resample, so the two directions of the
183
- * 24 <-> 20 trip agree about where frame f sits in time. The one deliberate
184
- * difference: `ratio` is the FRAME-COUNT ratio rather than the fps ratio, so a
185
- * clip whose rounded 20 fps length is a hair off 5/6 of its 24 fps length is
186
- * still covered end to end instead of stopping short. Positions only: the
187
- * source npy has no rotations and the driver wants none.
188
- */
189
- export function resamplePositions(positions, { joints, inFrames, outFrames }) {
190
- if (positions.length !== inFrames * joints * 3) {
191
- throw new Error(
192
- `resamplePositions: expected ${inFrames * joints * 3} values for ${inFrames} frames, got ${positions.length}`
193
- );
194
- }
195
- if (!Number.isInteger(outFrames) || outFrames < 1) {
196
- throw new Error(`resamplePositions: outFrames must be a positive integer, got ${outFrames}`);
197
- }
198
- const out = new Float32Array(outFrames * joints * 3);
199
- const ratio = outFrames === inFrames ? 1 : outFrames / inFrames;
200
- for (let frame = 0; frame < outFrames; frame += 1) {
201
- const s = frame / ratio;
202
- const i0 = Math.min(Math.floor(s), inFrames - 1);
203
- const i1 = Math.min(i0 + 1, inFrames - 1);
204
- const t = Math.min(Math.max(s - i0, 0), 1);
205
- const dst = frame * joints * 3;
206
- const src0 = i0 * joints * 3;
207
- const src1 = i1 * joints * 3;
208
- for (let index = 0; index < joints * 3; index += 1) {
209
- out[dst + index] = positions[src0 + index] * (1 - t) + positions[src1 + index] * t;
210
- }
211
- }
212
- return out;
213
- }
214
-
215
- /* ------------------------------------------------------------------------- */
216
- /* the take on disk */
217
- /* ------------------------------------------------------------------------- */
218
-
219
- /**
220
- * Read a cclay take npz into the motion-array shape every tool in this repo
221
- * passes around.
222
- *
223
- * readNpz (tools/kimodo/read-npz.mjs) rather than decodeMotionNpz: the browser
224
- * decoder is async and byte-cap hardened for untrusted input, which is the
225
- * wrong trade for a file this process wrote itself, and the bridge already uses
226
- * both readers for exactly that split.
227
- */
228
- export function readTakeNpz(path) {
229
- const members = readNpz(path);
230
- for (const required of ["local_rot_mats", "root_positions", "posed_joints"]) {
231
- if (!members[required]) throw new Error(`readTakeNpz: ${path} is missing ${required}`);
232
- }
233
- const rot = members.local_rot_mats;
234
- const root = members.root_positions;
235
- const posed = members.posed_joints;
236
- if (rot.shape.length !== 4 || rot.shape[1] !== CSKEL_JOINTS || rot.shape[2] !== 3 || rot.shape[3] !== 3) {
237
- throw new Error(`readTakeNpz: local_rot_mats must be [T,27,3,3], got [${rot.shape}]`);
238
- }
239
- if (posed.shape.length !== 3 || posed.shape[1] !== CSKEL_JOINTS || posed.shape[2] !== 3) {
240
- throw new Error(`readTakeNpz: posed_joints must be [T,27,3], got [${posed.shape}]`);
241
- }
242
- if (root.shape.length !== 2 || root.shape[1] !== 3) {
243
- throw new Error(`readTakeNpz: root_positions must be [T,3], got [${root.shape}]`);
244
- }
245
- const frames = rot.shape[0];
246
- if (posed.shape[0] !== frames || root.shape[0] !== frames) {
247
- throw new Error(
248
- `readTakeNpz: members disagree on frame count (${frames}, ${posed.shape[0]}, ${root.shape[0]})`
249
- );
250
- }
251
- // The bridge writes fps into every take it produces; a take without one is
252
- // not a cclay take, and guessing 24 would silently retime the edit.
253
- if (!members.fps) throw new Error(`readTakeNpz: ${path} has no fps member`);
254
- const fps = Math.round(members.fps.data[0]);
255
- if (!Number.isInteger(fps) || fps < 1) throw new Error(`readTakeNpz: ${path} has fps ${fps}`);
256
- const motion = {
257
- frames,
258
- fps,
259
- rotMats: rot.data,
260
- rootPos: root.data,
261
- posedJoints: posed.data,
262
- };
263
- if (members.person_scale) {
264
- const personScale = members.person_scale.data[0];
265
- if (personScale > 0 && personScale !== 1) motion.personScale = personScale;
266
- }
267
- return motion;
268
- }
269
-
270
- /* ------------------------------------------------------------------------- */
271
- /* the splice and its seams */
272
- /* ------------------------------------------------------------------------- */
273
-
274
- /** Largest per-joint travel between two consecutive frames of a take, in
275
- * metres. The unit gate GP2 is written in ("no frame-to-frame pop bigger than
276
- * the take's own median frame delta"). */
277
- export function maxJointStep(posedJoints, frame, { joints = CSKEL_JOINTS } = {}) {
278
- if (frame <= 0) return 0;
279
- let worst = 0;
280
- const current = frame * joints * 3;
281
- const previous = current - joints * 3;
282
- for (let joint = 0; joint < joints; joint += 1) {
283
- const step = Math.hypot(
284
- posedJoints[current + joint * 3] - posedJoints[previous + joint * 3],
285
- posedJoints[current + joint * 3 + 1] - posedJoints[previous + joint * 3 + 1],
286
- posedJoints[current + joint * 3 + 2] - posedJoints[previous + joint * 3 + 2]
287
- );
288
- if (step > worst) worst = step;
289
- }
290
- return worst;
291
- }
292
-
293
- /** The take's own median frame delta — the yardstick a seam is judged against.
294
- * Median, not mean: one real footfall must not raise the bar for the whole
295
- * clip. */
296
- export function medianFrameStep(motion, { joints = CSKEL_JOINTS } = {}) {
297
- if (motion.frames < 2) return 0;
298
- const steps = [];
299
- for (let frame = 1; frame < motion.frames; frame += 1) {
300
- steps.push(maxJointStep(motion.posedJoints, frame, { joints }));
301
- }
302
- steps.sort((a, b) => a - b);
303
- const middle = steps.length >> 1;
304
- return steps.length % 2 ? steps[middle] : (steps[middle - 1] + steps[middle]) / 2;
305
- }
306
-
307
- /**
308
- * Write `edited` into `take` over the half-open app-frame range, and measure
309
- * what the two cuts cost.
310
- *
311
- * `edited` may be one frame short or long of the take (the 24 -> 20 -> 24 round
312
- * trip is not a bijection: 117 app frames scale to 98 generation frames, which
313
- * scale back to 118). The tail index is clamped rather than the range refused —
314
- * the discrepancy is at most one frame at the very end of the clip and only
315
- * matters when the edit reaches it, whereas refusing would make whole clip
316
- * lengths un-editable. `framesClamped` in the returned meta says when it
317
- * happened, so the case is visible rather than silent.
318
- */
319
- export function spliceEditedRange(take, edited, { startFrame, endFrame }) {
320
- if (!Number.isInteger(startFrame) || !Number.isInteger(endFrame) || startFrame < 0 || endFrame <= startFrame) {
321
- throw new Error(`spliceEditedRange: ${startFrame}..${endFrame} is not a non-empty half-open range`);
322
- }
323
- if (endFrame > take.frames) {
324
- throw new Error(`spliceEditedRange: range ends at ${endFrame}, past the take's ${take.frames} frames`);
325
- }
326
- const span = endFrame - startFrame;
327
- const slice = {
328
- frames: span,
329
- fps: take.fps,
330
- rotMats: new Float32Array(span * CSKEL_JOINTS * 9),
331
- rootPos: new Float32Array(span * 3),
332
- posedJoints: new Float32Array(span * CSKEL_JOINTS * 3),
333
- };
334
- let framesClamped = 0;
335
- for (let index = 0; index < span; index += 1) {
336
- const wanted = startFrame + index;
337
- const source = Math.min(wanted, edited.frames - 1);
338
- if (source !== wanted) framesClamped += 1;
339
- slice.rotMats.set(
340
- edited.rotMats.subarray(source * CSKEL_JOINTS * 9, (source + 1) * CSKEL_JOINTS * 9),
341
- index * CSKEL_JOINTS * 9
342
- );
343
- slice.rootPos.set(edited.rootPos.subarray(source * 3, (source + 1) * 3), index * 3);
344
- slice.posedJoints.set(
345
- edited.posedJoints.subarray(source * CSKEL_JOINTS * 3, (source + 1) * CSKEL_JOINTS * 3),
346
- index * CSKEL_JOINTS * 3
347
- );
348
- }
349
- // replaceMotionSegment copies the base arrays and overwrites the span, so
350
- // every frame outside it is the source take's own bytes, unconditionally.
351
- const spliced = replaceMotionSegment(take, slice, startFrame);
352
- const median = medianFrameStep(take);
353
- const seams = {
354
- // The frame AT the range start is the first edited pose, so the jump into
355
- // it is the opening seam; endFrame is the first frame that is source
356
- // again, so the jump into IT is the closing seam. A range that starts at
357
- // frame 0 or ends at the clip has only one real seam; the other is 0.
358
- seamStartDelta: maxJointStep(spliced.posedJoints, startFrame),
359
- seamEndDelta: endFrame < spliced.frames ? maxJointStep(spliced.posedJoints, endFrame) : 0,
360
- medianFrameDelta: median,
361
- };
362
- seams.seamStartRatio = median > 0 ? seams.seamStartDelta / median : null;
363
- seams.seamEndRatio = median > 0 ? seams.seamEndDelta / median : null;
364
- return { motion: spliced, framesClamped, ...seams };
365
- }
366
-
367
- /* ------------------------------------------------------------------------- */
368
- /* the job */
369
- /* ------------------------------------------------------------------------- */
370
-
371
- /** C6's flat 9-element row-major R -> the 3x3 buildLineRequest validates. The
372
- * app sends flat (it is what a Matrix3 hands over); driver.py reshapes to (3,3)
373
- * either way, but generate.mjs refuses anything that is not nested, and this is
374
- * the seam where the two spellings meet. */
375
- function nestR(R) {
376
- if (Array.isArray(R) && R.length === 3 && R.every((row) => Array.isArray(row) && row.length === 3)) return R;
377
- if (!Array.isArray(R) || R.length !== 9) {
378
- throw new Error("lineEdit.camera.R must be 9 numbers (row-major) or a 3x3 array");
379
- }
380
- return [R.slice(0, 3), R.slice(3, 6), R.slice(6, 9)];
381
- }
382
-
383
- /**
384
- * Run one line edit end to end and write the edited take.
385
- *
386
- * @param {object} options
387
- * @param {object} options.lineEdit the C6 block exactly as the app sent it —
388
- * app-clock `frameRange {startFrame, endFrame}` (half-open), normalised
389
- * points2d, flat-R camera. Nothing in it is on the generation clock.
390
- * @param {string} options.takePath the stored cclay npz for `sourceMotion`.
391
- * @param {string} options.outputPath where the edited cclay npz goes.
392
- * @param {boolean} [options.preview] 20 ODE steps instead of 100.
393
- * @param {number} [options.seed]
394
- * @param {number} [options.appFps=24]
395
- * @param {(line: string) => void} [options.onStatus] box output, line by line.
396
- * @param {Function} [options.runLineEdit=lineEditOnBox] the box call. Injected
397
- * so the composition can be tested without a GPU: everything around it is
398
- * arithmetic on real npz files, and that arithmetic is where the bugs are.
399
- * @returns {Promise<object>} meta: clocks, frame counts, seam deltas, timings,
400
- * and the driver's own exactness numbers under `box`.
401
- */
402
- export async function runLineEditJob({
403
- lineEdit,
404
- takePath,
405
- outputPath,
406
- preview = false,
407
- seed,
408
- steps,
409
- appFps = APP_FPS,
410
- onStatus,
411
- runLineEdit = lineEditOnBox,
412
- } = {}) {
413
- if (!lineEdit || typeof lineEdit !== "object") throw new Error("runLineEditJob: lineEdit is required");
414
- if (!takePath) throw new Error("runLineEditJob: takePath is required");
415
- if (!outputPath) throw new Error("runLineEditJob: outputPath is required");
416
- const started = Date.now();
417
- const timings = {};
418
-
419
- // --- 1. the take ---------------------------------------------------------
420
- const take = readTakeNpz(takePath);
421
- if (take.fps !== appFps) {
422
- throw new Error(`runLineEditJob: the take is ${take.fps} fps; this bridge's clock is ${appFps}`);
423
- }
424
- const range = lineEdit.frameRange || {};
425
- const startFrame = range.startFrame;
426
- const endFrame = range.endFrame;
427
- if (!Number.isInteger(startFrame) || !Number.isInteger(endFrame) || startFrame < 0 || endFrame <= startFrame) {
428
- throw new Error(`runLineEditJob: frameRange ${startFrame}..${endFrame} is not a non-empty half-open range`);
429
- }
430
- // Re-checked against the FILE, not against the duration the request claimed:
431
- // the bridge validated the range against `duration * 24`, and a take that was
432
- // spliced or imported can disagree with it.
433
- if (endFrame > take.frames) {
434
- throw new Error(
435
- `runLineEditJob: frameRange ends at ${endFrame} but the source take has ${take.frames} frames`
436
- );
437
- }
438
- timings.readMs = Date.now() - started;
439
-
440
- // --- 2. 24 -> 20, positions only ----------------------------------------
441
- const genFrames = Math.max(2, toGenFrames(take.frames, { appFps, genFps: GEN_FPS }));
442
- const sourceHml22 = resamplePositions(
443
- cskel27ToHml22Positions({ frames: take.frames, posedJoints: take.posedJoints }),
444
- { joints: NUM_JOINTS, inFrames: take.frames, outFrames: genFrames }
445
- );
446
-
447
- // --- 3. the range on the generation clock -------------------------------
448
- const genRange = scaleFrameRange({ startFrame, endFrame }, genFrames, { appFps, genFps: GEN_FPS });
449
- const line = lineEdit.pins3d
450
- ? {
451
- track: lineEdit.track,
452
- frameRange: genRange,
453
- // NO CAMERA, and none is invented: a pin is already in the take's own
454
- // joint space (the app converts from viewport world with
455
- // worldPointToClip, the exact inverse of the trail projection), so the
456
- // positions go through UNCHANGED. cskel27ToHml22Positions is a pure
457
- // index gather with no coordinate transform — that is the whole reason
458
- // the wire can carry take-space metres and the driver can compare them
459
- // against source.npy directly.
460
- pins3d: scalePins(lineEdit.pins3d, genFrames, { appFps, genFps: GEN_FPS }),
461
- prompt: lineEdit.prompt ?? "",
462
- }
463
- : {
464
- track: lineEdit.track,
465
- frameRange: genRange,
466
- points2d: lineEdit.points2d,
467
- camera: { ...lineEdit.camera, R: nestR(lineEdit.camera?.R) },
468
- prompt: lineEdit.prompt ?? "",
469
- };
470
-
471
- // --- 4. the box ----------------------------------------------------------
472
- const workDir = await mkdtemp(join(tmpdir(), "cclay-line-edit-"));
473
- let result;
474
- try {
475
- const sourceNpy = join(workDir, "source.npy");
476
- writeNpyFloat32(sourceNpy, sourceHml22, [genFrames, NUM_JOINTS, 3]);
477
- const boxStarted = Date.now();
478
- result = await runLineEdit({
479
- sourceMotionNpy: sourceNpy,
480
- line,
481
- preview,
482
- ...(steps === undefined ? {} : { steps }),
483
- ...(seed === undefined ? {} : { seed }),
484
- // The raw hml22 result is kept beside the take for the same reason
485
- // Kimodo keeps its native npz: it is what a LATER edit would source
486
- // from, and the cskel27 file we write below is a lift of it.
487
- nativeOut: nativeMotionPath(outputPath),
488
- onLine: onStatus,
489
- });
490
- timings.boxMs = Date.now() - boxStarted;
491
- } finally {
492
- await rm(workDir, { recursive: true, force: true });
493
- }
494
- if (!result || !result.positions) throw new Error("runLineEditJob: the box returned no positions");
495
- if (result.frames !== genFrames) {
496
- // driver.py edits IN PLACE on the source's own T; a different count means
497
- // the source npy and the result describe different clips and the splice
498
- // below would be aligned to the wrong frames.
499
- throw new Error(
500
- `runLineEditJob: the box returned ${result.frames} frames for a ${genFrames}-frame source`
501
- );
502
- }
503
-
504
- // --- 5. 22 -> 27, then 20 -> 24 -----------------------------------------
505
- const convertStarted = Date.now();
506
- const converted = hml22ToCskel27Motion({ positions: result.positions, fps: result.fps ?? GEN_FPS });
507
- // The same call Kimodo's 30 fps output makes on its way to the timeline;
508
- // rotations slerp, the root lerps, and every other joint is re-derived by FK
509
- // so bone lengths never enter the blend.
510
- const retimed = retimeMotion({ ...converted, fps: converted.fps }, appFps);
511
- timings.convertMs = Date.now() - convertStarted;
512
-
513
- // --- 6. splice and write -------------------------------------------------
514
- const spliced = spliceEditedRange(take, retimed, { startFrame, endFrame });
515
- writeNpz(outputPath, motionArraysToNpzMembers({
516
- frames: spliced.motion.frames,
517
- fps: spliced.motion.fps,
518
- rotMats: spliced.motion.rotMats,
519
- rootPos: spliced.motion.rootPos,
520
- posedJoints: spliced.motion.posedJoints,
521
- personScale: spliced.motion.personScale,
522
- }));
523
- timings.totalMs = Date.now() - started;
524
-
525
- return {
526
- output: outputPath,
527
- nativeMotion: result.nativeNpy ?? null,
528
- track: lineEdit.track,
529
- frames: spliced.motion.frames,
530
- fps: spliced.motion.fps,
531
- appFrameRange: { startFrame, endFrame },
532
- genFps: GEN_FPS,
533
- genFrames,
534
- // Reported INCLUSIVE, the way driver.py received it, so a box log and this
535
- // meta can be compared without translating.
536
- genFrameRange: genRange,
537
- retimedFrames: retimed.frames,
538
- framesClamped: spliced.framesClamped,
539
- seamStartDelta: spliced.seamStartDelta,
540
- seamEndDelta: spliced.seamEndDelta,
541
- medianFrameDelta: spliced.medianFrameDelta,
542
- seamStartRatio: spliced.seamStartRatio,
543
- seamEndRatio: spliced.seamEndRatio,
544
- timings,
545
- box: result.meta ?? null,
546
- };
547
- }