cozyclay 2.1.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (583) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +12 -30
  3. package/bin/agent/agent-routes.mjs +40 -113
  4. package/bin/agent/agent-runner.mjs +9 -10
  5. package/bin/agent/cliproxy-provider.mjs +135 -0
  6. package/bin/agent/credential-store.mjs +30 -7
  7. package/bin/agent/pi-tools.mjs +4 -1
  8. package/bin/agent/provider-keys.mjs +32 -0
  9. package/bin/agent/provider-login.mjs +110 -0
  10. package/bin/agent/provider-routes.mjs +99 -0
  11. package/bin/agent/providers.mjs +19 -97
  12. package/bin/agent/script-tool.mjs +17 -4
  13. package/bin/agent/studio-motion-reference.mjs +23 -0
  14. package/bin/agent/studio-prompt.mjs +1 -1
  15. package/bin/agent/studio-rebase-text.mjs +32 -0
  16. package/bin/agent/studio-rebase.mjs +26 -39
  17. package/bin/agent/studio-tools.mjs +9 -28
  18. package/bin/agent/tool-arguments.mjs +49 -0
  19. package/bin/agent/turn-frames.mjs +66 -0
  20. package/bin/codex-auth.mjs +9 -2
  21. package/bin/cozyclay.mjs +10 -116
  22. package/bin/live/cli.mjs +22 -6
  23. package/bin/mcp-runtime.mjs +2 -12
  24. package/bin/package-signature.mjs +0 -4
  25. package/dist/app/index.html +7 -6
  26. package/dist/assets/EXRLoader-CvVIvGVE.js +1 -0
  27. package/dist/assets/analytics-C9sUnndd.js +1 -0
  28. package/dist/assets/app-CCh--1Hc.js +5087 -0
  29. package/dist/assets/app-f-fkV5uP.css +1 -0
  30. package/dist/assets/compile-worker-CkxiD6lH.js +5 -0
  31. package/dist/assets/fflate.module-KbGj5-H6.js +5 -0
  32. package/dist/assets/{first-shot-handoff-BlT33CGf.js → first-shot-handoff-CC4xe4HM.js} +1 -1
  33. package/dist/assets/{landing-BSesrCpL.js → landing-DjHD8u7k.js} +1 -1
  34. package/dist/assets/scene-asset-sync-Hx_FzdOH.js +20 -0
  35. package/dist/assets/scene-asset-sync-_S8NQl5B.css +1 -0
  36. package/dist/assets/workflow-C4yKH1p4.js +179 -0
  37. package/dist/cozyclay-package.json +1 -1
  38. package/dist/index.html +3 -3
  39. package/dist/llms.txt +14 -0
  40. package/dist/sw.js +3 -6
  41. package/dist/workflow/index.html +5 -4
  42. package/mcp/LIVE-PROTOCOL.md +7 -47
  43. package/mcp/README.md +16 -26
  44. package/mcp/live-hub.mjs +6 -10
  45. package/mcp/package.json +1 -3
  46. package/mcp/tool-handlers.mjs +4 -168
  47. package/package.json +3 -22
  48. package/src/App.jsx +174 -2639
  49. package/src/agent-progress.js +79 -0
  50. package/src/agent-turn-mark.js +30 -0
  51. package/src/analytics.js +1 -19
  52. package/src/app-context.js +5 -0
  53. package/src/app-stage.jsx +2 -34
  54. package/src/asset-pane.jsx +6 -5
  55. package/src/camera-block.js +21 -0
  56. package/src/camera-direction.js +120 -0
  57. package/src/character-kind.js +3 -9
  58. package/src/command-bus.js +8 -6
  59. package/src/commands/cast.js +5 -34
  60. package/src/commands/elements/character.js +1 -1
  61. package/src/commands/elements/motion.js +1 -6
  62. package/src/commands/elements/object.js +6 -2
  63. package/src/commands/elements/shot.js +4 -1
  64. package/src/commands/elements.js +38 -14
  65. package/src/commands/index.js +3 -1
  66. package/src/commands/kits.js +37 -0
  67. package/src/commands/motion-timing.js +57 -0
  68. package/src/commands/motion.js +9 -152
  69. package/src/commands/motion3-compile.js +65 -0
  70. package/src/commands/motion3.js +207 -0
  71. package/src/commands/objects.js +6 -4
  72. package/src/commands/shot.js +49 -11
  73. package/src/commands/stage.js +14 -1
  74. package/src/commands/view.js +4 -1
  75. package/src/document-mirrors.js +42 -0
  76. package/src/document-store.js +4 -0
  77. package/src/domains/cast.js +19 -194
  78. package/src/domains/motion/motion-range-pin-rules.js +1 -1
  79. package/src/domains/motion/take-source.js +51 -0
  80. package/src/domains/motion.js +60 -1838
  81. package/src/domains/scenes.js +12 -55
  82. package/src/domains/shots.js +2 -2
  83. package/src/domains/stage.js +4 -2
  84. package/src/draft-ghost.js +46 -0
  85. package/src/draft-ghost.jsx +129 -0
  86. package/src/dualview.jsx +4 -3
  87. package/src/effects/EffectsLayer.jsx +112 -0
  88. package/src/effects/effect-meshes.js +232 -0
  89. package/src/effects/effect-record.js +103 -0
  90. package/src/effects/emitter.js +59 -0
  91. package/src/effects/simulate.js +261 -0
  92. package/src/effects/state.js +59 -0
  93. package/src/execution-telemetry.js +2 -3
  94. package/src/first-success-guide.jsx +5 -5
  95. package/src/ghost-set.js +257 -0
  96. package/src/held-object-edit.jsx +113 -0
  97. package/src/held-object-pose.js +175 -0
  98. package/src/hierarchy-model.js +1 -1
  99. package/src/hierarchy-panel.jsx +1 -1
  100. package/src/imported-mesh.jsx +103 -0
  101. package/src/kits/held.js +84 -0
  102. package/src/kits/index.js +70 -0
  103. package/src/kits/smoking.js +57 -0
  104. package/src/light-blockers.js +93 -0
  105. package/src/motion/beat-board-model.js +84 -0
  106. package/src/motion/beat-board.css +124 -0
  107. package/src/motion/beat-board.jsx +59 -0
  108. package/src/motion/center-of-mass.js +66 -0
  109. package/src/motion/fix-collisions.js +850 -0
  110. package/src/motion/physics-review.js +118 -0
  111. package/src/{ardy → motion}/physics-support.js +1 -76
  112. package/src/motion/rig-snapshot.js +22 -0
  113. package/src/motion/take-segments.jsx +140 -0
  114. package/src/{ardy → motion}/timeline-coordinates.js +0 -21
  115. package/src/{ardy → motion}/timeline.css +1 -2
  116. package/src/{ardy → motion}/timeline.jsx +48 -419
  117. package/src/motion/waypoints.js +76 -0
  118. package/src/motion3/beats.js +83 -0
  119. package/src/motion3/body.js +158 -0
  120. package/src/motion3/bones.js +47 -0
  121. package/src/motion3/carried.js +47 -0
  122. package/src/motion3/compile-job.js +75 -0
  123. package/src/motion3/compile-worker.js +10 -0
  124. package/src/motion3/compiler/anchors.js +105 -0
  125. package/src/motion3/compiler/arm-orient.js +128 -0
  126. package/src/motion3/compiler/channels.js +125 -0
  127. package/src/motion3/compiler/compile.js +205 -0
  128. package/src/motion3/compiler/gait-rhythm.js +97 -0
  129. package/src/motion3/compiler/grip.js +171 -0
  130. package/src/motion3/compiler/head-steady.js +48 -0
  131. package/src/motion3/compiler/hold.js +134 -0
  132. package/src/motion3/compiler/idle.js +113 -0
  133. package/src/motion3/compiler/limbs.js +170 -0
  134. package/src/motion3/compiler/math.js +72 -0
  135. package/src/motion3/compiler/pose.js +162 -0
  136. package/src/motion3/compiler/quat.js +49 -0
  137. package/src/motion3/compiler/skeleton.js +74 -0
  138. package/src/motion3/compiler/validate-common.js +32 -0
  139. package/src/motion3/compiler/validate-goals.js +118 -0
  140. package/src/motion3/compiler/validate-objects.js +143 -0
  141. package/src/motion3/compiler/validate-parts.js +116 -0
  142. package/src/motion3/compiler/validate.js +122 -0
  143. package/src/motion3/compiler/vocab.js +73 -0
  144. package/src/motion3/compiler/walk-pelvis.js +275 -0
  145. package/src/motion3/compiler/walk.js +207 -0
  146. package/src/motion3/compiler/wrist-clamp.js +32 -0
  147. package/src/motion3/delta.js +47 -0
  148. package/src/motion3/describe.js +174 -0
  149. package/src/motion3/docs.js +27 -0
  150. package/src/motion3/edit-carry.js +21 -0
  151. package/src/motion3/fix/ease.js +16 -0
  152. package/src/motion3/fix/ground.js +37 -0
  153. package/src/motion3/fix/index.js +52 -0
  154. package/src/motion3/fix/push.js +71 -0
  155. package/src/motion3/fix/reach.js +46 -0
  156. package/src/motion3/fix/retime.js +79 -0
  157. package/src/motion3/fix/self.js +211 -0
  158. package/src/motion3/fix/slide.js +70 -0
  159. package/src/motion3/fix/turn.js +51 -0
  160. package/src/motion3/follow.js +39 -0
  161. package/src/motion3/free-motion.js +173 -0
  162. package/src/motion3/grip-choice.js +130 -0
  163. package/src/motion3/hold-plan.js +157 -0
  164. package/src/motion3/json-patch.js +77 -0
  165. package/src/motion3/key-aim.js +105 -0
  166. package/src/motion3/key-edit.js +82 -0
  167. package/src/motion3/lenient.js +127 -0
  168. package/src/motion3/object-keys.js +236 -0
  169. package/src/motion3/object-pose.js +182 -0
  170. package/src/motion3/object-tracks.js +12 -0
  171. package/src/motion3/pipeline.js +102 -0
  172. package/src/motion3/playback.js +51 -0
  173. package/src/motion3/probes.js +91 -0
  174. package/src/motion3/qa/body-checks.js +34 -0
  175. package/src/motion3/qa/check-names.js +14 -0
  176. package/src/motion3/qa/checks.js +121 -0
  177. package/src/motion3/qa/contact-checks.js +51 -0
  178. package/src/motion3/qa/explain.js +80 -0
  179. package/src/motion3/qa/fmt.js +5 -0
  180. package/src/motion3/qa/foot-checks.js +72 -0
  181. package/src/motion3/qa/geometry.js +77 -0
  182. package/src/motion3/qa/goals.js +191 -0
  183. package/src/motion3/qa/ground.js +60 -0
  184. package/src/motion3/qa/hints-contact.js +179 -0
  185. package/src/motion3/qa/hints-goal.js +160 -0
  186. package/src/motion3/qa/hints-knee.js +24 -0
  187. package/src/motion3/qa/hints-motion.js +209 -0
  188. package/src/motion3/qa/hints-object.js +91 -0
  189. package/src/motion3/qa/index.js +42 -0
  190. package/src/motion3/qa/key-checks.js +82 -0
  191. package/src/motion3/qa/near.js +96 -0
  192. package/src/motion3/qa/object-checks.js +135 -0
  193. package/src/motion3/qa/patch.js +161 -0
  194. package/src/motion3/qa/severity.js +38 -0
  195. package/src/motion3/qa/spec-checks.js +65 -0
  196. package/src/motion3/qa/style.js +218 -0
  197. package/src/motion3/qa/wrist-checks.js +69 -0
  198. package/src/motion3/qa/wrist-obs.js +60 -0
  199. package/src/motion3/refs.js +120 -0
  200. package/src/motion3/repair-tune.js +121 -0
  201. package/src/motion3/repair.js +83 -0
  202. package/src/motion3/report.js +67 -0
  203. package/src/motion3/rigs/index.js +14 -0
  204. package/src/motion3/rigs/x-bot-tpose-body.json +1 -0
  205. package/src/motion3/rigs/x-bot-tpose.json +1 -0
  206. package/src/motion3/rigs/y-bot-tpose-body.json +1 -0
  207. package/src/motion3/rigs/y-bot-tpose.json +1 -0
  208. package/src/motion3/scene.js +60 -0
  209. package/src/motion3/schema/brief.js +89 -0
  210. package/src/motion3/schema/fields.js +44 -0
  211. package/src/motion3/schema/spec-examples.js +46 -0
  212. package/src/motion3/schema/topics.js +111 -0
  213. package/src/motion3/schema/types.js +147 -0
  214. package/src/motion3/search.js +117 -0
  215. package/src/motion3/second-hand.js +112 -0
  216. package/src/motion3/skills/basic.js +82 -0
  217. package/src/motion3/skills/book.js +76 -0
  218. package/src/motion3/skills/car.js +145 -0
  219. package/src/motion3/skills/frames-auto.js +29 -0
  220. package/src/motion3/skills/index.js +83 -0
  221. package/src/motion3/skills/info.js +65 -0
  222. package/src/motion3/skills/smoke-kit.js +109 -0
  223. package/src/motion3/skills/smoke.js +160 -0
  224. package/src/motion3/spec-store.js +59 -0
  225. package/src/motion3/take-edit.js +155 -0
  226. package/src/motion3/take.js +63 -0
  227. package/src/motion3/timeline.js +127 -0
  228. package/src/motion3/views.js +121 -0
  229. package/src/motion3/walk-plan.js +119 -0
  230. package/src/object-catalog.jsx +7 -1
  231. package/src/object-colors.js +98 -0
  232. package/src/object-gizmo-parts.js +115 -0
  233. package/src/object-gizmo.jsx +37 -122
  234. package/src/object-glow.js +139 -0
  235. package/src/object-hidden.js +33 -0
  236. package/src/object-look.js +196 -0
  237. package/src/object-size.js +10 -0
  238. package/src/object-travel.js +71 -6
  239. package/src/panels/CameraPanel.jsx +7 -7
  240. package/src/panels/HdrEnvironmentControls.jsx +59 -0
  241. package/src/panels/LightPanel.jsx +25 -4
  242. package/src/panels/ObjectLookPanel.jsx +88 -0
  243. package/src/panels/PoseImagePanel.jsx +1 -1
  244. package/src/panels/PosePanel.jsx +2 -17
  245. package/src/panels/RigControlPanel.jsx +5 -53
  246. package/src/panels/SubjectBox.jsx +4 -1
  247. package/src/panels/details.css +14 -0
  248. package/src/posestudio.jsx +4 -27
  249. package/src/project-browser.jsx +3 -4
  250. package/src/project-resources.js +18 -31
  251. package/src/project.js +13 -104
  252. package/src/props.jsx +25 -280
  253. package/src/rail-dot-drag.js +23 -0
  254. package/src/render-passes.js +10 -0
  255. package/src/resource-status.jsx +1 -0
  256. package/src/result-modal.jsx +4 -19
  257. package/src/room.jsx +20 -12
  258. package/src/scene-assets.js +8 -6
  259. package/src/scene-light-place.js +72 -0
  260. package/src/scene-lights.jsx +148 -0
  261. package/src/scene-objects.js +31 -139
  262. package/src/scenes.js +15 -105
  263. package/src/semantic-edit.js +2 -5
  264. package/src/set-pieces.jsx +183 -0
  265. package/src/settings-menu.jsx +3 -11
  266. package/src/shell/BottomDock.jsx +11 -53
  267. package/src/shell/CompositionGuideButton.jsx +20 -0
  268. package/src/shell/DetailsSlot.jsx +16 -200
  269. package/src/shell/GhostsMenuGroup.jsx +74 -0
  270. package/src/shell/LibrarySlot.jsx +1 -3
  271. package/src/shell/MenuBar.jsx +3 -3
  272. package/src/shell/PreferencesDialog.jsx +15 -264
  273. package/src/shell/PreferencesSlot.jsx +2 -8
  274. package/src/shell/ShotPreviewHeader.jsx +17 -0
  275. package/src/shell/TopBar.jsx +5 -83
  276. package/src/shell/ViewportToolbar.jsx +23 -93
  277. package/src/shell/agent-glass.css +19 -6
  278. package/src/shell/details-selection.js +42 -0
  279. package/src/shell/ghosts-menu.css +22 -0
  280. package/src/shell/glass-regions.css +29 -3
  281. package/src/shell/preferences.css +6 -1
  282. package/src/shell/viewport.css +12 -0
  283. package/src/shot-authoring.js +4 -7
  284. package/src/soft-shadows.js +37 -0
  285. package/src/stage-attach-frames.js +12 -12
  286. package/src/stage-canvas-rigs.jsx +3 -3
  287. package/src/stage-capture.jsx +42 -8
  288. package/src/stage-characters.jsx +22 -216
  289. package/src/stage-editor-gizmos.jsx +6 -6
  290. package/src/stage-environment.js +93 -0
  291. package/src/stage-hdr-builtins.js +107 -0
  292. package/src/stage-hdr-image.js +116 -0
  293. package/src/stage-hdr-library.js +70 -0
  294. package/src/stage-hdr-map.js +94 -0
  295. package/src/stage-hdr.js +130 -0
  296. package/src/stage-lighting.js +149 -0
  297. package/src/stage-occlusion.js +210 -0
  298. package/src/stage-sky.jsx +159 -0
  299. package/src/stage-startup-defaults.js +5 -9
  300. package/src/studio-actions.js +12 -14
  301. package/src/studio-agent-commands.js +46 -16
  302. package/src/studio-agent-motion.js +9 -9
  303. package/src/studio-agent-protocol.js +46 -73
  304. package/src/studio-app-binding.js +29 -45
  305. package/src/studio-asset-list.js +17 -0
  306. package/src/studio-contact-sheet.js +37 -5
  307. package/src/studio-elements.js +27 -7
  308. package/src/studio-entity-bounds.js +18 -0
  309. package/src/studio-error-codes.js +8 -0
  310. package/src/studio-export-framing.js +152 -0
  311. package/src/studio-lighting-check.js +103 -0
  312. package/src/studio-motion-visual.js +150 -0
  313. package/src/studio-schema-errors.js +80 -0
  314. package/src/studio-support-gap.js +74 -0
  315. package/src/styles.css +1 -19
  316. package/src/timeline-defaults.js +5 -0
  317. package/src/timeline-extent.js +8 -20
  318. package/src/tutorial-walk.js +17 -0
  319. package/src/ui-scrub.js +1 -1
  320. package/src/use-shot-camera-tracks.js +12 -0
  321. package/src/view-ghosts.js +97 -0
  322. package/src/view-ghosts.jsx +49 -0
  323. package/src/workflow/AgentPanel.jsx +47 -166
  324. package/src/workflow/AgentProgressCard.jsx +107 -0
  325. package/src/workflow/AgentReplyText.jsx +37 -0
  326. package/src/workflow/AgentTurnSteps.jsx +84 -0
  327. package/src/workflow/LoginPanel.jsx +140 -0
  328. package/src/workflow/WorkflowBuilder.jsx +7 -23
  329. package/src/workflow/agent-client.js +54 -51
  330. package/src/workflow/agent-panel.css +71 -1
  331. package/src/workflow/agent-progress-card.css +167 -0
  332. package/src/workflow/agent-progress-view.js +47 -0
  333. package/src/workflow/agent-turn-steps.css +113 -0
  334. package/src/workflow/agent-turn.js +191 -0
  335. package/src/workflow/login-client.js +34 -0
  336. package/src/workflow/motion-input.js +18 -31
  337. package/src/workflow/node-schema.js +1 -1
  338. package/src/workflow/reply-markdown.js +69 -0
  339. package/src/workflow/scene-asset-sync.js +8 -8
  340. package/tools/agent-session-stats.mjs +61 -0
  341. package/tools/dev-full.mjs +5 -56
  342. package/tools/eslint-baseline.json +12 -13
  343. package/tools/headless-studio.mjs +85 -0
  344. package/tools/knip-baseline.json +0 -179
  345. package/tools/loc-baseline.json +33 -83
  346. package/tools/motion3/extract-body.mjs +103 -0
  347. package/tools/motion3/extract-rig.mjs +85 -0
  348. package/tools/process-supervisor.mjs +0 -88
  349. package/tools/run-tests.mjs +9 -124
  350. package/bin/agent/motion-runtime.mjs +0 -76
  351. package/dist/ardy/cskel27-rest.json +0 -557
  352. package/dist/assets/analytics-B41FmPYI.js +0 -1
  353. package/dist/assets/app-Do-Og5Pu.css +0 -1
  354. package/dist/assets/app-DwP-tl2x.js +0 -4920
  355. package/dist/assets/config-Bx7pYsD9.js +0 -1
  356. package/dist/assets/config-DeoFTYzb.css +0 -1
  357. package/dist/assets/demo-Dt0qZfS7.js +0 -1
  358. package/dist/assets/scene-asset-sync-BCWDJB1d.js +0 -19
  359. package/dist/assets/scene-asset-sync-DjJ_cJ2R.css +0 -1
  360. package/dist/assets/ticket-jqMhqL88.js +0 -1
  361. package/dist/assets/workflow-Br-K7Srs.js +0 -179
  362. package/dist/demo/walk-then-stop.npz +0 -0
  363. package/mcp/ardy-prompts.mjs +0 -218
  364. package/src/ardy/auto-fix-panel.css +0 -108
  365. package/src/ardy/auto-physics.js +0 -1194
  366. package/src/ardy/client.js +0 -153
  367. package/src/ardy/convert.js +0 -313
  368. package/src/ardy/cskel27-neutral.js +0 -39
  369. package/src/ardy/cskel27.js +0 -77
  370. package/src/ardy/export.js +0 -165
  371. package/src/ardy/fix-collisions.js +0 -2257
  372. package/src/ardy/foot-lock.js +0 -230
  373. package/src/ardy/motion-calibration.js +0 -118
  374. package/src/ardy/motion-edit.js +0 -213
  375. package/src/ardy/motion-url.js +0 -55
  376. package/src/ardy/mp4-duration.js +0 -102
  377. package/src/ardy/npz.js +0 -595
  378. package/src/ardy/physics-panel.css +0 -21
  379. package/src/ardy/physics-panel.jsx +0 -89
  380. package/src/ardy/physics-refine.js +0 -111
  381. package/src/ardy/physics-review.js +0 -567
  382. package/src/ardy/physics-temporal.js +0 -54
  383. package/src/ardy/platform-fit-panel.css +0 -64
  384. package/src/ardy/platform-fit-panel.jsx +0 -41
  385. package/src/ardy/platform-fit.js +0 -326
  386. package/src/ardy/playback.js +0 -711
  387. package/src/ardy/pose-pin.js +0 -97
  388. package/src/ardy/prompt-clips.js +0 -30
  389. package/src/ardy/retime.js +0 -211
  390. package/src/ardy/root-drop.js +0 -369
  391. package/src/ardy/timeline-resize.js +0 -13
  392. package/src/ardy/to-cskel27.js +0 -225
  393. package/src/ardy/trim.js +0 -33
  394. package/src/ardy/waypoints.js +0 -369
  395. package/src/ardy-bridge-contract.js +0 -281
  396. package/src/domains/motion/motion-video-capture.js +0 -664
  397. package/src/i2v-motion-client.js +0 -77
  398. package/src/i2v-motion-studio.jsx +0 -180
  399. package/src/line-edit.js +0 -1348
  400. package/src/motion/generation.js +0 -105
  401. package/src/motion-readiness-ui.jsx +0 -67
  402. package/src/motion-readiness.js +0 -40
  403. package/src/motion-resources.js +0 -177
  404. package/src/motion-store.js +0 -65
  405. package/src/motion-trail.js +0 -601
  406. package/src/multimodel-ingest.js +0 -394
  407. package/src/panels/PromptBlocksPanel.jsx +0 -444
  408. package/src/panels/VideoCapturePanel.jsx +0 -181
  409. package/src/pose-extract/browser.js +0 -42
  410. package/src/pose-extract/detector.js +0 -100
  411. package/src/pose-extract/fit.js +0 -227
  412. package/src/pose-extract/image-frame.js +0 -51
  413. package/src/pose-extract/index.js +0 -58
  414. package/src/pose-extract/keyframes.js +0 -74
  415. package/src/pose-extract/landmarks.js +0 -162
  416. package/src/pose-extract/mirror.js +0 -153
  417. package/src/pose-extract/take.js +0 -131
  418. package/src/pose-extract/video-frames.js +0 -91
  419. package/src/take-recipe.js +0 -165
  420. package/src/trail-key-conflicts.js +0 -39
  421. package/src/trail-pick.js +0 -59
  422. package/src/use-i2v-capture-modal.js +0 -15
  423. package/tools/ardy/BRIDGE.md +0 -37
  424. package/tools/ardy/GVHMR-NPZ.md +0 -68
  425. package/tools/ardy/README.md +0 -124
  426. package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
  427. package/tools/ardy/artifacts.mjs +0 -34
  428. package/tools/ardy/bridge.mjs +0 -1732
  429. package/tools/ardy/bvh-cskel27.mjs +0 -1265
  430. package/tools/ardy/cclay_constrained_generate.py +0 -561
  431. package/tools/ardy/cclay_gvhmr_worker.py +0 -264
  432. package/tools/ardy/cclay_motion_edit.py +0 -510
  433. package/tools/ardy/cclay_sequence_generate.py +0 -705
  434. package/tools/ardy/cclay_worker.py +0 -304
  435. package/tools/ardy/dump-npz.py +0 -205
  436. package/tools/ardy/extract-rest.mjs +0 -299
  437. package/tools/ardy/extract.mjs +0 -507
  438. package/tools/ardy/footage.mjs +0 -467
  439. package/tools/ardy/gvhmr-floor.mjs +0 -102
  440. package/tools/ardy/gvhmr-to-cskel27.mjs +0 -21
  441. package/tools/ardy/gvhmr_fastpath.py +0 -168
  442. package/tools/ardy/gvhmr_trajectory.py +0 -240
  443. package/tools/ardy/merge-text-encoder.py +0 -160
  444. package/tools/ardy/mocap-metrics.mjs +0 -200
  445. package/tools/ardy/mocap-quality-gate.mjs +0 -110
  446. package/tools/ardy/motion-stabilize.mjs +0 -348
  447. package/tools/ardy/npz.mjs +0 -414
  448. package/tools/ardy/pose-to-npz.mjs +0 -106
  449. package/tools/ardy/prompt-limits.mjs +0 -5
  450. package/tools/ardy/run-edit-on-box.sh +0 -73
  451. package/tools/ardy/run-local.mjs +0 -233
  452. package/tools/ardy/run-on-box.sh +0 -593
  453. package/tools/ardy/run-sequence-on-box.sh +0 -177
  454. package/tools/ardy/runners/gvhmr-worker.mjs +0 -206
  455. package/tools/ardy/runners/index.mjs +0 -18
  456. package/tools/ardy/runners/local.mjs +0 -455
  457. package/tools/ardy/runners/proc.mjs +0 -125
  458. package/tools/ardy/runners/remote.mjs +0 -252
  459. package/tools/ardy/setup-local.mjs +0 -189
  460. package/tools/ardy/setup-text-encoder-on-box.sh +0 -93
  461. package/tools/ardy/setup-text-encoder.py +0 -306
  462. package/tools/ardy/smpl-cskel27.mjs +0 -178
  463. package/tools/ardy/visual-qa.mjs +0 -196
  464. package/tools/ardy/vq-car.mjs +0 -52
  465. package/tools/bench/EXP3.md +0 -61
  466. package/tools/bench/cclay_bench_extract_incam.py +0 -125
  467. package/tools/bench/cclay_bench_extract_obs.py +0 -204
  468. package/tools/bench/cclay_bench_runner.py +0 -42
  469. package/tools/bench/cube-contact.mjs +0 -162
  470. package/tools/bench/exp3.mjs +0 -152
  471. package/tools/bench/extract-bench-lib.mjs +0 -216
  472. package/tools/bench/extract-bench.mjs +0 -302
  473. package/tools/bench/fal-generate.mjs +0 -88
  474. package/tools/bench/fit/README.md +0 -189
  475. package/tools/bench/fit/camera.mjs +0 -59
  476. package/tools/bench/fit/contact.mjs +0 -419
  477. package/tools/bench/fit/footlock.mjs +0 -234
  478. package/tools/bench/fit/motion.mjs +0 -85
  479. package/tools/bench/fit/pin.mjs +0 -27
  480. package/tools/bench/fit/remote.mjs +0 -105
  481. package/tools/bench/fit-bench.mjs +0 -126
  482. package/tools/bench/fit-sanity.mjs +0 -45
  483. package/tools/bench/metrics.mjs +0 -313
  484. package/tools/bench/obs/depth.mjs +0 -258
  485. package/tools/bench/obs/extrinsics.mjs +0 -175
  486. package/tools/bench/obs/fit_mannequin_betas.py +0 -340
  487. package/tools/bench/obs/ground.mjs +0 -294
  488. package/tools/bench/obs/heading.mjs +0 -151
  489. package/tools/bench/obs/ladder.mjs +0 -555
  490. package/tools/bench/obs/mannequin-betas.json +0 -124
  491. package/tools/bench/obs/remote.mjs +0 -83
  492. package/tools/bench/obs/rest_joints.py +0 -52
  493. package/tools/bench/obs/ybot-targets.mjs +0 -49
  494. package/tools/bench/obs-bench.mjs +0 -520
  495. package/tools/bench/score.mjs +0 -343
  496. package/tools/bench/summarize.mjs +0 -86
  497. package/tools/demo-worker/README.md +0 -137
  498. package/tools/demo-worker/api-client.mjs +0 -233
  499. package/tools/demo-worker/generate.mjs +0 -301
  500. package/tools/demo-worker/index.mjs +0 -466
  501. package/tools/demo-worker/sign-core.mjs +0 -112
  502. package/tools/demo-worker/sign.mjs +0 -161
  503. package/tools/gt-render/browser.mjs +0 -159
  504. package/tools/gt-render/camera-math.mjs +0 -276
  505. package/tools/gt-render/page.mjs +0 -267
  506. package/tools/gt-render/render.mjs +0 -514
  507. package/tools/gt-render/scene-box.mjs +0 -31
  508. package/tools/gt-render/take-transform.mjs +0 -87
  509. package/tools/kimodo/compare-seam.mjs +0 -108
  510. package/tools/kimodo/constraints.mjs +0 -244
  511. package/tools/kimodo/edit.mjs +0 -259
  512. package/tools/kimodo/effector-constraints.mjs +0 -200
  513. package/tools/kimodo/generate.mjs +0 -646
  514. package/tools/kimodo/local-output.mjs +0 -265
  515. package/tools/kimodo/measure-pose.mjs +0 -65
  516. package/tools/kimodo/measure-preserve.mjs +0 -899
  517. package/tools/kimodo/measure-waypoints.mjs +0 -55
  518. package/tools/kimodo/mlx-generate.py +0 -111
  519. package/tools/kimodo/pose-constraints.mjs +0 -248
  520. package/tools/kimodo/preserve-mask.mjs +0 -543
  521. package/tools/kimodo/read-npz.mjs +0 -189
  522. package/tools/kimodo/run-edit-on-box.mjs +0 -155
  523. package/tools/kimodo/run-sequence-on-box.mjs +0 -178
  524. package/tools/kimodo/runner.mjs +0 -284
  525. package/tools/kimodo/seam-batch.mjs +0 -77
  526. package/tools/kimodo/setup-local.mjs +0 -30
  527. package/tools/kimodo/setup-on-box.sh +0 -339
  528. package/tools/kimodo/soma77-to-cskel27.mjs +0 -305
  529. package/tools/morphgs/assets/gen_truth.py +0 -39
  530. package/tools/morphgs/assets/mesh_ori_rig.txt +0 -27
  531. package/tools/morphgs/assets/playback-check.mjs +0 -30
  532. package/tools/morphgs/demo-gate.sh +0 -26
  533. package/tools/morphgs/fbx2morphgs.mjs +0 -68
  534. package/tools/morphgs/morphgs-to-cskel27.mjs +0 -105
  535. package/tools/morphgs/patches/preprocess_src-none-mode.patch +0 -76
  536. package/tools/morphgs/setup-on-cluster.sh +0 -126
  537. package/tools/projflow/driver.py +0 -1069
  538. package/tools/projflow/generate.mjs +0 -805
  539. package/tools/projflow/hml22-to-cskel27.mjs +0 -735
  540. package/tools/projflow/line-edit-job.mjs +0 -547
  541. package/tools/projflow/replay.mjs +0 -452
  542. package/tools/projflow/runner.mjs +0 -269
  543. package/tools/projflow/service.mjs +0 -742
  544. package/tools/track/DESIGN.md +0 -513
  545. package/tools/track/backfill-provenance.mjs +0 -116
  546. package/tools/track/budget.mjs +0 -8
  547. package/tools/track/check-rig.mjs +0 -209
  548. package/tools/track/diagnostics.schema.json +0 -60
  549. package/tools/track/export-rig.mjs +0 -256
  550. package/tools/track/fallback.mjs +0 -7
  551. package/tools/track/fk-parity-fixture.mjs +0 -161
  552. package/tools/track/gate.mjs +0 -349
  553. package/tools/track/masks.mjs +0 -318
  554. package/tools/track/metrics.mjs +0 -201
  555. package/tools/track/publish-obs.mjs +0 -108
  556. package/tools/track/py/check_env.py +0 -61
  557. package/tools/track/py/eval_lr.py +0 -229
  558. package/tools/track/py/lr_viterbi.py +0 -294
  559. package/tools/track/py/masks.py +0 -405
  560. package/tools/track/py/objective.py +0 -382
  561. package/tools/track/py/rig.py +0 -508
  562. package/tools/track/py/scene.py +0 -326
  563. package/tools/track/py/test_joint_indices.py +0 -123
  564. package/tools/track/py/test_lr_viterbi.py +0 -195
  565. package/tools/track/py/test_masks.py +0 -229
  566. package/tools/track/py/test_rig.py +0 -288
  567. package/tools/track/py/test_scene.py +0 -354
  568. package/tools/track/py/test_track.py +0 -328
  569. package/tools/track/py/track.py +0 -411
  570. package/tools/track/remote.mjs +0 -140
  571. package/tools/track/rig-dump.mjs +0 -227
  572. package/tools/track/run-box-tests.mjs +0 -14
  573. package/tools/track/run-box.mjs +0 -168
  574. package/tools/track/setup-box.sh +0 -59
  575. package/tools/track/study-2d.mjs +0 -728
  576. /package/src/{ardy → motion}/collision-blockers.js +0 -0
  577. /package/src/{ardy → motion}/ground.js +0 -0
  578. /package/src/{ardy → motion}/ik-drag.js +0 -0
  579. /package/src/{ardy → motion}/ik-key-json.js +0 -0
  580. /package/src/{ardy → motion}/ik.js +0 -0
  581. /package/src/{ardy → motion}/physics-surface.js +0 -0
  582. /package/src/{ardy → motion}/playback-clock.js +0 -0
  583. /package/src/{ardy → motion}/range-pin.js +0 -0
@@ -1,108 +0,0 @@
1
- /**
2
- * compare-seam.mjs — measure what a multi-segment take does AT THE SEAM,
3
- * comparably across backends that run at different frame rates.
4
- *
5
- * ARDY-Core runs at 20 fps and Kimodo-SOMA at 30, so a per-FRAME root
6
- * displacement means different things in each file and cannot be compared
7
- * directly — every figure here is per SECOND.
8
- *
9
- * Two different failures are reported because they are not the same thing:
10
- * - a STALL: root speed collapsing toward zero at the boundary, which is the
11
- * "the motion stops between the two prompts" symptom.
12
- * - a JUMP: a one-frame teleport, which is a discontinuity rather than a stall.
13
- */
14
-
15
- import { readNpz } from "./read-npz.mjs";
16
-
17
- /** Root speed per frame, in m/s. */
18
- export function rootSpeeds(rootPos, frames, fps) {
19
- const speeds = [];
20
- for (let frame = 1; frame < frames; frame += 1) {
21
- const a = frame * 3;
22
- const b = (frame - 1) * 3;
23
- speeds.push(
24
- Math.hypot(rootPos[a] - rootPos[b], rootPos[a + 1] - rootPos[b + 1], rootPos[a + 2] - rootPos[b + 2]) * fps
25
- );
26
- }
27
- return speeds;
28
- }
29
-
30
- /**
31
- * @param {Float32Array} rootPos flat [frames*3]
32
- * @param {number} boundaryFrame first frame of the second segment
33
- * @param {number} windowS how far either side of the seam to look
34
- */
35
- export function seamReport(rootPos, frames, fps, boundaryFrame, windowS = 0.25) {
36
- const speeds = rootSpeeds(rootPos, frames, fps);
37
- const half = Math.max(1, Math.round(windowS * fps));
38
- // speeds[i] is the step from frame i to i+1.
39
- const seamIndex = boundaryFrame - 1;
40
- const from = Math.max(0, seamIndex - half);
41
- const to = Math.min(speeds.length - 1, seamIndex + half);
42
-
43
- const window = speeds.slice(from, to + 1);
44
- const outsideBefore = speeds.slice(Math.max(0, from - half), from);
45
- const outsideAfter = speeds.slice(to + 1, Math.min(speeds.length, to + 1 + half));
46
- const surrounding = [...outsideBefore, ...outsideAfter];
47
-
48
- const mean = (xs) => (xs.length ? xs.reduce((a, b) => a + b, 0) / xs.length : 0);
49
- const cruise = mean(surrounding);
50
- const seamMin = window.length ? Math.min(...window) : 0;
51
- const seamMax = window.length ? Math.max(...window) : 0;
52
-
53
- return {
54
- fps,
55
- frames,
56
- boundaryFrame,
57
- seam_speed_at_boundary_mps: speeds[seamIndex] ?? null,
58
- seam_min_speed_mps: seamMin,
59
- seam_max_speed_mps: seamMax,
60
- cruise_speed_mps: cruise,
61
- // 1.0 = the seam keeps the surrounding pace; 0.0 = a dead stop.
62
- stall_ratio: cruise > 0 ? seamMin / cruise : null,
63
- overall_max_speed_mps: Math.max(...speeds),
64
- overall_mean_speed_mps: mean(speeds),
65
- };
66
- }
67
-
68
- function loadRoot(path) {
69
- const members = readNpz(path);
70
- const root = members.root_positions ?? members.smooth_root_pos;
71
- if (!root) throw new Error(`${path}: no root_positions member`);
72
- const frames = root.shape[0];
73
- return { rootPos: root.data, frames };
74
- }
75
-
76
- // Only act as a CLI when executed directly; seam-batch.mjs imports seamReport.
77
- const invokedDirectly = process.argv[1] && process.argv[1].endsWith("compare-seam.mjs");
78
- const [, , ...args] = process.argv;
79
- if (!invokedDirectly) {
80
- // imported as a library
81
- } else if (args.length % 3 !== 0 || args.length === 0) {
82
- console.error('usage: compare-seam.mjs <label> <npz> <fps> [<label> <npz> <fps> ...]');
83
- process.exit(2);
84
- }
85
-
86
- if (invokedDirectly) {
87
- const rows = [];
88
- for (let index = 0; index < args.length; index += 3) {
89
- const label = args[index];
90
- const path = args[index + 1];
91
- const fps = Number(args[index + 2]);
92
- const { rootPos, frames } = loadRoot(path);
93
- // Both takes here are two equal-length segments, so the seam is the middle.
94
- const report = seamReport(rootPos, frames, fps, Math.round(frames / 2));
95
- rows.push({ label, ...report });
96
- }
97
-
98
- for (const row of rows) {
99
- console.log(`\n=== ${row.label} ===`);
100
- console.log(` ${row.frames} frames @ ${row.fps} fps (seam at frame ${row.boundaryFrame})`);
101
- console.log(` cruise speed around seam : ${row.cruise_speed_mps.toFixed(3)} m/s`);
102
- console.log(` slowest speed at seam : ${row.seam_min_speed_mps.toFixed(3)} m/s`);
103
- console.log(` STALL RATIO : ${row.stall_ratio.toFixed(3)} (1.0 = no stall, 0.0 = dead stop)`);
104
- console.log(` fastest speed at seam : ${row.seam_max_speed_mps.toFixed(3)} m/s`);
105
- console.log(` overall max / mean : ${row.overall_max_speed_mps.toFixed(3)} / ${row.overall_mean_speed_mps.toFixed(3)} m/s`);
106
- }
107
- console.log("");
108
- }
@@ -1,244 +0,0 @@
1
- /**
2
- * constraints.mjs — CozyClay's authored root path → Kimodo's `--constraints` JSON.
3
- *
4
- * Two coordinate translations happen here, and both are silent-bug shaped, so
5
- * each is pinned by test/verify-kimodo-waypoints.mjs:
6
- *
7
- * 1. FRAME SPACE. CozyClay authors waypoints against the app's clip, which runs
8
- * at ARDY's 20 fps (src/App.jsx ARDY_FPS). Kimodo generates at 30 and the
9
- * take is retimed back down afterwards, so a constraint frame must be scaled
10
- * onto the GENERATION clock or the path lands at 2/3 of the intended time.
11
- *
12
- * 2. ORIGIN. Kimodo canonicalises training motion so the smoothed root starts at
13
- * XZ (0,0) on frame 0, and the docs require constraints to be authored
14
- * against that canonical origin. CozyClay waypoints are absolute world
15
- * metres, so the whole path is translated by its own first waypoint. Feeding
16
- * absolute coordinates straight in would ask the character to teleport to its
17
- * world position on frame 0.
18
- *
19
- * 3. SEGMENT SPACE. Kimodo generates each prompt segment separately. For every
20
- * segment after the first it shifts that segment's constraints to the origin
21
- * (translate_2d(observed_motion, -last_smooth_root_2d)), generates, then adds
22
- * last_smooth_root_2d back — where last_smooth_root_2d is the segment's
23
- * ACTUAL GENERATED start. `crop_move` only re-indexes frames; it never moves
24
- * smooth_root_2d. So a constraint in segment N must be a DISPLACEMENT FROM
25
- * THAT SEGMENT'S START, and an absolute whole-clip target lands there as a
26
- * doubled offset that yanks the root at the boundary.
27
- *
28
- * This was isolated by measurement on a real 2-segment take, not inferred:
29
- * no waypoints -> boundary jump 0.105 m
30
- * waypoints inside segment 1 -> boundary jump 0.084 m
31
- * absolute waypoints in seg 2 -> boundary jump 2.353 m
32
- * A first attempt that re-anchored each segment onto its own first waypoint
33
- * was measured WORSE still (4.48 m, waypoint error 3.55 m) because it
34
- * discarded the displacement the author actually asked for.
35
- *
36
- * `global_root_heading` is a [cos, sin] direction PAIR per constrained frame,
37
- * NOT a radian — passing radians through would be accepted as a number pair and
38
- * silently steer the character wrong, so heading is converted, never forwarded.
39
- */
40
-
41
- export const ROOT2D_TYPE = "root2d";
42
-
43
- function requireFinite(value, label) {
44
- if (typeof value !== "number" || !Number.isFinite(value)) {
45
- throw new Error(`buildRoot2dConstraints: ${label} must be a finite number, got ${JSON.stringify(value)}`);
46
- }
47
- return value;
48
- }
49
-
50
- /**
51
- * @param {Array<{frame:number,x:number,z:number,heading:number|null}>} waypoints
52
- * @param {{appFps:number, genFps:number, genFrames:number, segmentBoundaries?:number[],
53
- * segmentStarts?:number[], transitionFrames?:number}} options
54
- * `segmentBoundaries` are APP-space frames where a new prompt segment begins
55
- * (excluding 0). Omit or pass [] for a single-segment take.
56
- * `segmentStarts` are the same boundaries already in GENERATION space, used
57
- * to keep constraints clear of each segment's transition window.
58
- * @returns {Array<object>} Kimodo constraint entries (empty when nothing is constrained)
59
- */
60
- export function buildRoot2dConstraints(
61
- waypoints,
62
- { appFps, genFps, genFrames, segmentBoundaries = [], segmentStarts = [], transitionFrames = 5, densifyStride = 0 } = {}
63
- ) {
64
- if (!Number.isInteger(densifyStride) || densifyStride < 0) {
65
- throw new Error(`buildRoot2dConstraints: densifyStride must be a non-negative integer, got ${JSON.stringify(densifyStride)}`);
66
- }
67
- if (!waypoints || waypoints.length === 0) return [];
68
- if (!Array.isArray(waypoints)) {
69
- throw new Error("buildRoot2dConstraints: waypoints must be an array");
70
- }
71
- requireFinite(appFps, "appFps");
72
- requireFinite(genFps, "genFps");
73
- requireFinite(genFrames, "genFrames");
74
- if (genFrames < 1) throw new Error(`buildRoot2dConstraints: genFrames must be >= 1, got ${genFrames}`);
75
-
76
- const scale = genFps / appFps;
77
- const lastFrame = Math.round(genFrames) - 1;
78
-
79
- // Validate and normalise in author order first, so a bad path is refused
80
- // before any of it is translated.
81
- let previousFrame = -1;
82
- const normalised = waypoints.map((waypoint, index) => {
83
- if (!waypoint || typeof waypoint !== "object") {
84
- throw new Error(`buildRoot2dConstraints: waypoints[${index}] must be an object`);
85
- }
86
- const frame = waypoint.frame;
87
- if (!Number.isInteger(frame) || frame < 0) {
88
- throw new Error(
89
- `buildRoot2dConstraints: waypoints[${index}].frame must be a non-negative integer, got ${JSON.stringify(frame)}`
90
- );
91
- }
92
- if (frame <= previousFrame) {
93
- throw new Error(
94
- `buildRoot2dConstraints: waypoint frames must be strictly ascending; waypoints[${index}].frame ${frame} follows ${previousFrame}`
95
- );
96
- }
97
- previousFrame = frame;
98
- const x = requireFinite(waypoint.x, `waypoints[${index}].x`);
99
- const z = requireFinite(waypoint.z, `waypoints[${index}].z`);
100
- const heading = waypoint.heading;
101
- if (heading !== null && heading !== undefined) {
102
- requireFinite(heading, `waypoints[${index}].heading`);
103
- }
104
- // A frame past the generated clip is CLAMPED rather than dropped: the
105
- // author asked for the path to end there, and Kimodo rejects an index
106
- // outside the clip, so the honest reading is "as late as possible".
107
- const genFrame = Math.min(lastFrame, Math.max(0, Math.round(frame * scale)));
108
- return { genFrame, x, z, heading: heading ?? null };
109
- });
110
-
111
- // Two authored frames can round onto one generation frame (a downscale, or
112
- // waypoints one app-frame apart). Duplicate frame_indices would constrain a
113
- // single frame to two different targets, so the LAST author wins — matching
114
- // "the most recent instruction for that moment".
115
- const byFrame = new Map();
116
- for (const entry of normalised) byFrame.set(entry.genFrame, entry);
117
- const points = [...byFrame.values()].sort((a, b) => a.genFrame - b.genFrame);
118
-
119
- // The AUTHORED polyline, frozen before any nudge or densification mutates
120
- // `points`: every later interpolation samples what the author drew, never a
121
- // constraint that has already been moved for Kimodo's benefit.
122
- const authored = points.map((point) => ({ ...point }));
123
-
124
- // Where does the authored path sit when each segment BEGINS? That position is
125
- // the origin Kimodo will re-zero this segment to, so every constraint inside
126
- // the segment is emitted as a displacement from it. The path is sampled
127
- // (linearly, matching a straight walk between pins) rather than snapped to a
128
- // waypoint, because a segment need not start on one.
129
- const genBoundaries = [...new Set(segmentBoundaries.map((frame) => Math.round(frame * scale)))].sort(
130
- (a, b) => a - b
131
- );
132
- const sampleAt = (genFrame) => {
133
- if (genFrame <= authored[0].genFrame) return authored[0];
134
- const last = authored[authored.length - 1];
135
- if (genFrame >= last.genFrame) return last;
136
- for (let index = 1; index < authored.length; index += 1) {
137
- const a = authored[index - 1];
138
- const b = authored[index];
139
- if (genFrame <= b.genFrame) {
140
- const span = b.genFrame - a.genFrame;
141
- const t = span === 0 ? 0 : (genFrame - a.genFrame) / span;
142
- return { x: a.x + (b.x - a.x) * t, z: a.z + (b.z - a.z) * t };
143
- }
144
- }
145
- return last;
146
- };
147
- const segmentStartOf = (genFrame) => {
148
- let start = 0;
149
- for (const boundary of genBoundaries) if (genFrame >= boundary) start = boundary;
150
- return start;
151
- };
152
- const originCache = new Map();
153
- const originFor = (genFrame) => {
154
- const start = segmentStartOf(genFrame);
155
- if (!originCache.has(start)) originCache.set(start, start === 0 ? points[0] : sampleAt(start));
156
- return originCache.get(start);
157
- };
158
-
159
- // Keep constraints out of each segment's TRANSITION WINDOW. For every segment
160
- // after the first, Kimodo builds its own FullBody + EndEffector constraints
161
- // over that segment's first `num_transition_frames` from the previous
162
- // segment's tail. A root2d constraint in the same window disagrees with them
163
- // and the root teleports at the seam: measured 2.353 m with the constraint on
164
- // the boundary against 0.400 m ten frames later, same path and seed.
165
- //
166
- // Only the TIMING moves; the authored position is preserved exactly, because
167
- // dropping the constraint would silently discard what the author asked for.
168
- if (segmentStarts.length > 0 && points.length > 0) {
169
- const windows = [...new Set(segmentStarts.map((frame) => Math.round(frame)))].sort((a, b) => a - b);
170
- for (const point of points) {
171
- for (const start of windows) {
172
- if (point.genFrame >= start && point.genFrame < start + transitionFrames) {
173
- point.genFrame = Math.min(lastFrame, start + transitionFrames);
174
- }
175
- }
176
- }
177
- // A nudge can land on a frame another waypoint already owns, and duplicate
178
- // indices would constrain one frame twice; the later author wins, matching
179
- // the dedupe above.
180
- const deduped = new Map();
181
- for (const point of points) deduped.set(point.genFrame, point);
182
- points.length = 0;
183
- points.push(...[...deduped.values()].sort((a, b) => a.genFrame - b.genFrame));
184
- }
185
-
186
- // DENSIFICATION — the seam fix. Kimodo generates each prompt segment
187
- // separately and crops constraints with a half-open [start, end): a waypoint
188
- // ON a boundary belongs entirely to the segment that starts there, so the
189
- // segment BEFORE it never learns the path exists, ends wherever it likes, and
190
- // the root teleports at the seam (measured 2.353 m) to satisfy the constraint
191
- // one frame later. Laying interpolated samples of the authored polyline every
192
- // `densifyStride` generation frames tells every segment where the path goes,
193
- // so each one arrives at its boundary already in place. Samples stay strictly
194
- // inside [first, last] authored frames (constraining past the path's end
195
- // would invent motion) and out of every transition window (Kimodo owns those
196
- // frames with its own constraints; see the nudge above).
197
- if (densifyStride > 0 && points.length >= 2) {
198
- const windows = [...new Set(segmentStarts.map((frame) => Math.round(frame)))];
199
- const inWindow = (frame) => windows.some((start) => frame >= start && frame < start + transitionFrames);
200
- const taken = new Set(points.map((point) => point.genFrame));
201
- const first = authored[0].genFrame;
202
- const last = authored[authored.length - 1].genFrame;
203
- for (let frame = first + densifyStride; frame < last; frame += densifyStride) {
204
- if (taken.has(frame) || inWindow(frame)) continue;
205
- const { x, z } = sampleAt(frame);
206
- points.push({ genFrame: frame, x, z, heading: null });
207
- taken.add(frame);
208
- }
209
- points.sort((a, b) => a.genFrame - b.genFrame);
210
- }
211
-
212
- const anyHeading = points.some((point) => point.heading !== null);
213
-
214
- // A lone waypoint at the origin carries no displacement and no heading, so
215
- // it constrains nothing; emitting it would hand Kimodo a degenerate entry.
216
- if (points.length === 1 && !anyHeading) return [];
217
-
218
- const entry = {
219
- type: ROOT2D_TYPE,
220
- frame_indices: points.map((point) => point.genFrame),
221
- smooth_root_2d: points.map((point) => {
222
- const origin = originFor(point.genFrame);
223
- return [point.x - origin.x, point.z - origin.z];
224
- }),
225
- };
226
-
227
- if (anyHeading) {
228
- // Every constrained frame needs a pair so the array stays index-aligned
229
- // with frame_indices. An unauthored heading borrows the next authored
230
- // one (falling back to the previous), which keeps the character facing
231
- // along the path it is about to follow rather than snapping at the
232
- // first authored pin.
233
- const resolved = points.map((point) => point.heading);
234
- for (let index = resolved.length - 1; index >= 0; index -= 1) {
235
- if (resolved[index] === null) resolved[index] = resolved[index + 1] ?? null;
236
- }
237
- for (let index = 0; index < resolved.length; index += 1) {
238
- if (resolved[index] === null) resolved[index] = resolved[index - 1] ?? 0;
239
- }
240
- entry.global_root_heading = resolved.map((radians) => [Math.cos(radians), Math.sin(radians)]);
241
- }
242
-
243
- return [entry];
244
- }
@@ -1,259 +0,0 @@
1
- /**
2
- * edit.mjs — regenerate one span of an existing take with Kimodo.
3
- *
4
- * ARDY does this by feeding the surrounding motion in as autoregressive
5
- * history. Kimodo has no history input, so the same intent is expressed with the
6
- * constraint protocol it does have: the span is regenerated as part of a whole
7
- * clip that is PINNED to the original take on both sides of the edit, and only
8
- * the edited span is kept.
9
- *
10
- * frames < start anchored to the source -> the take rejoins what came before
11
- * frames in [start,end) free, except the author's own IK keyframes
12
- * frames >= end anchored to the source -> the take rejoins what comes after
13
- *
14
- * Anchors are ordinary `fullbody` constraints built by pose-constraints.mjs, the
15
- * same ones pose pinning uses and which measured 0.00 deg tracking, so the joins
16
- * are as tight as a pinned pose rather than a blend.
17
- *
18
- * The result is spliced back with replaceMotionSegment: everything outside the
19
- * edited range is copied from the source byte for byte, because the author
20
- * changed one span and expects the rest of their take untouched.
21
- *
22
- * ROUND 2 (contract C5) — THE AUTHOR'S KEYS MAY BE EFFECTOR-SCOPED
23
- * ---------------------------------------------------------------
24
- * A `fullbody` key freezes all 77 joints at that frame. For an ANCHOR that is the
25
- * point: the frame is context we want held exactly. For the author's own key it is
26
- * the bug C5 exists to remove — dragging one hand should move the arm, not weld
27
- * the legs, the spine and the head to a pose the user never authored.
28
- *
29
- * So planEditConstraints splits the two kinds of frame:
30
- *
31
- * anchors -> ALWAYS fullbody, in every mode
32
- * author keyframes -> end-effector constraints IFF every edited track in the
33
- * manifest maps to a limb chain, else fullbody
34
- *
35
- * "EVERY track" is the condition, not "any": the manifest's tracks describe ONE
36
- * authored pose per frame, and an edit that also moved the spine or the head has
37
- * no effector to carry those bones — emitting hand constraints for it would
38
- * silently drop half the user's edit. Mixed or non-effector track lists therefore
39
- * keep round 1's behaviour wholesale, and the chosen mode is REPORTED
40
- * (`constraintMode`) rather than inferred, so the wrapper can log which pinning a
41
- * take actually got.
42
- *
43
- * A CAVEAT WORTH REPEATING AT THE SWITCH (effector-constraints.mjs states it in
44
- * full): an EE constraint still pins root XZ, root height and heading at its
45
- * frames — Kimodo's EndEffectorConstraintSet appends them unconditionally and has
46
- * no flag to disable it. On an edit that is harmless by construction: every pose
47
- * here is either read back out of the source take or authored on top of it, so
48
- * the pinned root is the take's own root at that frame. It is NOT a free root.
49
- */
50
-
51
- import { readFileSync } from "node:fs";
52
- import { dirname, isAbsolute, resolve } from "node:path";
53
- import { buildEffectorConstraints, effectorTypeForTrack } from "./effector-constraints.mjs";
54
- import { buildFullBodyConstraints } from "./pose-constraints.mjs";
55
- import { readNpz } from "./read-npz.mjs";
56
-
57
- /** How many source frames to pin on each side when the caller asks for context. */
58
- const ANCHOR_SAMPLES = 3;
59
-
60
- /**
61
- * Read the bridge's edit-manifest.json into a resolved plan.
62
- * Paths inside it are relative to the manifest, which is what the bridge writes.
63
- */
64
- export function parseEditManifest(manifestPath) {
65
- const raw = JSON.parse(readFileSync(manifestPath, "utf8"));
66
- const base = dirname(resolve(manifestPath));
67
-
68
- if (!Number.isInteger(raw.start_frame) || raw.start_frame < 0) {
69
- throw new Error(`parseEditManifest: start_frame must be a non-negative integer, got ${JSON.stringify(raw.start_frame)}`);
70
- }
71
- if (!Number.isInteger(raw.end_frame)) {
72
- throw new Error(`parseEditManifest: end_frame must be an integer, got ${JSON.stringify(raw.end_frame)}`);
73
- }
74
- if (raw.end_frame <= raw.start_frame) {
75
- throw new Error(`parseEditManifest: range ${raw.start_frame}..${raw.end_frame} is empty or inverted`);
76
- }
77
- if (!Array.isArray(raw.edits) || raw.edits.length === 0) {
78
- throw new Error("parseEditManifest: edits must be a non-empty array");
79
- }
80
-
81
- let previous = -1;
82
- const edits = raw.edits.map((entry, index) => {
83
- if (!entry || typeof entry !== "object") {
84
- throw new Error(`parseEditManifest: edits[${index}] must be an object`);
85
- }
86
- if (!Number.isInteger(entry.frame)) {
87
- throw new Error(`parseEditManifest: edits[${index}].frame must be an integer`);
88
- }
89
- if (entry.frame < raw.start_frame || entry.frame >= raw.end_frame) {
90
- throw new Error(
91
- `parseEditManifest: edits[${index}].frame ${entry.frame} is outside the edited range ${raw.start_frame}..${raw.end_frame}`
92
- );
93
- }
94
- if (entry.frame <= previous) {
95
- throw new Error(
96
- `parseEditManifest: edit frames must be strictly ascending; edits[${index}].frame ${entry.frame} follows ${previous}`
97
- );
98
- }
99
- previous = entry.frame;
100
- if (typeof entry.pose_path !== "string" || !entry.pose_path) {
101
- throw new Error(`parseEditManifest: edits[${index}].pose_path is required`);
102
- }
103
- return {
104
- frame: entry.frame,
105
- tracks: Array.isArray(entry.tracks) ? entry.tracks : [],
106
- posePath: isAbsolute(entry.pose_path) ? entry.pose_path : resolve(base, entry.pose_path),
107
- };
108
- });
109
-
110
- return { startFrame: raw.start_frame, endFrame: raw.end_frame, edits };
111
- }
112
-
113
- /** One frame of a cclay motion npz as the {local_rot_mats, posed_joints} a pose wants. */
114
- function frameAsPose(members, frame) {
115
- const rot = members.local_rot_mats;
116
- const pos = members.posed_joints;
117
- const joints = rot.shape.at(-3);
118
- if (joints !== 27) throw new Error(`planEditConstraints: source motion has ${joints} joints, expected 27`);
119
- const local_rot_mats = [];
120
- const posed_joints = [];
121
- for (let j = 0; j < 27; j += 1) {
122
- const b = (frame * 27 + j) * 9;
123
- local_rot_mats.push([
124
- [rot.data[b], rot.data[b + 1], rot.data[b + 2]],
125
- [rot.data[b + 3], rot.data[b + 4], rot.data[b + 5]],
126
- [rot.data[b + 6], rot.data[b + 7], rot.data[b + 8]],
127
- ]);
128
- const p = (frame * 27 + j) * 3;
129
- posed_joints.push([pos.data[p], pos.data[p + 1], pos.data[p + 2]]);
130
- }
131
- return { local_rot_mats, posed_joints };
132
- }
133
-
134
- /** A cclay pose npz (single keyframe) as the same shape. */
135
- function poseFromNpz(path) {
136
- const members = readNpz(path);
137
- for (const key of ["local_rot_mats", "posed_joints"]) {
138
- if (!members[key]) throw new Error(`planEditConstraints: pose npz ${path} is missing ${key}`);
139
- }
140
- return frameAsPose(members, 0);
141
- }
142
-
143
- /**
144
- * Build the constraint set for an edit: the author's keyframes inside the range,
145
- * plus source anchors on both sides so the regenerated span rejoins the take.
146
- *
147
- * `poses` are the same pins in APP frame space, which is what generateOnBox
148
- * takes; `constraints` is the already-scaled Kimodo form, used by the tests and
149
- * for reporting how many frames ended up pinned.
150
- *
151
- * @returns {{constraints:Array<object>, poses:Array<object>, startFrame:number, endFrame:number, sourceFrames:number, fps:number}}
152
- */
153
- export function planEditConstraints({ sourcePath, manifestPath, contextBefore = 0, contextAfter = 0, genFps = 30, appFps = 24 }) {
154
- const manifest = parseEditManifest(manifestPath);
155
- const source = readNpz(sourcePath);
156
- if (!source.local_rot_mats || !source.posed_joints) {
157
- throw new Error(`planEditConstraints: ${sourcePath} is not a cclay motion npz`);
158
- }
159
- const sourceFrames = source.local_rot_mats.shape[0];
160
- const fps = source.fps ? Math.round(source.fps.data[0]) : appFps;
161
- if (manifest.endFrame > sourceFrames) {
162
- throw new Error(
163
- `planEditConstraints: edited range ends at ${manifest.endFrame} but the source has ${sourceFrames} frames`
164
- );
165
- }
166
-
167
- // Anchor frames: the last few source frames before the range and the first
168
- // few at/after it. Sampling several rather than one gives the model a
169
- // direction to rejoin on, not just a point.
170
- const anchors = [];
171
- if (contextBefore > 0) {
172
- const from = Math.max(0, manifest.startFrame - contextBefore);
173
- const step = Math.max(1, Math.floor((manifest.startFrame - from) / ANCHOR_SAMPLES) || 1);
174
- for (let f = from; f < manifest.startFrame; f += step) anchors.push(f);
175
- }
176
- if (contextAfter > 0) {
177
- const to = Math.min(sourceFrames - 1, manifest.endFrame + contextAfter - 1);
178
- const step = Math.max(1, Math.floor((to - manifest.endFrame) / ANCHOR_SAMPLES) || 1);
179
- for (let f = manifest.endFrame; f <= to; f += step) anchors.push(f);
180
- }
181
-
182
- // Author keyframes win over an anchor on the same frame: the whole point of
183
- // the edit is that those frames changed. `kind` rides along because round 2
184
- // constrains the two differently — and this map is what GUARANTEES a frame can
185
- // never carry both an anchor and an author key, i.e. never both a fullbody
186
- // anchor and an EE edit.
187
- const byFrame = new Map();
188
- for (const frame of anchors) {
189
- if (frame < 0 || frame >= sourceFrames) continue;
190
- byFrame.set(frame, { frame, kind: "anchor", pose: frameAsPose(source, frame) });
191
- }
192
- for (const edit of manifest.edits) {
193
- byFrame.set(edit.frame, { frame: edit.frame, kind: "edit", pose: poseFromNpz(edit.posePath) });
194
- }
195
- const poses = [...byFrame.values()].sort((a, b) => a.frame - b.frame);
196
-
197
- // C5's switch, decided from the MANIFEST rather than from the surviving poses:
198
- // an author key that lost its slot to app->gen frame rounding still describes
199
- // what the user edited. EVERY edited track must map to a limb chain (see the
200
- // header); an edit that names no tracks at all cannot claim one, so it is
201
- // fullbody.
202
- const editedTracks = [];
203
- let allEffector = manifest.edits.length > 0;
204
- for (const edit of manifest.edits) {
205
- if (edit.tracks.length === 0) allEffector = false;
206
- for (const track of edit.tracks) {
207
- if (effectorTypeForTrack(track) === null) allEffector = false;
208
- if (!editedTracks.includes(track)) editedTracks.push(track);
209
- }
210
- }
211
- const constraintMode = allEffector ? "effector" : "fullbody";
212
-
213
- // The clip Kimodo generates runs at genFps; the manifest speaks in app frames.
214
- // ONE scaling pass over the merged, sorted list, so the gen-frame dedup stays
215
- // SHARED: two source frames that round onto the same generation frame keep the
216
- // earlier one whatever kind it is, and an anchor and an author key can never
217
- // both survive onto one frame in two different constraint entries.
218
- const scale = genFps / appFps;
219
- const genFrames = Math.max(1, Math.round(sourceFrames * scale));
220
- const scaled = [];
221
- const seen = new Set();
222
- for (const entry of poses) {
223
- const genFrame = Math.min(genFrames - 1, Math.max(0, Math.round(entry.frame * scale)));
224
- if (seen.has(genFrame)) continue;
225
- seen.add(genFrame);
226
- scaled.push({ frame: genFrame, kind: entry.kind, pose: entry.pose });
227
- }
228
-
229
- // fullbody mode is round 1 unchanged: ONE entry over every frame, anchors and
230
- // author keys alike. Effector mode splits them — the anchors stay fullbody
231
- // (they are context to hold exactly), the author's keys become one entry per
232
- // distinct effector chain, over the edited frames only.
233
- let constraints;
234
- if (constraintMode === "effector") {
235
- const anchorPoses = scaled.filter((entry) => entry.kind === "anchor");
236
- const editPoses = scaled.filter((entry) => entry.kind === "edit");
237
- constraints = [
238
- ...buildFullBodyConstraints(anchorPoses, { genFrames }),
239
- ...buildEffectorConstraints(editPoses, { genFrames, tracks: editedTracks }),
240
- ];
241
- } else {
242
- constraints = buildFullBodyConstraints(scaled, { genFrames });
243
- }
244
-
245
- return {
246
- constraints,
247
- // Which pinning the AUTHOR's keyframes got ("effector" | "fullbody"), and
248
- // the tracks that decided it. Reported rather than re-derived downstream:
249
- // run-edit-on-box logs it and the tests assert it.
250
- constraintMode,
251
- editedTracks,
252
- poses,
253
- startFrame: manifest.startFrame,
254
- endFrame: manifest.endFrame,
255
- sourceFrames,
256
- fps,
257
- genFrames,
258
- };
259
- }