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,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
- }