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,230 +0,0 @@
1
- /* Pure-math foot locking, ported from Daniel Holden's article "Inverse
2
- * Kinematics and Foot Locking" (https://theorangeduck.com/page/inverse-kinematics-foot-locking).
3
- * Foot slide is a velocity error: instead of snapping a planted toe to the
4
- * floor, the locked contact target is blended in and out with cubic
5
- * inertialization so position and velocity stay continuous, the TOE (never
6
- * the heel) is the locked point, and IK stays a minimal modification of the
7
- * source pose. Everything here is a pure function over THREE.Vector3 data —
8
- * no rig, scene, or playback access — so callers decide how targets reach
9
- * the skeleton. */
10
- import * as THREE from "three";
11
-
12
- /* Shared read-only zero vector for "the contact point is stationary". Never
13
- * mutated; Vector3 methods that would mutate it are never called on it. */
14
- const ZERO = new THREE.Vector3();
15
-
16
- /* Cubic inertialization weights (Holden eq. for the cubic ease). w0/w1 blend
17
- * position, w2/w3 blend velocity; w1/w2 carry the blendTime units. blendTime
18
- * is guarded so a zero blend degrades to "snap now" instead of dividing by
19
- * zero. */
20
- function cubicWeights(time, blendTime) {
21
- const bt = Math.max(blendTime, 1e-8);
22
- const t = Math.min(Math.max(time / bt, 0), 1);
23
- const t2 = t * t;
24
- const t3 = t2 * t;
25
- return {
26
- w0: 2 * t3 - 3 * t2 + 1,
27
- w1: (t3 - 2 * t2 + t) * bt,
28
- w2: (6 * t2 - 6 * t) / bt,
29
- w3: 3 * t2 - 4 * t + 1,
30
- };
31
- }
32
-
33
- /* Re-target the inertializer: fold the current offset state at `time` into a
34
- * fresh offset that reproduces the same output trajectory relative to the new
35
- * destination, then restart the blend clock. Source is where the motion is
36
- * coming from (the pre-switch target), destination where it goes next. */
37
- export function inertializeCubicTransition(offsetPosition, offsetVelocity, time, sourcePosition, sourceVelocity, destinationPosition, destinationVelocity, blendTime) {
38
- const { w0, w1, w2, w3 } = cubicWeights(time.value, blendTime);
39
- return {
40
- offsetPosition: sourcePosition.clone().addScaledVector(offsetPosition, w0).addScaledVector(offsetVelocity, w1).sub(destinationPosition),
41
- offsetVelocity: sourceVelocity.clone().addScaledVector(offsetPosition, w2).addScaledVector(offsetVelocity, w3).sub(destinationVelocity),
42
- time: { value: 0 },
43
- };
44
- }
45
-
46
- /* Advance the inertialized output one step: the input stream plus the decaying
47
- * offset. `time` is a mutable { value } box so the offset state can be handed
48
- * around without wrapping it in a class. */
49
- export function inertializeCubicUpdate(position, velocity, time, inputPosition, inputVelocity, offsetPosition, offsetVelocity, deltaTime, blendTime) {
50
- // Holden evaluates the weights at time+deltaTime so the step lands at the
51
- // end of the interval, not its start.
52
- const { w0, w1, w2, w3 } = cubicWeights(time.value + deltaTime, blendTime);
53
- position.copy(inputPosition).addScaledVector(offsetPosition, w0).addScaledVector(offsetVelocity, w1);
54
- velocity.copy(inputVelocity).addScaledVector(offsetPosition, w2).addScaledVector(offsetVelocity, w3);
55
- time.value += deltaTime;
56
- return position;
57
- }
58
-
59
- /* Fresh foot-lock state for one toe. `position` seeds both the filtered
60
- * output and the "previous input" so the first finite-difference velocity is
61
- * not polluted by an arbitrary origin. */
62
- export function createFootLockState(position) {
63
- return {
64
- position: position.clone(),
65
- velocity: new THREE.Vector3(),
66
- inputPosition: position.clone(),
67
- inputVelocity: new THREE.Vector3(),
68
- offsetPosition: new THREE.Vector3(),
69
- offsetVelocity: new THREE.Vector3(),
70
- time: { value: 0 },
71
- contact: position.clone(),
72
- locked: false,
73
- };
74
- }
75
-
76
- /* Per-frame foot locking for one toe (Holden's UpdateFootLockingState).
77
- * contactHeight plants the lock at floor height; pass null to keep the input
78
- * y. options.contactPoint (used by footLockTrack for span anchors) overrides
79
- * the first-contact input position as the locked point. */
80
- export function updateFootLockState(state, inputPosition, inputContact, contactHeight, deltaTime, { unlockDistance = 0.2, lockDistance = 0.1, blendTime = 0.2, contactPoint = null } = {}) {
81
- // Input velocity by finite difference: slide is a velocity error, so the
82
- // corrector needs to know how fast the raw toe was drifting.
83
- const dt = Math.max(deltaTime, 1e-8);
84
- state.inputVelocity.copy(inputPosition).sub(state.inputPosition).divideScalar(dt);
85
-
86
- if (!state.locked && inputContact && state.position.distanceTo(inputPosition) < lockDistance) {
87
- // Lock onto the TOE: capture the contact point (input, dropped to the
88
- // requested height) and hand the current motion error to the
89
- // inertializer as a transition from the input onto the stationary
90
- // contact.
91
- state.locked = true;
92
- if (contactPoint) {
93
- // An explicit anchor (e.g. a hand-placed ground mark) IS the contact
94
- // point — its y is authoritative, never re-derived from the input.
95
- state.contact.copy(contactPoint);
96
- } else {
97
- state.contact.copy(inputPosition);
98
- if (contactHeight === null || contactHeight === undefined) state.contact.y = inputPosition.y;
99
- else state.contact.y = contactHeight;
100
- }
101
- const next = inertializeCubicTransition(state.offsetPosition, state.offsetVelocity, state.time, inputPosition, state.inputVelocity, state.contact, ZERO, blendTime);
102
- state.offsetPosition.copy(next.offsetPosition);
103
- state.offsetVelocity.copy(next.offsetVelocity);
104
- state.time.value = next.time.value;
105
- } else if (state.locked && (!inputContact || state.position.distanceTo(inputPosition) > unlockDistance)) {
106
- // Release: transition from the stationary contact back onto the moving
107
- // input so the toe catches up instead of teleporting.
108
- state.locked = false;
109
- const next = inertializeCubicTransition(state.offsetPosition, state.offsetVelocity, state.time, state.contact, ZERO, inputPosition, state.inputVelocity, blendTime);
110
- state.offsetPosition.copy(next.offsetPosition);
111
- state.offsetVelocity.copy(next.offsetVelocity);
112
- state.time.value = next.time.value;
113
- }
114
-
115
- const target = state.locked ? state.contact : inputPosition;
116
- const targetVelocity = state.locked ? ZERO : state.inputVelocity;
117
- inertializeCubicUpdate(state.position, state.velocity, state.time, target, targetVelocity, state.offsetPosition, state.offsetVelocity, deltaTime, blendTime);
118
- state.inputPosition.copy(inputPosition);
119
- return state.position;
120
- }
121
-
122
- /* Offline driver: run the per-frame state over a whole clip. `inputs` is one
123
- * toe position per frame; `contacts` is booleans per frame, or — when
124
- * `anchors` is given — span objects { start, end, anchor? }. anchors[f] being
125
- * a Vector3 pins the locked contact to that exact point (e.g. a hand-placed
126
- * ground mark) instead of wherever the toe first touched; with contactHeight
127
- * null the input y is kept. Frame 0 just initialises the state. */
128
- export function footLockTrack(inputs, contacts, { fps = 30, blendTime = 0.2, lockDistance = 0.1, unlockDistance = 0.2, contactHeight = null, anchors = null } = {}) {
129
- const count = inputs.length;
130
- const flags = new Array(count).fill(false);
131
- const points = new Array(count).fill(null);
132
- // Contacts arrive either as per-frame booleans or as {start,end,anchor?}
133
- // spans; expand spans so both shapes feed the same flags/points.
134
- for (let f = 0; f < count; f++) {
135
- const c = contacts[f];
136
- if (typeof c === "boolean") {
137
- flags[f] = c;
138
- } else if (c && Number.isFinite(c.start)) {
139
- for (let g = Math.max(0, c.start); g <= Math.min(count - 1, c.end); g++) {
140
- flags[g] = true;
141
- if (points[g] === null && c.anchor instanceof THREE.Vector3) points[g] = c.anchor;
142
- }
143
- }
144
- }
145
- if (anchors !== null) {
146
- // Per-frame anchors name a specific frame, so they outrank span anchors.
147
- for (let f = 0; f < count; f++) {
148
- if (anchors[f] instanceof THREE.Vector3) points[f] = anchors[f];
149
- }
150
- }
151
-
152
- const state = createFootLockState(inputs[0]);
153
- const out = [state.position.clone()];
154
- const deltaTime = 1 / Math.max(fps, 1e-6);
155
- for (let f = 1; f < count; f++) {
156
- updateFootLockState(state, inputs[f], flags[f], contactHeight, deltaTime, { lockDistance, unlockDistance, blendTime, contactPoint: points[f] });
157
- out.push(state.position.clone());
158
- }
159
- return out;
160
- }
161
-
162
- /* Holden's offline relaxation: given SOURCE toe/pelvis tracks and per-frame
163
- * contact flags, absorb contact-slide drift without inventing motion. Inputs
164
- * are never mutated; clones are relaxed and returned. Contact frames collapse
165
- * toward a shared ground point (hard); air frames chase their neighbour plus
166
- * the SOURCE relative displacement so the gait survives (soft); the in-frame
167
- * leg-length constraint keeps extension at what the source had instead of
168
- * pulling the hips down. */
169
- export function relaxToePath(toes, pelvis, contacts, { iterations = 200, softFactor = 0.05, hardFactor = 0.9, toeMinHeight = 0, restLengths = null, relaxPelvis = false } = {}) {
170
- const count = toes.length;
171
- const sourceToes = toes.map((t) => t.clone());
172
- const outToes = toes.map((t) => t.clone());
173
- const outPelvis = pelvis.map((p) => p.clone());
174
- // Rest lengths default to the source pose: the point is to stop the leg
175
- // stretching further than the animation ever did.
176
- const rest = restLengths ?? sourceToes.map((t, i) => outPelvis[i].distanceTo(t));
177
- const tmp = new THREE.Vector3();
178
- const target = new THREE.Vector3();
179
-
180
- // Move `point` factor of the way to `target`, clamping y up to minY when
181
- // requested; returns the applied distance so the caller can early-exit.
182
- const relaxPoint = (point, to, factor, minY) => {
183
- const ox = point.x, oy = point.y, oz = point.z;
184
- point.lerp(to, factor);
185
- if (minY !== null && point.y < minY) point.y = minY;
186
- return Math.hypot(point.x - ox, point.y - oy, point.z - oz);
187
- };
188
- // Soft target for frame i: neighbour j plus the SOURCE i-j displacement,
189
- // so air frames preserve stride rather than smearing into each other.
190
- const chaseNeighbour = (out, source, i, j, factor, minY) => {
191
- target.copy(out[j]).add(source[i]).sub(source[j]);
192
- return relaxPoint(out[i], target, factor, minY);
193
- };
194
-
195
- for (let iter = 0; iter < iterations; iter++) {
196
- let largest = 0;
197
- for (let i = 1; i < count; i++) {
198
- if (contacts[i - 1] && contacts[i]) {
199
- // Both frames planted: they belong at ONE spot, so pull both
200
- // toward their midpoint dropped to the ground height.
201
- target.copy(outToes[i]).add(outToes[i - 1]).multiplyScalar(0.5);
202
- target.y = Array.isArray(toeMinHeight) ? Math.max(toeMinHeight[i] ?? 0, toeMinHeight[i - 1] ?? 0) : toeMinHeight;
203
- largest = Math.max(largest, relaxPoint(outToes[i], target, hardFactor, null));
204
- largest = Math.max(largest, relaxPoint(outToes[i - 1], target, hardFactor, null));
205
- } else {
206
- largest = Math.max(largest, chaseNeighbour(outToes, sourceToes, i, i - 1, softFactor, Array.isArray(toeMinHeight) ? toeMinHeight[i] : toeMinHeight));
207
- largest = Math.max(largest, chaseNeighbour(outToes, sourceToes, i - 1, i, softFactor, Array.isArray(toeMinHeight) ? toeMinHeight[i - 1] : toeMinHeight));
208
- if (relaxPelvis) {
209
- largest = Math.max(largest, chaseNeighbour(outPelvis, pelvis, i, i - 1, softFactor, null));
210
- largest = Math.max(largest, chaseNeighbour(outPelvis, pelvis, i - 1, i, softFactor, null));
211
- }
212
- }
213
- }
214
- // In-frame constraint: hold |pelvis - toe| at the rest length by
215
- // moving the toe (splitting the correction with the pelvis only when
216
- // asked — moving the pelvis is the "pull the hips down" failure the
217
- // article warns about).
218
- for (let i = 0; i < count; i++) {
219
- tmp.copy(outToes[i]).sub(outPelvis[i]);
220
- const d = tmp.length();
221
- if (d < 1e-9) continue;
222
- const correction = ((d - rest[i]) * softFactor) / d;
223
- outToes[i].addScaledVector(tmp, -correction);
224
- if (relaxPelvis) outPelvis[i].addScaledVector(tmp, correction);
225
- largest = Math.max(largest, Math.abs(correction) * d);
226
- }
227
- if (largest < 1e-5) break;
228
- }
229
- return { toes: outToes, pelvis: outPelvis };
230
- }
@@ -1,118 +0,0 @@
1
- // Pure scene-space calibration for decoded GVHMR motion. Extraction owns a
2
- // camera-independent coordinate system; this pass applies the optional rigid
3
- // transform recorded by the scene (uniform scale, Y rotation and translation).
4
- // It deliberately leaves local joint rotations untouched.
5
-
6
- const MOTION_CALIBRATION_LIMITS = Object.freeze({
7
- minScale: 0.1,
8
- maxScale: 10,
9
- maxOffset: 100,
10
- });
11
-
12
- const finite = (value, fallback) => {
13
- const number = Number(value);
14
- return Number.isFinite(number) ? number : fallback;
15
- };
16
-
17
- function wrapDegrees(value) {
18
- const wrapped = ((value + 180) % 360 + 360) % 360 - 180;
19
- // Keep the canonical positive half-open interval stable for exact 180.
20
- return Object.is(wrapped, -0) ? 0 : wrapped;
21
- }
22
-
23
- /**
24
- * Return a safe, JSON-friendly calibration envelope. Missing values are the
25
- * identity transform; finite values outside the scene envelope are clamped so
26
- * persisted metadata cannot throw a take metres away or produce NaNs.
27
- */
28
- export function normalizeMotionCalibration(value, limits = MOTION_CALIBRATION_LIMITS) {
29
- const source = value && typeof value === "object" && !Array.isArray(value) ? value : {};
30
- const minScale = Math.max(Number.EPSILON, finite(limits?.minScale, MOTION_CALIBRATION_LIMITS.minScale));
31
- const maxScale = Math.max(minScale, finite(limits?.maxScale, MOTION_CALIBRATION_LIMITS.maxScale));
32
- const maxOffset = Math.max(0, finite(limits?.maxOffset, MOTION_CALIBRATION_LIMITS.maxOffset));
33
- const rawScale = finite(source.scale, 1);
34
- const rawYaw = finite(source.yawDeg ?? source.yaw, 0);
35
- const clampOffset = (entry) => Math.max(-maxOffset, Math.min(maxOffset, finite(entry, 0)));
36
- return {
37
- scale: Math.max(minScale, Math.min(maxScale, rawScale > 0 ? rawScale : 1)),
38
- yawDeg: wrapDegrees(rawYaw),
39
- offsetX: clampOffset(source.offsetX ?? source.offset_x),
40
- offsetY: clampOffset(source.offsetY ?? source.offset_y),
41
- offsetZ: clampOffset(source.offsetZ ?? source.offset_z),
42
- };
43
- }
44
-
45
- function copyArray(values) {
46
- if (values == null) return values;
47
- if (typeof values.slice === "function") return values.slice();
48
- return Array.from(values);
49
- }
50
-
51
- function validArray(values, length) {
52
- return values != null && typeof values.length === "number" && values.length >= length;
53
- }
54
-
55
- /**
56
- * Apply a rigid scene calibration to root and posed-joint positions.
57
- *
58
- * The input take is never mutated. The result follows the same
59
- * `{ motion, diagnostics }` contract as the other ARDY correction passes.
60
- * `rotMats` are local joint rotations, so a world-space yaw does not change
61
- * them and the original array is intentionally retained by reference.
62
- */
63
- export function applyMotionCalibration(motion, metadata = motion?.sceneCalibration, options = {}) {
64
- const identity = normalizeMotionCalibration(null, options.limits);
65
- const baseDiagnostics = {
66
- status: "not-needed",
67
- applied: false,
68
- changedFrames: 0,
69
- maxDisplacement: 0,
70
- maxRootDisplacement: 0,
71
- maxJointDisplacement: 0,
72
- calibration: identity,
73
- };
74
- if (!motion || typeof motion !== "object") return { motion, diagnostics: { ...baseDiagnostics, status: "invalid-motion" } };
75
- const calibration = normalizeMotionCalibration(metadata, options.limits);
76
- const frames = Number(motion.frames);
77
- const hasRoot = Number.isInteger(frames) && frames >= 0 && validArray(motion.rootPos, frames * 3);
78
- const hasJoints = Number.isInteger(frames) && frames >= 0 && validArray(motion.posedJoints, 0) && motion.posedJoints.length % (frames * 3 || 1) === 0;
79
- if (!hasRoot && !hasJoints) return { motion, diagnostics: { ...baseDiagnostics, status: "invalid-motion", calibration } };
80
- const isIdentity = calibration.scale === 1 && calibration.yawDeg === 0 && calibration.offsetX === 0 && calibration.offsetY === 0 && calibration.offsetZ === 0;
81
- if (isIdentity) return { motion, diagnostics: { ...baseDiagnostics, calibration } };
82
-
83
- const radians = calibration.yawDeg * Math.PI / 180;
84
- const cos = Math.cos(radians), sin = Math.sin(radians), transform = (x, y, z) => {
85
- const sx = calibration.scale * x, sy = calibration.scale * y, sz = calibration.scale * z;
86
- return [cos * sx + sin * sz + calibration.offsetX, sy + calibration.offsetY, -sin * sx + cos * sz + calibration.offsetZ];
87
- };
88
- const rootPos = hasRoot ? copyArray(motion.rootPos) : motion.rootPos;
89
- const posedJoints = hasJoints ? copyArray(motion.posedJoints) : motion.posedJoints;
90
- let maxRootDisplacement = 0, maxJointDisplacement = 0, changedFrames = 0;
91
- const displacement = (before, after) => {
92
- if (![...before, ...after].every(Number.isFinite)) return 0;
93
- return Math.hypot(after[0] - before[0], after[1] - before[1], after[2] - before[2]);
94
- };
95
- for (let frame = 0; frame < frames; frame += 1) {
96
- let frameChanged = false;
97
- if (hasRoot) {
98
- const offset = frame * 3, before = [motion.rootPos[offset], motion.rootPos[offset + 1], motion.rootPos[offset + 2]], after = transform(...before);
99
- rootPos[offset] = after[0]; rootPos[offset + 1] = after[1]; rootPos[offset + 2] = after[2];
100
- maxRootDisplacement = Math.max(maxRootDisplacement, displacement(before, after)); frameChanged ||= displacement(before, after) > 1e-9;
101
- }
102
- if (hasJoints) {
103
- const jointCount = motion.posedJoints.length / (frames * 3);
104
- for (let joint = 0; joint < jointCount; joint += 1) {
105
- const offset = (frame * jointCount + joint) * 3, before = [motion.posedJoints[offset], motion.posedJoints[offset + 1], motion.posedJoints[offset + 2]], after = transform(...before);
106
- posedJoints[offset] = after[0]; posedJoints[offset + 1] = after[1]; posedJoints[offset + 2] = after[2];
107
- maxJointDisplacement = Math.max(maxJointDisplacement, displacement(before, after)); frameChanged ||= displacement(before, after) > 1e-9;
108
- }
109
- }
110
- if (frameChanged) changedFrames += 1;
111
- }
112
- const diagnostics = {
113
- status: "calibrated", applied: true, changedFrames,
114
- maxDisplacement: Math.max(maxRootDisplacement, maxJointDisplacement),
115
- maxRootDisplacement, maxJointDisplacement, calibration,
116
- };
117
- return { motion: { ...motion, rootPos, posedJoints }, diagnostics };
118
- }
@@ -1,213 +0,0 @@
1
- import { retimeMotion } from "./retime.js";
2
- import { sliceMotion } from "./trim.js";
3
-
4
- const SPEED_MIN = 0.1;
5
- const SPEED_MAX = 4;
6
- const SPEED_STEP = 0.1;
7
- const normalizeSpeed = (speed) => Math.round(Number(speed) / SPEED_STEP) * SPEED_STEP;
8
-
9
- const segmentSourceFrames = (segment) => segment.sourceEnd - segment.sourceStart + 1;
10
- const segmentTimelineFrames = (segment) => Math.max(1, Math.round(segmentSourceFrames(segment) / segment.speed));
11
-
12
- export function createMotionEdit(frames) {
13
- if (!Number.isInteger(frames) || frames < 1) {
14
- throw new Error("createMotionEdit: frames must be a positive integer");
15
- }
16
- return [{ id: "motion-0", sourceStart: 0, sourceEnd: frames - 1, speed: 1 }];
17
- }
18
-
19
- export function motionEditDuration(edit) {
20
- if (!Array.isArray(edit) || edit.length < 1) return 0;
21
- return edit.reduce((total, segment) => total + segmentTimelineFrames(segment), 0);
22
- }
23
-
24
- export function motionEditLayout(edit) {
25
- let cursor = 0;
26
- return edit.map((segment) => {
27
- const frames = segmentTimelineFrames(segment);
28
- const item = {
29
- ...segment,
30
- timelineStart: cursor,
31
- timelineEnd: cursor + frames - 1,
32
- timelineFrames: frames,
33
- };
34
- cursor += frames;
35
- return item;
36
- });
37
- }
38
-
39
- export function timelineFrameToMotionFrame(edit, frame) {
40
- const layout = motionEditLayout(edit);
41
- if (layout.length < 1) return Math.max(0, Math.round(frame) || 0);
42
- const timelineFrame = Math.max(0, Math.min(Number.isFinite(frame) ? frame : 0, layout.at(-1).timelineEnd));
43
- const segment = layout.find((item) => timelineFrame <= item.timelineEnd) ?? layout.at(-1);
44
- if (segment.timelineFrames <= 1) return segment.sourceStart;
45
- const local = timelineFrame - segment.timelineStart;
46
- const progress = local / (segment.timelineFrames - 1);
47
- return segment.sourceStart + progress * (segment.sourceEnd - segment.sourceStart);
48
- }
49
-
50
- /** The inverse of timelineFrameToMotionFrame: which timeline frame shows a
51
- * SOURCE frame under this edit. Returns null when no segment covers it (the
52
- * source range was deleted or trimmed away). When two segments cover the same
53
- * source frame (a cut boundary), the first segment answers. */
54
- export function motionFrameToTimelineFrame(edit, sourceFrame) {
55
- if (!Array.isArray(edit) || edit.length < 1 || !Number.isFinite(sourceFrame)) return null;
56
- const layout = motionEditLayout(edit);
57
- const segment = layout.find((item) => sourceFrame >= item.sourceStart && sourceFrame <= item.sourceEnd);
58
- if (!segment) return null;
59
- if (segment.sourceEnd === segment.sourceStart) return segment.timelineStart;
60
- const progress = (sourceFrame - segment.sourceStart) / (segment.sourceEnd - segment.sourceStart);
61
- return Math.round(segment.timelineStart + progress * (segment.timelineFrames - 1));
62
- }
63
-
64
- /**
65
- * Where an OLD timeline frame lands after a segment edit (#79): old timeline
66
- * → source → new timeline, each leg piecewise-linear per segment. Returns
67
- * null when the frame's source content no longer exists in the new edit.
68
- * Anything pinned to timeline frames (IK correction keys, prompt clips) must
69
- * ride this mapping when segments change, or it stays glued to frame NUMBERS
70
- * while the POSES those numbers address move away.
71
- */
72
- export function remapTimelineFrame(previousEdit, nextEdit, frame) {
73
- const source = timelineFrameToMotionFrame(previousEdit, frame);
74
- return motionFrameToTimelineFrame(nextEdit, source);
75
- }
76
-
77
- /** Migrate a frame-keyed Map through a segment edit. Values move untouched;
78
- * a speed-up can land two old frames on one new frame — the earlier old
79
- * frame wins so an authored key is never silently replaced by a later one —
80
- * and keys whose source frames were deleted drop out. */
81
- export function remapFrameKeyMap(keys, previousEdit, nextEdit) {
82
- const out = new Map();
83
- for (const frame of [...keys.keys()].sort((a, b) => a - b)) {
84
- const mapped = remapTimelineFrame(previousEdit, nextEdit, frame);
85
- if (mapped === null || out.has(mapped)) continue;
86
- out.set(mapped, keys.get(frame));
87
- }
88
- return out;
89
- }
90
-
91
- export function splitMotionEdit(edit, timelineFrame) {
92
- if (!Array.isArray(edit) || edit.length < 1) return edit;
93
- const layout = motionEditLayout(edit);
94
- const cut = Math.round(timelineFrame);
95
- if (cut <= 0 || cut >= motionEditDuration(edit)) return edit;
96
- const index = layout.findIndex((segment) => cut > segment.timelineStart && cut <= segment.timelineEnd);
97
- if (index < 0) return edit;
98
- const segment = layout[index];
99
- const sourceCut = Math.max(
100
- segment.sourceStart + 1,
101
- Math.min(segment.sourceEnd, Math.round(timelineFrameToMotionFrame(edit, cut))),
102
- );
103
- if (sourceCut <= segment.sourceStart || sourceCut > segment.sourceEnd) return edit;
104
- const next = [...edit];
105
- next.splice(index, 1,
106
- { id: `${segment.id}-a`, sourceStart: segment.sourceStart, sourceEnd: sourceCut - 1, speed: segment.speed },
107
- { id: `${segment.id}-b`, sourceStart: sourceCut, sourceEnd: segment.sourceEnd, speed: segment.speed },
108
- );
109
- return next;
110
- }
111
-
112
- export function setMotionSegmentSpeed(edit, id, speed) {
113
- const normalized = normalizeSpeed(speed);
114
- if (!Number.isFinite(normalized) || normalized < SPEED_MIN || normalized > SPEED_MAX) {
115
- throw new Error("setMotionSegmentSpeed: speed must be between 0.1 and 4.0 in 0.1 steps");
116
- }
117
- return edit.map((segment) => segment.id === id ? { ...segment, speed: Number(normalized.toFixed(1)) } : segment);
118
- }
119
-
120
- /** Delete one segment from the edit. The last remaining segment stays: an
121
- * empty edit has no duration, and "no motion" is clearMotion's job, not a
122
- * segment operation's. Unknown ids return the edit unchanged. */
123
- export function removeMotionSegment(edit, id) {
124
- if (!Array.isArray(edit) || edit.length < 2) return edit;
125
- const next = edit.filter((segment) => segment.id !== id);
126
- return next.length === edit.length ? edit : next;
127
- }
128
-
129
- /** The speed that renders a segment across `timelineFrames` production
130
- * frames — the drag-resize inverse of segmentTimelineFrames. Clamped to the
131
- * same 0.1x..4x range and 0.1 grid setMotionSegmentSpeed enforces, so a drag
132
- * can never commit a speed the numeric input would reject. */
133
- export function motionSegmentSpeedForFrames(segment, timelineFrames) {
134
- const frames = Math.max(1, Math.round(Number(timelineFrames) || 1));
135
- const raw = segmentSourceFrames(segment) / frames;
136
- const clamped = Math.min(SPEED_MAX, Math.max(SPEED_MIN, raw));
137
- return Number(normalizeSpeed(clamped).toFixed(1));
138
- }
139
-
140
- export function trimMotionEdit(edit, startFrame, endFrame) {
141
- if (!Array.isArray(edit) || edit.length < 1) return edit;
142
- const duration = motionEditDuration(edit);
143
- if (
144
- !Number.isInteger(startFrame) ||
145
- !Number.isInteger(endFrame) ||
146
- startFrame < 0 ||
147
- endFrame >= duration ||
148
- startFrame > endFrame
149
- ) {
150
- throw new Error(`trimMotionEdit: range ${startFrame}..${endFrame} is outside 0..${duration - 1}`);
151
- }
152
- const layout = motionEditLayout(edit);
153
- return layout.flatMap((segment) => {
154
- const overlapStart = Math.max(startFrame, segment.timelineStart);
155
- const overlapEnd = Math.min(endFrame, segment.timelineEnd);
156
- if (overlapStart > overlapEnd) return [];
157
- const sourceStart = Math.ceil(timelineFrameToMotionFrame(edit, overlapStart));
158
- const sourceEnd = Math.floor(timelineFrameToMotionFrame(edit, overlapEnd));
159
- return [{
160
- id: `${segment.id}-trim-${sourceStart}`,
161
- sourceStart: Math.max(segment.sourceStart, sourceStart),
162
- sourceEnd: Math.min(segment.sourceEnd, sourceEnd),
163
- speed: segment.speed,
164
- }];
165
- }).filter((segment) => segment.sourceStart <= segment.sourceEnd);
166
- }
167
-
168
- function copyFrame(source, sourceFrame, target, targetFrame, stride) {
169
- const from = sourceFrame * stride;
170
- target.set(source.subarray(from, from + stride), targetFrame * stride);
171
- }
172
-
173
- export function renderMotionEdit(full, edit) {
174
- if (!full || !Number.isInteger(full.frames) || full.frames < 1 || !Number.isFinite(full.fps) || full.fps <= 0) {
175
- throw new Error("renderMotionEdit: motion must have positive frames and fps");
176
- }
177
- const segments = Array.isArray(edit) && edit.length ? edit : createMotionEdit(full.frames);
178
- const rendered = segments.map((segment) => {
179
- const sliced = sliceMotion(full, segment.sourceStart, segment.sourceEnd);
180
- return segment.speed === 1 ? sliced : retimeMotion(sliced, full.fps / segment.speed);
181
- });
182
- const frames = rendered.reduce((total, motion) => total + motion.frames, 0);
183
- const rotStride = full.rotMats.length / full.frames;
184
- const rootStride = full.rootPos.length / full.frames;
185
- const posedStride = full.posedJoints.length / full.frames;
186
- const rotMats = new Float32Array(frames * rotStride);
187
- const rootPos = new Float32Array(frames * rootStride);
188
- const posedJoints = new Float32Array(frames * posedStride);
189
- let cursor = 0;
190
- for (let index = 0; index < rendered.length; index += 1) {
191
- const motion = rendered[index];
192
- const segment = segments[index];
193
- for (let frame = 0; frame < motion.frames; frame += 1) {
194
- const finalFrame = frame === motion.frames - 1 && segment.speed > 1;
195
- const sourceMotion = finalFrame ? full : motion;
196
- const sourceFrame = finalFrame ? segment.sourceEnd : frame;
197
- copyFrame(sourceMotion.rotMats, sourceFrame, rotMats, cursor + frame, rotStride);
198
- copyFrame(sourceMotion.rootPos, sourceFrame, rootPos, cursor + frame, rootStride);
199
- copyFrame(sourceMotion.posedJoints, sourceFrame, posedJoints, cursor + frame, posedStride);
200
- }
201
- cursor += motion.frames;
202
- }
203
- return {
204
- ...full,
205
- frames,
206
- fps: full.fps,
207
- rotMats,
208
- rootPos,
209
- posedJoints,
210
- anchorFrame: 0,
211
- editSegments: segments.map((segment) => ({ ...segment })),
212
- };
213
- }
@@ -1,55 +0,0 @@
1
- /**
2
- * Query-string gate for externally supplied motion URLs (`/app/?motion=`).
3
- *
4
- * The hosted demo hands a visitor a result link that opens the app with a
5
- * `?motion=<url>` query. That value is attacker-controllable, so it is a
6
- * security boundary, not a convenience: only same-origin paths and the
7
- * hosted demo API may ever reach `loadMotionFromUrl`'s fetch. Everything
8
- * else — foreign hosts, downgraded schemes, `javascript:`, protocol-relative
9
- * tricks, userinfo-embedded lookalikes — resolves to `null` and the app
10
- * silently falls back to its normal seed behavior.
11
- *
12
- * Kept as a plain module (no React, no DOM) so the allowlist is testable
13
- * under Node: see test/demo/verify-motion-url-allowlist.mjs.
14
- */
15
-
16
- /** Hosts allowed to serve motion npz files cross-origin, over https only. */
17
- export const MOTION_URL_ALLOWED_HOSTS = ["api.cozyclay.org"];
18
-
19
- /**
20
- * Extract and validate the `motion` query parameter.
21
- *
22
- * @param {string} search - a query string (`location.search`)
23
- * @param {string} origin - the page origin (`location.origin`)
24
- * @returns {string|null} a same-origin path (`/demo/x.npz`) or an absolute
25
- * https URL on an allowlisted host; `null` when absent or not allowed.
26
- */
27
- export function motionUrlFromQuery(search, origin) {
28
- let value;
29
- try {
30
- value = new URLSearchParams(search ?? "").get("motion");
31
- } catch {
32
- return null;
33
- }
34
- if (!value) return null;
35
- let base;
36
- let parsed;
37
- try {
38
- base = new URL(origin);
39
- parsed = new URL(value, base);
40
- } catch {
41
- return null;
42
- }
43
- // Userinfo is only ever used to disguise the real host
44
- // (https://api.cozyclay.org@evil.example/x.npz) — reject it outright,
45
- // even for the page's own origin.
46
- if (parsed.username || parsed.password) return null;
47
- // Same-origin: return the path form so dev (http://127.0.0.1:5180) and
48
- // production resolve identically.
49
- if (parsed.origin === base.origin) return parsed.pathname + parsed.search;
50
- // Cross-origin: https only, exact host match (a non-standard port makes
51
- // `host` differ from the allowlist entry and is therefore rejected).
52
- if (parsed.protocol !== "https:") return null;
53
- if (!MOTION_URL_ALLOWED_HOSTS.includes(parsed.host)) return null;
54
- return parsed.href;
55
- }