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,200 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * mocap-metrics.mjs — compare cskel27 motion npz takes on the three defects a
4
- * bad 2D observation produces, so an estimator change (e.g. #180's palette
5
- * keypoints against ViTPose) is judged by numbers instead of by eye:
6
- *
7
- * foot slide the planted foot's horizontal speed while it is planted.
8
- * A supporting foot should read ~0 cm/s; skate is the single
9
- * most visible mocap artefact (SAM's single-image lift
10
- * measured 250 cm/s on locomotion). "Planted" is decided
11
- * per side from the take itself: the frames whose ankle is
12
- * in the lower PLANT_HEIGHT_PERCENTILE of that side's ankle
13
- * heights AND whose horizontal speed is below the side's
14
- * median — that is the stance phase, without needing the
15
- * contact logits (a converted take has dropped them).
16
- * jitter mean |second difference| of posed_joints. The raw
17
- * mm/frame^2 value is retained for diagnostics and the
18
- * fps-normalised mm/s^2 value is used for cross-rate gates.
19
- * below floor frames whose lowest foot joint sits under Y=0. The
20
- * converter grounds the clip on its 10th percentile, so a
21
- * few frames are expected; many mean the legs are wrong.
22
- *
23
- * usage: node tools/ardy/mocap-metrics.mjs <a.npz> [<b.npz> ...]
24
- */
25
- import { readNpz } from "../kimodo/read-npz.mjs";
26
- import { CSKEL27_JOINTS } from "../../src/ardy/cskel27.js";
27
- import { pathToFileURL } from "node:url";
28
-
29
- const JOINT = Object.fromEntries(CSKEL27_JOINTS.map((name, index) => [name, index]));
30
- const JOINTS = CSKEL27_JOINTS.length;
31
- const FEET = ["RightFoot", "RightToeBase", "LeftFoot", "LeftToeBase"].map((name) => JOINT[name]);
32
- const ANKLES = { right: JOINT.RightFoot, left: JOINT.LeftFoot };
33
- // The stance phase of a step: the foot is both low and slow. Deriving it from
34
- // the take's own distribution keeps the measure comparable across clips with
35
- // different step heights, and needs no contact channel.
36
- const PLANT_HEIGHT_PERCENTILE = 0.35;
37
- const FLOOR_TOLERANCE_M = 0.005; // 5 mm: float32 round-trip, not penetration
38
-
39
- function member(members, name, path) {
40
- const value = members[name];
41
- if (!value?.data) throw new Error(`${path}: missing or unreadable member ${name}`);
42
- return value;
43
- }
44
-
45
- function percentile(values, fraction) {
46
- if (!values.length) return NaN;
47
- const sorted = [...values].sort((a, b) => a - b);
48
- const at = (sorted.length - 1) * fraction;
49
- const low = Math.floor(at);
50
- return sorted[low] * (1 - (at - low)) + (sorted[Math.min(low + 1, sorted.length - 1)] ?? sorted[low]) * (at - low);
51
- }
52
-
53
- const median = (values) => percentile(values, 0.5);
54
-
55
- function read(path) {
56
- const members = readNpz(path);
57
- const joints = member(members, "posed_joints", path);
58
- if (joints.shape.length !== 3 || joints.shape[1] !== JOINTS || joints.shape[2] !== 3) {
59
- throw new Error(`${path}: posed_joints must be (F, ${JOINTS}, 3), got (${joints.shape.join(", ")})`);
60
- }
61
- const fpsMember = member(members, "fps", path);
62
- const fps = fpsMember.data[0];
63
- if (!(fps > 0)) throw new Error(`${path}: invalid fps ${fps}`);
64
- const frames = joints.shape[0];
65
- const root = members.root_positions?.data ?? null;
66
- return { path, frames, fps, joints: joints.data, root };
67
- }
68
-
69
- /** Build metrics from a converted motion already held in memory. */
70
- export function mocapMetricsFromMotion(motion, { path = "<memory>" } = {}) {
71
- if (!motion || !Number.isInteger(motion.frames) || motion.frames < 1) {
72
- throw new Error(`${path}: motion.frames must be a positive integer`);
73
- }
74
- const fps = Number(motion.fps);
75
- if (!(fps > 0)) throw new Error(`${path}: invalid fps ${motion.fps}`);
76
- const joints = motion.posedJoints ?? motion.joints;
77
- const expectedJoints = motion.frames * JOINTS * 3;
78
- if (!joints || joints.length !== expectedJoints) {
79
- throw new Error(`${path}: posedJoints must contain ${expectedJoints} values`);
80
- }
81
- const root = motion.rootPos ?? motion.root ?? null;
82
- if (root !== null && root.length !== motion.frames * 3) {
83
- throw new Error(`${path}: rootPos must contain ${motion.frames * 3} values`);
84
- }
85
- const take = { path, frames: motion.frames, fps, joints, root };
86
- const floor = belowFloor(take);
87
- const jitterMmPerFrame2 = jitter(take);
88
- return {
89
- path,
90
- frames: take.frames,
91
- fps: take.fps,
92
- footSlideCmPerS: footSlide(take),
93
- // Keep the raw frame-space value for backwards-compatible diagnostics,
94
- // but also expose a time-normalised acceleration. A second difference
95
- // scales with dt², so the raw value alone would make the same physical
96
- // shake look 4x smaller when a 30fps take is sampled at 60fps.
97
- jitterMmPerFrame2,
98
- jitterMmPerS2: jitterMmPerFrame2 * take.fps * take.fps,
99
- framesBelowFloor: floor.count,
100
- deepestBelowFloorCm: floor.deepestCm,
101
- rootTravelM: travel(take),
102
- };
103
- }
104
-
105
- /** Median horizontal speed (cm/s) of each ankle over its own stance frames. */
106
- function footSlide({ frames, fps, joints }) {
107
- const speeds = [];
108
- for (const ankle of Object.values(ANKLES)) {
109
- const at = (frame, axis) => joints[(frame * JOINTS + ankle) * 3 + axis];
110
- const step = [];
111
- for (let frame = 1; frame < frames; frame += 1) {
112
- step.push({
113
- speed: Math.hypot(at(frame, 0) - at(frame - 1, 0), at(frame, 2) - at(frame - 1, 2)) * fps * 100,
114
- height: Math.min(at(frame, 1), at(frame - 1, 1)),
115
- });
116
- }
117
- if (!step.length) continue;
118
- const lowEnough = percentile(step.map((s) => s.height), PLANT_HEIGHT_PERCENTILE);
119
- const slowEnough = median(step.map((s) => s.speed));
120
- const planted = step.filter((s) => s.height <= lowEnough && s.speed <= slowEnough);
121
- speeds.push(median((planted.length ? planted : step).map((s) => s.speed)));
122
- }
123
- return median(speeds);
124
- }
125
-
126
- /** Mean |second difference| over every joint, in mm per frame^2. */
127
- function jitter({ frames, joints }) {
128
- if (frames < 3) return NaN;
129
- let total = 0;
130
- let count = 0;
131
- for (let frame = 1; frame < frames - 1; frame += 1) {
132
- for (let joint = 0; joint < JOINTS; joint += 1) {
133
- const base = (frame * JOINTS + joint) * 3;
134
- const before = ((frame - 1) * JOINTS + joint) * 3;
135
- const after = ((frame + 1) * JOINTS + joint) * 3;
136
- total += Math.hypot(
137
- joints[after] - 2 * joints[base] + joints[before],
138
- joints[after + 1] - 2 * joints[base + 1] + joints[before + 1],
139
- joints[after + 2] - 2 * joints[base + 2] + joints[before + 2],
140
- ) * 1000;
141
- count += 1;
142
- }
143
- }
144
- return total / count;
145
- }
146
-
147
- /** Frames whose lowest foot joint is under the floor, and how deep it goes. */
148
- function belowFloor({ frames, joints }) {
149
- let count = 0;
150
- let deepest = 0;
151
- for (let frame = 0; frame < frames; frame += 1) {
152
- let lowest = Infinity;
153
- for (const foot of FEET) lowest = Math.min(lowest, joints[(frame * JOINTS + foot) * 3 + 1]);
154
- if (lowest < -FLOOR_TOLERANCE_M) {
155
- count += 1;
156
- deepest = Math.min(deepest, lowest);
157
- }
158
- }
159
- return { count, deepestCm: -deepest * 100 };
160
- }
161
-
162
- /** Straight-line XZ travel of the root, for context on the slide number. */
163
- function travel({ frames, joints, root }) {
164
- const source = root ?? joints;
165
- const stride = root ? 3 : JOINTS * 3;
166
- const last = (frames - 1) * stride;
167
- return Math.hypot(source[last] - source[0], source[last + 2] - source[2]);
168
- }
169
-
170
- /** Accept an NPZ path (legacy CLI/API) or a converted motion object. */
171
- export function mocapMetrics(input) {
172
- if (typeof input === "string") {
173
- const take = read(input);
174
- return mocapMetricsFromMotion(take, { path: input });
175
- }
176
- return mocapMetricsFromMotion(input);
177
- }
178
-
179
- if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
180
- const paths = process.argv.slice(2);
181
- if (!paths.length) {
182
- console.error("usage: node tools/ardy/mocap-metrics.mjs <a.npz> [<b.npz> ...]");
183
- process.exit(2);
184
- }
185
- const rows = paths.map(mocapMetrics);
186
- const columns = [
187
- ["take", (row) => row.path.replace(/^.*\//, ""), 26],
188
- ["frames", (row) => String(row.frames), 6],
189
- ["fps", (row) => String(row.fps), 3],
190
- ["foot slide cm/s", (row) => row.footSlideCmPerS.toFixed(2), 15],
191
- ["jitter mm/f^2", (row) => row.jitterMmPerFrame2.toFixed(3), 13],
192
- ["jitter mm/s^2", (row) => row.jitterMmPerS2.toFixed(1), 13],
193
- ["below floor", (row) => `${row.framesBelowFloor} (${row.deepestBelowFloorCm.toFixed(1)} cm)`, 16],
194
- ["root travel m", (row) => row.rootTravelM.toFixed(2), 13],
195
- ];
196
- const line = (cells) => cells.map((cell, index) => cell.padEnd(columns[index][2])).join(" ").trimEnd();
197
- console.log(line(columns.map(([title]) => title)));
198
- console.log(line(columns.map(([, , width]) => "-".repeat(width))));
199
- for (const row of rows) console.log(line(columns.map(([, value]) => value(row))));
200
- }
@@ -1,110 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Acceptance gate for a converted CSKEL27 mocap take.
4
- *
5
- * The report in mocap-metrics.mjs is intentionally descriptive. This gate
6
- * turns the values into an explicit, reviewable decision so a visibly shaky
7
- * clip cannot be shipped by accident. Thresholds are defaults for ordinary
8
- * 24–60 fps footage and can be overridden per footage class with flags.
9
- */
10
- import { mocapMetrics } from "./mocap-metrics.mjs";
11
- import { pathToFileURL } from "node:url";
12
-
13
- export const DEFAULT_LIMITS = Object.freeze({
14
- maxFootSlideCmPerS: 3,
15
- maxJitterMmPerFrame2: 6,
16
- // 6 mm/frame² at the 30fps reference rate. The gate prefers the
17
- // time-normalised metric when the extractor provides it, so a 60fps clip
18
- // cannot pass merely because its frame interval is shorter.
19
- maxJitterMmPerS2: 5400,
20
- maxBelowFloorFraction: 0.02,
21
- maxDeepestBelowFloorCm: 1.5,
22
- maxVerticalContactErrorCm: 2,
23
- maxCameraDriftPx: 2,
24
- maxBackgroundDriftP95: 8,
25
- });
26
-
27
- /** Return a structured decision; pure so CI and browser QA can reuse it. */
28
- export function evaluateQuality(metrics, limits = DEFAULT_LIMITS) {
29
- const fps = Number(metrics?.fps);
30
- const jitterIsNormalised = Number.isFinite(metrics?.jitterMmPerS2);
31
- const jitterValue = jitterIsNormalised ? metrics.jitterMmPerS2 : metrics?.jitterMmPerFrame2;
32
- // Preserve callers that override the legacy frame-space limit. Its
33
- // historical meaning was measured at 30fps, so convert it to acceleration
34
- // units when a normalised metric is available.
35
- const frameJitterLimit = Number(limits.maxJitterMmPerFrame2);
36
- const secondJitterLimit = Number(limits.maxJitterMmPerS2);
37
- const jitterLimit = jitterIsNormalised
38
- ? (Number.isFinite(frameJitterLimit) &&
39
- (!Number.isFinite(secondJitterLimit) || secondJitterLimit === DEFAULT_LIMITS.maxJitterMmPerS2)
40
- ? frameJitterLimit * 30 * 30
41
- : secondJitterLimit)
42
- : limits.maxJitterMmPerFrame2;
43
- const checks = [
44
- {
45
- name: "foot-slide",
46
- value: metrics.footSlideCmPerS,
47
- limit: limits.maxFootSlideCmPerS,
48
- pass: metrics.footSlideCmPerS <= limits.maxFootSlideCmPerS,
49
- unit: "cm/s",
50
- },
51
- {
52
- name: "jitter",
53
- value: jitterValue,
54
- limit: jitterLimit,
55
- pass: Number.isFinite(jitterValue) && jitterValue <= jitterLimit,
56
- unit: jitterIsNormalised ? "mm/s²" : "mm/frame²",
57
- ...(jitterIsNormalised ? { rawValue: metrics.jitterMmPerFrame2, fps } : {}),
58
- },
59
- {
60
- name: "below-floor-fraction",
61
- value: metrics.framesBelowFloor / metrics.frames,
62
- limit: limits.maxBelowFloorFraction,
63
- pass: metrics.framesBelowFloor / metrics.frames <= limits.maxBelowFloorFraction,
64
- unit: "fraction",
65
- },
66
- {
67
- name: "deepest-below-floor",
68
- value: metrics.deepestBelowFloorCm,
69
- limit: limits.maxDeepestBelowFloorCm,
70
- pass: metrics.deepestBelowFloorCm <= limits.maxDeepestBelowFloorCm,
71
- unit: "cm",
72
- },
73
- ];
74
- // These checks need scene/video observations that an NPZ alone does not
75
- // contain. Include them whenever the extractor supplies the measurements;
76
- // omission is reported to callers as "unmeasured" rather than guessed.
77
- for (const [name, valueKey, limitKey, unit] of [
78
- ["vertical-contact", "verticalContactErrorCm", "maxVerticalContactErrorCm", "cm"],
79
- ["camera-drift", "cameraDriftPx", "maxCameraDriftPx", "px"],
80
- ["background-drift", "backgroundDriftP95", "maxBackgroundDriftP95", "mean-absolute-RGB"],
81
- ]) {
82
- if (Number.isFinite(metrics[valueKey]) && Number.isFinite(limits[limitKey])) {
83
- checks.push({ name, value: metrics[valueKey], limit: limits[limitKey], pass: metrics[valueKey] <= limits[limitKey], unit });
84
- }
85
- }
86
- return { pass: checks.every((check) => check.pass), checks, metrics };
87
- }
88
-
89
- function usage() {
90
- console.error("usage: node tools/ardy/mocap-quality-gate.mjs <take.npz> [--foot-slide cm/s] [--jitter mm/frame² @30fps] [--jitter-s2 mm/s²] [--below-floor fraction] [--depth cm]");
91
- }
92
-
93
- if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
94
- const [path, ...args] = process.argv.slice(2);
95
- if (!path) { usage(); process.exit(2); }
96
- const limits = { ...DEFAULT_LIMITS };
97
- for (let i = 0; i < args.length; i += 2) {
98
- const value = Number(args[i + 1]);
99
- if (!Number.isFinite(value)) { usage(); process.exit(2); }
100
- if (args[i] === "--foot-slide") limits.maxFootSlideCmPerS = value;
101
- else if (args[i] === "--jitter") limits.maxJitterMmPerFrame2 = value;
102
- else if (args[i] === "--jitter-s2") limits.maxJitterMmPerS2 = value;
103
- else if (args[i] === "--below-floor") limits.maxBelowFloorFraction = value;
104
- else if (args[i] === "--depth") limits.maxDeepestBelowFloorCm = value;
105
- else { usage(); process.exit(2); }
106
- }
107
- const result = evaluateQuality(mocapMetrics(path), limits);
108
- console.log(JSON.stringify(result, null, 2));
109
- if (!result.pass) process.exitCode = 1;
110
- }
@@ -1,348 +0,0 @@
1
- /**
2
- * Small, model-agnostic cleanup pass for GVHMR retargets.
3
- *
4
- * GVHMR is temporally aware, but its per-frame segmentation/keypoint inputs
5
- * can still produce one-frame spikes. This pass removes only high-frequency
6
- * residuals: the correction is centred on the neighbouring frames and its
7
- * strength falls as local speed increases. A fast step therefore remains a
8
- * step while a stationary foot no longer buzzes in place.
9
- */
10
-
11
- const JOINTS = 27;
12
-
13
- const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
14
- const distance3 = (a, b) => Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2]);
15
-
16
- function smoothPositions(input, frames, stride, fps) {
17
- const out = new Float32Array(input);
18
- if (frames < 3) return { values: out, corrected: 0 };
19
- let corrected = 0;
20
- for (let f = 1; f < frames - 1; f += 1) {
21
- for (let item = 0; item < stride; item += 1) {
22
- const o = (f * stride + item) * 3;
23
- const p = (f - 1) * stride * 3 + item * 3;
24
- const n = (f + 1) * stride * 3 + item * 3;
25
- const prev = [input[p], input[p + 1], input[p + 2]];
26
- const curr = [input[o], input[o + 1], input[o + 2]];
27
- const next = [input[n], input[n + 1], input[n + 2]];
28
- const prevVelocity = [curr[0] - prev[0], curr[1] - prev[1], curr[2] - prev[2]];
29
- const nextVelocity = [next[0] - curr[0], next[1] - curr[1], next[2] - curr[2]];
30
- const speed = (distance3(curr, prev) + distance3(next, curr)) * 0.5 * fps;
31
- // At rest blend up to 35% towards the centred neighbour estimate;
32
- // above roughly 1 m/s preserve almost all of the authored motion.
33
- const reversal = prevVelocity[0] * nextVelocity[0] + prevVelocity[1] * nextVelocity[1] + prevVelocity[2] * nextVelocity[2] < 0;
34
- const blend = reversal ? 0.72 : clamp(0.35 - speed * 0.12, 0.04, 0.35);
35
- const correction = [(prev[0] + next[0]) * 0.5 - curr[0],
36
- (prev[1] + next[1]) * 0.5 - curr[1],
37
- (prev[2] + next[2]) * 0.5 - curr[2]];
38
- // Isolated reversals are segmentation outliers (often 10–15 cm on a
39
- // rendered limb), so permit a larger correction only in that case.
40
- const amount = Math.min(reversal ? 0.12 : 0.045, Math.hypot(...correction) * blend);
41
- if (amount < 1e-5) continue;
42
- const scale = amount / Math.max(Math.hypot(...correction), 1e-8);
43
- out[o] = curr[0] + correction[0] * scale;
44
- out[o + 1] = curr[1] + correction[1] * scale;
45
- out[o + 2] = curr[2] + correction[2] * scale;
46
- corrected += 1;
47
- }
48
- }
49
- // A segmentation/keypoint dropout can persist for a few frames. A single
50
- // three-tap pass repairs the edges of that burst but leaves its centre at the
51
- // wrong location. Detect a short burst against a longer chord, using the
52
- // immutable source so a correction cannot spread into clean entry/exit
53
- // frames. The radius is tied to time (about 100 ms), not a fixed frame count,
54
- // so 24, 30 and 60 fps clips behave alike.
55
- const radius = Math.max(3, Math.min(5, Math.round(fps * 0.1)));
56
- if (frames > radius * 2 + 1) {
57
- const pass = new Float32Array(out);
58
- // Hips (joint 0) is the authored root trajectory. A real stair or chair
59
- // ascent can be a short, high-amplitude change, so never run the dropout
60
- // repair over it; only limb observations are eligible.
61
- const firstItem = stride === JOINTS ? 1 : stride;
62
- for (let item = firstItem; item < stride; item += 1) {
63
- // First identify a contiguous run from the raw input. Requiring two
64
- // adjacent outliers rejects a genuine one-frame limb snap, which the
65
- // preceding three-tap pass already handles with a smaller correction.
66
- const candidates = new Uint8Array(frames);
67
- for (let f = radius; f < frames - radius; f += 1) {
68
- const o = (f * stride + item) * 3;
69
- const before = ((f - radius) * stride + item) * 3;
70
- const after = ((f + radius) * stride + item) * 3;
71
- const rawCurrent = [input[o], input[o + 1], input[o + 2]];
72
- const rawEstimate = [(input[before] + input[after]) * 0.5,
73
- (input[before + 1] + input[after + 1]) * 0.5,
74
- (input[before + 2] + input[after + 2]) * 0.5];
75
- // 8 cm is above ordinary retarget noise and catches the short,
76
- // high-amplitude plateaus seen when a detector loses a limb.
77
- if (distance3(rawCurrent, rawEstimate) < 0.08) continue;
78
- const spanSpeed = distance3(
79
- [input[before], input[before + 1], input[before + 2]],
80
- [input[after], input[after + 1], input[after + 2]],
81
- ) * fps / (2 * radius);
82
- // Preserve running/throwing trajectories; the long chord is only a
83
- // dropout signal when the surrounding motion is below 2 m/s.
84
- if (spanSpeed > 2) continue;
85
- candidates[f] = 1;
86
- }
87
- for (let f = radius; f < frames - radius;) {
88
- if (!candidates[f]) { f += 1; continue; }
89
- const start = f;
90
- while (f < frames - radius && candidates[f]) f += 1;
91
- if (f - start < 2) continue;
92
- // A dropout plateau has almost no movement inside its run. Reject a
93
- // run whose own samples are moving quickly; that is a real gesture,
94
- // even when its longer chord happens to curve by several centimetres.
95
- let coherent = true;
96
- for (let g = start + 1; g < f; g += 1) {
97
- const prev = ((g - 1) * stride + item) * 3;
98
- const curr = (g * stride + item) * 3;
99
- if (distance3([input[prev], input[prev + 1], input[prev + 2]], [input[curr], input[curr + 1], input[curr + 2]]) > 0.015) {
100
- coherent = false;
101
- break;
102
- }
103
- }
104
- if (!coherent) continue;
105
- for (let currentFrame = start; currentFrame < f; currentFrame += 1) {
106
- const o = (currentFrame * stride + item) * 3;
107
- const before = ((currentFrame - radius) * stride + item) * 3;
108
- const after = ((currentFrame + radius) * stride + item) * 3;
109
- const estimate = [(pass[before] + pass[after]) * 0.5,
110
- (pass[before + 1] + pass[after + 1]) * 0.5,
111
- (pass[before + 2] + pass[after + 2]) * 0.5];
112
- const current = [pass[o], pass[o + 1], pass[o + 2]];
113
- const residual = distance3(current, estimate);
114
- const amount = Math.min(0.1, residual * 0.9);
115
- if (amount < 1e-5) continue;
116
- const scale = amount / Math.max(residual, 1e-8);
117
- out[o] = current[0] + (estimate[0] - current[0]) * scale;
118
- out[o + 1] = current[1] + (estimate[1] - current[1]) * scale;
119
- out[o + 2] = current[2] + (estimate[2] - current[2]) * scale;
120
- corrected += 1;
121
- }
122
- }
123
- }
124
- }
125
- return { values: out, corrected };
126
- }
127
-
128
- function matrixToQuat(m) {
129
- const trace = m[0] + m[4] + m[8];
130
- let x; let y; let z; let w;
131
- if (trace > 0) {
132
- const s = Math.sqrt(trace + 1) * 2; w = 0.25 * s; x = (m[7] - m[5]) / s; y = (m[2] - m[6]) / s; z = (m[3] - m[1]) / s;
133
- } else if (m[0] > m[4] && m[0] > m[8]) {
134
- const s = Math.sqrt(1 + m[0] - m[4] - m[8]) * 2; w = (m[7] - m[5]) / s; x = 0.25 * s; y = (m[1] + m[3]) / s; z = (m[2] + m[6]) / s;
135
- } else if (m[4] > m[8]) {
136
- const s = Math.sqrt(1 + m[4] - m[0] - m[8]) * 2; w = (m[2] - m[6]) / s; x = (m[1] + m[3]) / s; y = 0.25 * s; z = (m[5] + m[7]) / s;
137
- } else {
138
- const s = Math.sqrt(1 + m[8] - m[0] - m[4]) * 2; w = (m[3] - m[1]) / s; x = (m[2] + m[6]) / s; y = (m[5] + m[7]) / s; z = 0.25 * s;
139
- }
140
- const norm = Math.hypot(x, y, z, w) || 1;
141
- return [x / norm, y / norm, z / norm, w / norm];
142
- }
143
-
144
- function quatToMatrix(q) {
145
- const [x, y, z, w] = q;
146
- return [1 - 2 * (y * y + z * z), 2 * (x * y - z * w), 2 * (x * z + y * w),
147
- 2 * (x * y + z * w), 1 - 2 * (x * x + z * z), 2 * (y * z - x * w),
148
- 2 * (x * z - y * w), 2 * (y * z + x * w), 1 - 2 * (x * x + y * y)];
149
- }
150
-
151
- function slerp(a, b, t) {
152
- let d = a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
153
- if (d < 0) { b = b.map((v) => -v); d = -d; }
154
- if (d > 0.9995) {
155
- const q = a.map((v, i) => v + (b[i] - v) * t); const n = Math.hypot(...q) || 1; return q.map((v) => v / n);
156
- }
157
- const angle = Math.acos(clamp(d, -1, 1));
158
- const sin = Math.sin(angle) || 1;
159
- const wa = Math.sin((1 - t) * angle) / sin; const wb = Math.sin(t * angle) / sin;
160
- return a.map((v, i) => v * wa + b[i] * wb);
161
- }
162
-
163
- function smoothRotations(input, frames, joints, fps) {
164
- const out = new Float32Array(input); if (frames < 3) return { values: out, corrected: 0 };
165
- let corrected = 0;
166
- for (let j = 0; j < joints; j += 1) {
167
- const qs = new Array(frames);
168
- for (let f = 0; f < frames; f += 1) qs[f] = matrixToQuat(input.subarray((f * joints + j) * 9, (f * joints + j + 1) * 9));
169
- for (let f = 1; f < frames - 1; f += 1) {
170
- const prev = qs[f - 1]; const curr = qs[f]; const next = qs[f + 1];
171
- const neighbour = slerp(prev, next, 0.5);
172
- const angular = 2 * Math.acos(clamp(Math.abs(curr[0] * prev[0] + curr[1] * prev[1] + curr[2] * prev[2] + curr[3] * prev[3]), -1, 1)) * fps;
173
- const blend = clamp(0.28 - angular * 0.035, 0.025, 0.28);
174
- const q = slerp(curr, neighbour, blend);
175
- const o = (f * joints + j) * 9; const m = quatToMatrix(q);
176
- for (let k = 0; k < 9; k += 1) out[o + k] = m[k];
177
- if (blend > 0.05) corrected += 1;
178
- }
179
- }
180
- return { values: out, corrected };
181
- }
182
-
183
- /** Return a copy of a converted motion with transient jitter reduced. */
184
- function stabilizeContacts(root, posed, frames, fps, { contactHeight, groundY } = {}) {
185
- const outRoot = new Float32Array(root); const outPosed = new Float32Array(posed);
186
- const feet = [21, 22, 25, 26]; const window = Math.max(2, Math.round(fps * .12));
187
- let corrected = 0; const targetHeight = Number.isFinite(contactHeight) ? contactHeight : (Number.isFinite(groundY) ? groundY : null);
188
- // Without an explicit surface datum we cannot tell floor contact from a
189
- // step, stair, or chair. Leave the source trajectory untouched; smoothing
190
- // above remains safe for every scene.
191
- if (targetHeight === null) return { root: outRoot, posed: outPosed, corrected };
192
- for (let f = window; f < frames - window; f += 1) {
193
- const samples = [];
194
- for (const j of feet) {
195
- const speed = distance3(
196
- [posed[(f * JOINTS + j) * 3], posed[(f * JOINTS + j) * 3 + 1], posed[(f * JOINTS + j) * 3 + 2]],
197
- [posed[((f - window) * JOINTS + j) * 3], posed[((f - window) * JOINTS + j) * 3 + 1], posed[((f - window) * JOINTS + j) * 3 + 2]],
198
- ) / (window / fps);
199
- const ys = []; for (let t = f - window; t <= f + window; t += 1) ys.push(posed[(t * JOINTS + j) * 3 + 1]);
200
- const spread = Math.max(...ys) - Math.min(...ys);
201
- if (speed < .08 && spread < .025) samples.push(j);
202
- }
203
- // Bilateral contact is a conservative signal that root translation should
204
- // not buzz. Single-foot IK belongs to the character-specific solver.
205
- if (samples.length < 2) continue;
206
- let dx = 0; let dy = 0; let dz = 0;
207
- for (const j of samples) { const o = (f * JOINTS + j) * 3; dx += posed[o]; dy += posed[o + 1]; dz += posed[o + 2]; }
208
- dx /= samples.length; dy /= samples.length; dz /= samples.length;
209
- if (targetHeight !== null) dy -= targetHeight;
210
- // Apply only a small centred correction; larger changes indicate a real
211
- // step or jump and are left to the source trajectory.
212
- const prevAvg = [0, 0, 0]; const nextAvg = [0, 0, 0];
213
- for (const j of samples) {
214
- for (let k = 0; k < 3; k += 1) { prevAvg[k] += posed[((f - 1) * JOINTS + j) * 3 + k]; nextAvg[k] += posed[((f + 1) * JOINTS + j) * 3 + k]; }
215
- }
216
- for (let k = 0; k < 3; k += 1) { prevAvg[k] /= samples.length; nextAvg[k] /= samples.length; }
217
- const correction = [((prevAvg[0] + nextAvg[0]) * .5 - dx), targetHeight !== null ? targetHeight - dy : ((prevAvg[1] + nextAvg[1]) * .5 - dy), ((prevAvg[2] + nextAvg[2]) * .5 - dz)];
218
- const amount = Math.min(.025, Math.hypot(...correction));
219
- if (amount < 1e-4) continue;
220
- const scale = amount / Math.max(Math.hypot(...correction), 1e-8);
221
- for (let j = 0; j < JOINTS; j += 1) { const o = (f * JOINTS + j) * 3; outPosed[o] += correction[0] * scale; outPosed[o + 1] += correction[1] * scale; outPosed[o + 2] += correction[2] * scale; }
222
- outRoot[f * 3] += correction[0] * scale; outRoot[f * 3 + 1] += correction[1] * scale; outRoot[f * 3 + 2] += correction[2] * scale; corrected += 1;
223
- }
224
- return { root: outRoot, posed: outPosed, corrected };
225
- }
226
-
227
- /**
228
- * Foot-anchored root re-integration (#380).
229
- *
230
- * GVHMR integrates the root from per-frame velocity and, on rendered clips,
231
- * under-scales the stride: measured on v13c the stance ankle moonwalked at
232
- * 45 cm/s in world while the hips advanced 49 cm/s — the legs walk faster
233
- * than the body moves. Its contact logits did not correlate with planted
234
- * frames (r <= 0.09) so the anchor is read off the take itself: per frame the
235
- * foot that is BOTH lowest and slowest is the support, and the whole body is
236
- * shifted rigidly (root + every joint by one vector) so that foot holds the
237
- * world XZ it had when its stance run began. A rigid shift keeps every pose
238
- * exactly as authored; only the trajectory changes. Y is never touched (a
239
- * step, stair or chair is a real height change).
240
- *
241
- * Stance is a run: the anchor is only trusted once a foot has been low+slow
242
- * for MIN_STANCE frames, and it releases as soon as the foot lifts or
243
- * accelerates. Low and slow are both read off the INPUT arrays (the shift is
244
- * written to copies), so the correction cannot feed back into its own
245
- * trigger. Between runs the accumulated offset is held, never reset, so the
246
- * take stays continuous.
247
- */
248
- function anchorFeetToFloor(root, posed, frames, fps, reference = posed) {
249
- const outRoot = new Float32Array(root); const outPosed = new Float32Array(posed);
250
- const FEET = [21, 22, 25, 26];
251
- const MIN_STANCE = Math.max(2, Math.round(fps * 0.08));
252
- // Stance is read off `reference` — the AUTHORED joints, before the spike
253
- // filter above: that filter's reversal branch moves the last stance frame
254
- // (a velocity reversal by definition) by up to 2 cm, which would shorten
255
- // every run by one frame and leave that frame sliding.
256
- const at = (f, j, a) => reference[(f * JOINTS + j) * 3 + a];
257
- const heights = FEET.map((j) => Array.from({ length: frames }, (_, f) => at(f, j, 1)));
258
- const low = FEET.map((_, i) => { const s = [...heights[i]].sort((a, b) => a - b); return s[Math.floor((s.length - 1) * 0.35)]; });
259
- // World horizontal speed of the authored foot. Stance is low AND slow;
260
- // "slow" is half the foot's upper-quartile speed (its swing), which
261
- // separates the two phases without depending on the walk's duty cycle —
262
- // a median gate sits ON the stance speed of a 50/50 gait and then admits
263
- // or rejects stance frames on float noise.
264
- const speed = FEET.map((j) => Array.from({ length: frames }, (_, f) => f === 0 ? 0 : Math.hypot(at(f, j, 0) - at(f - 1, j, 0), at(f, j, 2) - at(f - 1, j, 2)) * fps));
265
- const quantile = (v, q) => { const s = [...v].sort((a, b) => a - b); return s[Math.floor((s.length - 1) * q)]; };
266
- const slow = speed.map((v) => quantile(v, 0.75) * 0.5);
267
- const planted = (i, f) => heights[i][f] <= low[i] + 0.01 && speed[i][f] <= slow[i];
268
- // Pass 1: stance runs. A run is one foot planted for >= MIN_STANCE
269
- // consecutive frames; the lowest such foot carries the frame. Runs of the
270
- // same foot that touch are one run.
271
- const support = new Int8Array(frames).fill(-1);
272
- for (let f = 0; f < frames; f += 1) {
273
- let best = -1;
274
- for (let i = 0; i < FEET.length; i += 1) {
275
- let run = 0; for (let t = f; t >= 0 && planted(i, t); t -= 1) run += 1;
276
- for (let t = f + 1; t < frames && run < MIN_STANCE && planted(i, t); t += 1) run += 1;
277
- if (run >= MIN_STANCE && (best < 0 || heights[i][f] < heights[best][f])) best = i;
278
- }
279
- support[f] = best;
280
- }
281
- // Pass 2: the offset that holds each run's foot at its landing position.
282
- // Inside a run only the foot's DRIFT is cancelled — the straight line from
283
- // where it landed to where it lifted, which is the under-scaled stride —
284
- // never its frame-to-frame wobble: mirroring that wobble into the root
285
- // would move the whole body by the foot's estimation noise (measured:
286
- // +4.4 mm/f² of root jitter for -25 cm/s of slide). Across the gap between
287
- // two runs the offset ramps linearly so the swap is spread over the swing.
288
- // Every pose is shifted rigidly per frame.
289
- const offsets = new Array(frames).fill(null);
290
- let anchoredFrames = 0; let carry = [0, 0]; let f = 0;
291
- while (f < frames) {
292
- const i = support[f];
293
- if (i < 0) { f += 1; continue; }
294
- let g = f; while (g + 1 < frames && support[g + 1] === i) g += 1;
295
- const j = FEET[i];
296
- // Land where the previous run left the body: the foot's own position
297
- // at the run's first frame plus the offset carried in.
298
- const driftX = at(g, j, 0) - at(f, j, 0), driftZ = at(g, j, 2) - at(f, j, 2), len = Math.max(1, g - f);
299
- for (let t = f; t <= g; t += 1) { const w = (t - f) / len; offsets[t] = [carry[0] - driftX * w, carry[1] - driftZ * w]; anchoredFrames += 1; }
300
- carry = offsets[g];
301
- f = g + 1;
302
- }
303
- // Fill the gaps: lead-in holds the first run's offset, gaps ramp, tail holds.
304
- let prev = -1;
305
- for (let t = 0; t < frames; t += 1) {
306
- if (offsets[t]) { prev = t; continue; }
307
- let nextAt = t; while (nextAt < frames && !offsets[nextAt]) nextAt += 1;
308
- const a = prev >= 0 ? offsets[prev] : (nextAt < frames ? offsets[nextAt] : [0, 0]);
309
- const b = nextAt < frames ? offsets[nextAt] : a;
310
- const span = nextAt - (prev >= 0 ? prev : t - 1);
311
- for (let u = t; u < nextAt; u += 1) { const w = prev >= 0 && nextAt < frames ? (u - prev) / span : 0; offsets[u] = [a[0] + (b[0] - a[0]) * w, a[1] + (b[1] - a[1]) * w]; }
312
- t = nextAt - 1;
313
- }
314
- // The offset series is piecewise linear with a slope change at every run
315
- // and gap boundary (the stride correction differs per step). Blur it over
316
- // ~120 ms so the body's velocity, not just its position, is continuous:
317
- // the slope changes become ramps and add no second-difference of their own.
318
- // A blur this short leaves the stance foot within a few mm of still.
319
- const radius = Math.max(1, Math.round(fps * 0.08));
320
- const kernel = []; let norm = 0;
321
- for (let k = -radius; k <= radius; k += 1) { const w = Math.exp(-0.5 * (k / (radius / 2)) ** 2); kernel.push(w); norm += w; }
322
- for (let t = 0; t < frames; t += 1) {
323
- let ox = 0, oz = 0;
324
- for (let k = -radius; k <= radius; k += 1) { const u = Math.min(frames - 1, Math.max(0, t + k)); const w = kernel[k + radius] / norm; ox += offsets[u][0] * w; oz += offsets[u][1] * w; }
325
- for (let j = 0; j < JOINTS; j += 1) { const o = (t * JOINTS + j) * 3; outPosed[o] = posed[o] + ox; outPosed[o + 2] = posed[o + 2] + oz; }
326
- outRoot[t * 3] = root[t * 3] + ox; outRoot[t * 3 + 2] = root[t * 3 + 2] + oz;
327
- }
328
- return { root: outRoot, posed: outPosed, anchoredFrames };
329
- }
330
-
331
- export function stabilizeMotion(motion, { enabled = true, smoothRotations: smoothRotationSeries = true, contactHeight, groundY, anchorFeet = false } = {}) {
332
- if (!enabled || !motion || motion.frames < 3) return { ...motion, stabilization: { enabled: false, correctedPositions: 0, correctedRotations: 0, anchoredFrames: 0 } };
333
- const frames = motion.frames; const fps = Number(motion.fps) || 30;
334
- const root = smoothPositions(motion.rootPos, frames, 1, fps);
335
- const posed = smoothPositions(motion.posedJoints, frames, JOINTS, fps);
336
- const rotations = smoothRotationSeries ? smoothRotations(motion.rotMats, frames, JOINTS, fps) : { values: new Float32Array(motion.rotMats), corrected: 0 };
337
- // Keep the root channel and the Hips joint coherent after filtering.
338
- for (let f = 0; f < frames; f += 1) {
339
- root.values[f * 3] = posed.values[(f * JOINTS) * 3];
340
- root.values[f * 3 + 1] = posed.values[(f * JOINTS) * 3 + 1];
341
- root.values[f * 3 + 2] = posed.values[(f * JOINTS) * 3 + 2];
342
- }
343
- const contacts = stabilizeContacts(root.values, posed.values, frames, fps, { contactHeight, groundY });
344
- const anchored = anchorFeet ? anchorFeetToFloor(contacts.root, contacts.posed, frames, fps, motion.posedJoints) : { root: contacts.root, posed: contacts.posed, anchoredFrames: 0 };
345
- return { ...motion, rootPos: anchored.root, posedJoints: anchored.posed, rotMats: rotations.values,
346
- stabilization: { enabled: true, correctedPositions: posed.corrected + root.corrected, correctedRotations: rotations.corrected,
347
- correctedContacts: contacts.corrected, anchoredFrames: anchored.anchoredFrames, contactHeight: Number.isFinite(contactHeight) ? contactHeight : (Number.isFinite(groundY) ? groundY : null), fps } };
348
- }