cozyclay 2.1.1 → 3.0.1

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