cozyclay 2.1.1 → 3.0.0

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