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