cozyclay 2.1.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (583) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +12 -30
  3. package/bin/agent/agent-routes.mjs +40 -113
  4. package/bin/agent/agent-runner.mjs +9 -10
  5. package/bin/agent/cliproxy-provider.mjs +135 -0
  6. package/bin/agent/credential-store.mjs +30 -7
  7. package/bin/agent/pi-tools.mjs +4 -1
  8. package/bin/agent/provider-keys.mjs +32 -0
  9. package/bin/agent/provider-login.mjs +110 -0
  10. package/bin/agent/provider-routes.mjs +99 -0
  11. package/bin/agent/providers.mjs +19 -97
  12. package/bin/agent/script-tool.mjs +17 -4
  13. package/bin/agent/studio-motion-reference.mjs +23 -0
  14. package/bin/agent/studio-prompt.mjs +1 -1
  15. package/bin/agent/studio-rebase-text.mjs +32 -0
  16. package/bin/agent/studio-rebase.mjs +26 -39
  17. package/bin/agent/studio-tools.mjs +9 -28
  18. package/bin/agent/tool-arguments.mjs +49 -0
  19. package/bin/agent/turn-frames.mjs +66 -0
  20. package/bin/codex-auth.mjs +9 -2
  21. package/bin/cozyclay.mjs +10 -116
  22. package/bin/live/cli.mjs +22 -6
  23. package/bin/mcp-runtime.mjs +2 -12
  24. package/bin/package-signature.mjs +0 -4
  25. package/dist/app/index.html +7 -6
  26. package/dist/assets/EXRLoader-CvVIvGVE.js +1 -0
  27. package/dist/assets/analytics-C9sUnndd.js +1 -0
  28. package/dist/assets/app-CCh--1Hc.js +5087 -0
  29. package/dist/assets/app-f-fkV5uP.css +1 -0
  30. package/dist/assets/compile-worker-CkxiD6lH.js +5 -0
  31. package/dist/assets/fflate.module-KbGj5-H6.js +5 -0
  32. package/dist/assets/{first-shot-handoff-BlT33CGf.js → first-shot-handoff-CC4xe4HM.js} +1 -1
  33. package/dist/assets/{landing-BSesrCpL.js → landing-DjHD8u7k.js} +1 -1
  34. package/dist/assets/scene-asset-sync-Hx_FzdOH.js +20 -0
  35. package/dist/assets/scene-asset-sync-_S8NQl5B.css +1 -0
  36. package/dist/assets/workflow-C4yKH1p4.js +179 -0
  37. package/dist/cozyclay-package.json +1 -1
  38. package/dist/index.html +3 -3
  39. package/dist/llms.txt +14 -0
  40. package/dist/sw.js +3 -6
  41. package/dist/workflow/index.html +5 -4
  42. package/mcp/LIVE-PROTOCOL.md +7 -47
  43. package/mcp/README.md +16 -26
  44. package/mcp/live-hub.mjs +6 -10
  45. package/mcp/package.json +1 -3
  46. package/mcp/tool-handlers.mjs +4 -168
  47. package/package.json +3 -22
  48. package/src/App.jsx +174 -2639
  49. package/src/agent-progress.js +79 -0
  50. package/src/agent-turn-mark.js +30 -0
  51. package/src/analytics.js +1 -19
  52. package/src/app-context.js +5 -0
  53. package/src/app-stage.jsx +2 -34
  54. package/src/asset-pane.jsx +6 -5
  55. package/src/camera-block.js +21 -0
  56. package/src/camera-direction.js +120 -0
  57. package/src/character-kind.js +3 -9
  58. package/src/command-bus.js +8 -6
  59. package/src/commands/cast.js +5 -34
  60. package/src/commands/elements/character.js +1 -1
  61. package/src/commands/elements/motion.js +1 -6
  62. package/src/commands/elements/object.js +6 -2
  63. package/src/commands/elements/shot.js +4 -1
  64. package/src/commands/elements.js +38 -14
  65. package/src/commands/index.js +3 -1
  66. package/src/commands/kits.js +37 -0
  67. package/src/commands/motion-timing.js +57 -0
  68. package/src/commands/motion.js +9 -152
  69. package/src/commands/motion3-compile.js +65 -0
  70. package/src/commands/motion3.js +207 -0
  71. package/src/commands/objects.js +6 -4
  72. package/src/commands/shot.js +49 -11
  73. package/src/commands/stage.js +14 -1
  74. package/src/commands/view.js +4 -1
  75. package/src/document-mirrors.js +42 -0
  76. package/src/document-store.js +4 -0
  77. package/src/domains/cast.js +19 -194
  78. package/src/domains/motion/motion-range-pin-rules.js +1 -1
  79. package/src/domains/motion/take-source.js +51 -0
  80. package/src/domains/motion.js +60 -1838
  81. package/src/domains/scenes.js +12 -55
  82. package/src/domains/shots.js +2 -2
  83. package/src/domains/stage.js +4 -2
  84. package/src/draft-ghost.js +46 -0
  85. package/src/draft-ghost.jsx +129 -0
  86. package/src/dualview.jsx +4 -3
  87. package/src/effects/EffectsLayer.jsx +112 -0
  88. package/src/effects/effect-meshes.js +232 -0
  89. package/src/effects/effect-record.js +103 -0
  90. package/src/effects/emitter.js +59 -0
  91. package/src/effects/simulate.js +261 -0
  92. package/src/effects/state.js +59 -0
  93. package/src/execution-telemetry.js +2 -3
  94. package/src/first-success-guide.jsx +5 -5
  95. package/src/ghost-set.js +257 -0
  96. package/src/held-object-edit.jsx +113 -0
  97. package/src/held-object-pose.js +175 -0
  98. package/src/hierarchy-model.js +1 -1
  99. package/src/hierarchy-panel.jsx +1 -1
  100. package/src/imported-mesh.jsx +103 -0
  101. package/src/kits/held.js +84 -0
  102. package/src/kits/index.js +70 -0
  103. package/src/kits/smoking.js +57 -0
  104. package/src/light-blockers.js +93 -0
  105. package/src/motion/beat-board-model.js +84 -0
  106. package/src/motion/beat-board.css +124 -0
  107. package/src/motion/beat-board.jsx +59 -0
  108. package/src/motion/center-of-mass.js +66 -0
  109. package/src/motion/fix-collisions.js +850 -0
  110. package/src/motion/physics-review.js +118 -0
  111. package/src/{ardy → motion}/physics-support.js +1 -76
  112. package/src/motion/rig-snapshot.js +22 -0
  113. package/src/motion/take-segments.jsx +140 -0
  114. package/src/{ardy → motion}/timeline-coordinates.js +0 -21
  115. package/src/{ardy → motion}/timeline.css +1 -2
  116. package/src/{ardy → motion}/timeline.jsx +48 -419
  117. package/src/motion/waypoints.js +76 -0
  118. package/src/motion3/beats.js +83 -0
  119. package/src/motion3/body.js +158 -0
  120. package/src/motion3/bones.js +47 -0
  121. package/src/motion3/carried.js +47 -0
  122. package/src/motion3/compile-job.js +75 -0
  123. package/src/motion3/compile-worker.js +10 -0
  124. package/src/motion3/compiler/anchors.js +105 -0
  125. package/src/motion3/compiler/arm-orient.js +128 -0
  126. package/src/motion3/compiler/channels.js +125 -0
  127. package/src/motion3/compiler/compile.js +205 -0
  128. package/src/motion3/compiler/gait-rhythm.js +97 -0
  129. package/src/motion3/compiler/grip.js +171 -0
  130. package/src/motion3/compiler/head-steady.js +48 -0
  131. package/src/motion3/compiler/hold.js +134 -0
  132. package/src/motion3/compiler/idle.js +113 -0
  133. package/src/motion3/compiler/limbs.js +170 -0
  134. package/src/motion3/compiler/math.js +72 -0
  135. package/src/motion3/compiler/pose.js +162 -0
  136. package/src/motion3/compiler/quat.js +49 -0
  137. package/src/motion3/compiler/skeleton.js +74 -0
  138. package/src/motion3/compiler/validate-common.js +32 -0
  139. package/src/motion3/compiler/validate-goals.js +118 -0
  140. package/src/motion3/compiler/validate-objects.js +143 -0
  141. package/src/motion3/compiler/validate-parts.js +116 -0
  142. package/src/motion3/compiler/validate.js +122 -0
  143. package/src/motion3/compiler/vocab.js +73 -0
  144. package/src/motion3/compiler/walk-pelvis.js +275 -0
  145. package/src/motion3/compiler/walk.js +207 -0
  146. package/src/motion3/compiler/wrist-clamp.js +32 -0
  147. package/src/motion3/delta.js +47 -0
  148. package/src/motion3/describe.js +174 -0
  149. package/src/motion3/docs.js +27 -0
  150. package/src/motion3/edit-carry.js +21 -0
  151. package/src/motion3/fix/ease.js +16 -0
  152. package/src/motion3/fix/ground.js +37 -0
  153. package/src/motion3/fix/index.js +52 -0
  154. package/src/motion3/fix/push.js +71 -0
  155. package/src/motion3/fix/reach.js +46 -0
  156. package/src/motion3/fix/retime.js +79 -0
  157. package/src/motion3/fix/self.js +211 -0
  158. package/src/motion3/fix/slide.js +70 -0
  159. package/src/motion3/fix/turn.js +51 -0
  160. package/src/motion3/follow.js +39 -0
  161. package/src/motion3/free-motion.js +173 -0
  162. package/src/motion3/grip-choice.js +130 -0
  163. package/src/motion3/hold-plan.js +157 -0
  164. package/src/motion3/json-patch.js +77 -0
  165. package/src/motion3/key-aim.js +105 -0
  166. package/src/motion3/key-edit.js +82 -0
  167. package/src/motion3/lenient.js +127 -0
  168. package/src/motion3/object-keys.js +236 -0
  169. package/src/motion3/object-pose.js +182 -0
  170. package/src/motion3/object-tracks.js +12 -0
  171. package/src/motion3/pipeline.js +102 -0
  172. package/src/motion3/playback.js +51 -0
  173. package/src/motion3/probes.js +91 -0
  174. package/src/motion3/qa/body-checks.js +34 -0
  175. package/src/motion3/qa/check-names.js +14 -0
  176. package/src/motion3/qa/checks.js +121 -0
  177. package/src/motion3/qa/contact-checks.js +51 -0
  178. package/src/motion3/qa/explain.js +80 -0
  179. package/src/motion3/qa/fmt.js +5 -0
  180. package/src/motion3/qa/foot-checks.js +72 -0
  181. package/src/motion3/qa/geometry.js +77 -0
  182. package/src/motion3/qa/goals.js +191 -0
  183. package/src/motion3/qa/ground.js +60 -0
  184. package/src/motion3/qa/hints-contact.js +179 -0
  185. package/src/motion3/qa/hints-goal.js +160 -0
  186. package/src/motion3/qa/hints-knee.js +24 -0
  187. package/src/motion3/qa/hints-motion.js +209 -0
  188. package/src/motion3/qa/hints-object.js +91 -0
  189. package/src/motion3/qa/index.js +42 -0
  190. package/src/motion3/qa/key-checks.js +82 -0
  191. package/src/motion3/qa/near.js +96 -0
  192. package/src/motion3/qa/object-checks.js +135 -0
  193. package/src/motion3/qa/patch.js +161 -0
  194. package/src/motion3/qa/severity.js +38 -0
  195. package/src/motion3/qa/spec-checks.js +65 -0
  196. package/src/motion3/qa/style.js +218 -0
  197. package/src/motion3/qa/wrist-checks.js +69 -0
  198. package/src/motion3/qa/wrist-obs.js +60 -0
  199. package/src/motion3/refs.js +120 -0
  200. package/src/motion3/repair-tune.js +121 -0
  201. package/src/motion3/repair.js +83 -0
  202. package/src/motion3/report.js +67 -0
  203. package/src/motion3/rigs/index.js +14 -0
  204. package/src/motion3/rigs/x-bot-tpose-body.json +1 -0
  205. package/src/motion3/rigs/x-bot-tpose.json +1 -0
  206. package/src/motion3/rigs/y-bot-tpose-body.json +1 -0
  207. package/src/motion3/rigs/y-bot-tpose.json +1 -0
  208. package/src/motion3/scene.js +60 -0
  209. package/src/motion3/schema/brief.js +89 -0
  210. package/src/motion3/schema/fields.js +44 -0
  211. package/src/motion3/schema/spec-examples.js +46 -0
  212. package/src/motion3/schema/topics.js +111 -0
  213. package/src/motion3/schema/types.js +147 -0
  214. package/src/motion3/search.js +117 -0
  215. package/src/motion3/second-hand.js +112 -0
  216. package/src/motion3/skills/basic.js +82 -0
  217. package/src/motion3/skills/book.js +76 -0
  218. package/src/motion3/skills/car.js +145 -0
  219. package/src/motion3/skills/frames-auto.js +29 -0
  220. package/src/motion3/skills/index.js +83 -0
  221. package/src/motion3/skills/info.js +65 -0
  222. package/src/motion3/skills/smoke-kit.js +109 -0
  223. package/src/motion3/skills/smoke.js +160 -0
  224. package/src/motion3/spec-store.js +59 -0
  225. package/src/motion3/take-edit.js +155 -0
  226. package/src/motion3/take.js +63 -0
  227. package/src/motion3/timeline.js +127 -0
  228. package/src/motion3/views.js +121 -0
  229. package/src/motion3/walk-plan.js +119 -0
  230. package/src/object-catalog.jsx +7 -1
  231. package/src/object-colors.js +98 -0
  232. package/src/object-gizmo-parts.js +115 -0
  233. package/src/object-gizmo.jsx +37 -122
  234. package/src/object-glow.js +139 -0
  235. package/src/object-hidden.js +33 -0
  236. package/src/object-look.js +196 -0
  237. package/src/object-size.js +10 -0
  238. package/src/object-travel.js +71 -6
  239. package/src/panels/CameraPanel.jsx +7 -7
  240. package/src/panels/HdrEnvironmentControls.jsx +59 -0
  241. package/src/panels/LightPanel.jsx +25 -4
  242. package/src/panels/ObjectLookPanel.jsx +88 -0
  243. package/src/panels/PoseImagePanel.jsx +1 -1
  244. package/src/panels/PosePanel.jsx +2 -17
  245. package/src/panels/RigControlPanel.jsx +5 -53
  246. package/src/panels/SubjectBox.jsx +4 -1
  247. package/src/panels/details.css +14 -0
  248. package/src/posestudio.jsx +4 -27
  249. package/src/project-browser.jsx +3 -4
  250. package/src/project-resources.js +18 -31
  251. package/src/project.js +13 -104
  252. package/src/props.jsx +25 -280
  253. package/src/rail-dot-drag.js +23 -0
  254. package/src/render-passes.js +10 -0
  255. package/src/resource-status.jsx +1 -0
  256. package/src/result-modal.jsx +4 -19
  257. package/src/room.jsx +20 -12
  258. package/src/scene-assets.js +8 -6
  259. package/src/scene-light-place.js +72 -0
  260. package/src/scene-lights.jsx +148 -0
  261. package/src/scene-objects.js +31 -139
  262. package/src/scenes.js +15 -105
  263. package/src/semantic-edit.js +2 -5
  264. package/src/set-pieces.jsx +183 -0
  265. package/src/settings-menu.jsx +3 -11
  266. package/src/shell/BottomDock.jsx +11 -53
  267. package/src/shell/CompositionGuideButton.jsx +20 -0
  268. package/src/shell/DetailsSlot.jsx +16 -200
  269. package/src/shell/GhostsMenuGroup.jsx +74 -0
  270. package/src/shell/LibrarySlot.jsx +1 -3
  271. package/src/shell/MenuBar.jsx +3 -3
  272. package/src/shell/PreferencesDialog.jsx +15 -264
  273. package/src/shell/PreferencesSlot.jsx +2 -8
  274. package/src/shell/ShotPreviewHeader.jsx +17 -0
  275. package/src/shell/TopBar.jsx +5 -83
  276. package/src/shell/ViewportToolbar.jsx +23 -93
  277. package/src/shell/agent-glass.css +19 -6
  278. package/src/shell/details-selection.js +42 -0
  279. package/src/shell/ghosts-menu.css +22 -0
  280. package/src/shell/glass-regions.css +29 -3
  281. package/src/shell/preferences.css +6 -1
  282. package/src/shell/viewport.css +12 -0
  283. package/src/shot-authoring.js +4 -7
  284. package/src/soft-shadows.js +37 -0
  285. package/src/stage-attach-frames.js +12 -12
  286. package/src/stage-canvas-rigs.jsx +3 -3
  287. package/src/stage-capture.jsx +42 -8
  288. package/src/stage-characters.jsx +22 -216
  289. package/src/stage-editor-gizmos.jsx +6 -6
  290. package/src/stage-environment.js +93 -0
  291. package/src/stage-hdr-builtins.js +107 -0
  292. package/src/stage-hdr-image.js +116 -0
  293. package/src/stage-hdr-library.js +70 -0
  294. package/src/stage-hdr-map.js +94 -0
  295. package/src/stage-hdr.js +130 -0
  296. package/src/stage-lighting.js +149 -0
  297. package/src/stage-occlusion.js +210 -0
  298. package/src/stage-sky.jsx +159 -0
  299. package/src/stage-startup-defaults.js +5 -9
  300. package/src/studio-actions.js +12 -14
  301. package/src/studio-agent-commands.js +46 -16
  302. package/src/studio-agent-motion.js +9 -9
  303. package/src/studio-agent-protocol.js +46 -73
  304. package/src/studio-app-binding.js +29 -45
  305. package/src/studio-asset-list.js +17 -0
  306. package/src/studio-contact-sheet.js +37 -5
  307. package/src/studio-elements.js +27 -7
  308. package/src/studio-entity-bounds.js +18 -0
  309. package/src/studio-error-codes.js +8 -0
  310. package/src/studio-export-framing.js +152 -0
  311. package/src/studio-lighting-check.js +103 -0
  312. package/src/studio-motion-visual.js +150 -0
  313. package/src/studio-schema-errors.js +80 -0
  314. package/src/studio-support-gap.js +74 -0
  315. package/src/styles.css +1 -19
  316. package/src/timeline-defaults.js +5 -0
  317. package/src/timeline-extent.js +8 -20
  318. package/src/tutorial-walk.js +17 -0
  319. package/src/ui-scrub.js +1 -1
  320. package/src/use-shot-camera-tracks.js +12 -0
  321. package/src/view-ghosts.js +97 -0
  322. package/src/view-ghosts.jsx +49 -0
  323. package/src/workflow/AgentPanel.jsx +47 -166
  324. package/src/workflow/AgentProgressCard.jsx +107 -0
  325. package/src/workflow/AgentReplyText.jsx +37 -0
  326. package/src/workflow/AgentTurnSteps.jsx +84 -0
  327. package/src/workflow/LoginPanel.jsx +140 -0
  328. package/src/workflow/WorkflowBuilder.jsx +7 -23
  329. package/src/workflow/agent-client.js +54 -51
  330. package/src/workflow/agent-panel.css +71 -1
  331. package/src/workflow/agent-progress-card.css +167 -0
  332. package/src/workflow/agent-progress-view.js +47 -0
  333. package/src/workflow/agent-turn-steps.css +113 -0
  334. package/src/workflow/agent-turn.js +191 -0
  335. package/src/workflow/login-client.js +34 -0
  336. package/src/workflow/motion-input.js +18 -31
  337. package/src/workflow/node-schema.js +1 -1
  338. package/src/workflow/reply-markdown.js +69 -0
  339. package/src/workflow/scene-asset-sync.js +8 -8
  340. package/tools/agent-session-stats.mjs +61 -0
  341. package/tools/dev-full.mjs +5 -56
  342. package/tools/eslint-baseline.json +12 -13
  343. package/tools/headless-studio.mjs +85 -0
  344. package/tools/knip-baseline.json +0 -179
  345. package/tools/loc-baseline.json +33 -83
  346. package/tools/motion3/extract-body.mjs +103 -0
  347. package/tools/motion3/extract-rig.mjs +85 -0
  348. package/tools/process-supervisor.mjs +0 -88
  349. package/tools/run-tests.mjs +9 -124
  350. package/bin/agent/motion-runtime.mjs +0 -76
  351. package/dist/ardy/cskel27-rest.json +0 -557
  352. package/dist/assets/analytics-B41FmPYI.js +0 -1
  353. package/dist/assets/app-Do-Og5Pu.css +0 -1
  354. package/dist/assets/app-DwP-tl2x.js +0 -4920
  355. package/dist/assets/config-Bx7pYsD9.js +0 -1
  356. package/dist/assets/config-DeoFTYzb.css +0 -1
  357. package/dist/assets/demo-Dt0qZfS7.js +0 -1
  358. package/dist/assets/scene-asset-sync-BCWDJB1d.js +0 -19
  359. package/dist/assets/scene-asset-sync-DjJ_cJ2R.css +0 -1
  360. package/dist/assets/ticket-jqMhqL88.js +0 -1
  361. package/dist/assets/workflow-Br-K7Srs.js +0 -179
  362. package/dist/demo/walk-then-stop.npz +0 -0
  363. package/mcp/ardy-prompts.mjs +0 -218
  364. package/src/ardy/auto-fix-panel.css +0 -108
  365. package/src/ardy/auto-physics.js +0 -1194
  366. package/src/ardy/client.js +0 -153
  367. package/src/ardy/convert.js +0 -313
  368. package/src/ardy/cskel27-neutral.js +0 -39
  369. package/src/ardy/cskel27.js +0 -77
  370. package/src/ardy/export.js +0 -165
  371. package/src/ardy/fix-collisions.js +0 -2257
  372. package/src/ardy/foot-lock.js +0 -230
  373. package/src/ardy/motion-calibration.js +0 -118
  374. package/src/ardy/motion-edit.js +0 -213
  375. package/src/ardy/motion-url.js +0 -55
  376. package/src/ardy/mp4-duration.js +0 -102
  377. package/src/ardy/npz.js +0 -595
  378. package/src/ardy/physics-panel.css +0 -21
  379. package/src/ardy/physics-panel.jsx +0 -89
  380. package/src/ardy/physics-refine.js +0 -111
  381. package/src/ardy/physics-review.js +0 -567
  382. package/src/ardy/physics-temporal.js +0 -54
  383. package/src/ardy/platform-fit-panel.css +0 -64
  384. package/src/ardy/platform-fit-panel.jsx +0 -41
  385. package/src/ardy/platform-fit.js +0 -326
  386. package/src/ardy/playback.js +0 -711
  387. package/src/ardy/pose-pin.js +0 -97
  388. package/src/ardy/prompt-clips.js +0 -30
  389. package/src/ardy/retime.js +0 -211
  390. package/src/ardy/root-drop.js +0 -369
  391. package/src/ardy/timeline-resize.js +0 -13
  392. package/src/ardy/to-cskel27.js +0 -225
  393. package/src/ardy/trim.js +0 -33
  394. package/src/ardy/waypoints.js +0 -369
  395. package/src/ardy-bridge-contract.js +0 -281
  396. package/src/domains/motion/motion-video-capture.js +0 -664
  397. package/src/i2v-motion-client.js +0 -77
  398. package/src/i2v-motion-studio.jsx +0 -180
  399. package/src/line-edit.js +0 -1348
  400. package/src/motion/generation.js +0 -105
  401. package/src/motion-readiness-ui.jsx +0 -67
  402. package/src/motion-readiness.js +0 -40
  403. package/src/motion-resources.js +0 -177
  404. package/src/motion-store.js +0 -65
  405. package/src/motion-trail.js +0 -601
  406. package/src/multimodel-ingest.js +0 -394
  407. package/src/panels/PromptBlocksPanel.jsx +0 -444
  408. package/src/panels/VideoCapturePanel.jsx +0 -181
  409. package/src/pose-extract/browser.js +0 -42
  410. package/src/pose-extract/detector.js +0 -100
  411. package/src/pose-extract/fit.js +0 -227
  412. package/src/pose-extract/image-frame.js +0 -51
  413. package/src/pose-extract/index.js +0 -58
  414. package/src/pose-extract/keyframes.js +0 -74
  415. package/src/pose-extract/landmarks.js +0 -162
  416. package/src/pose-extract/mirror.js +0 -153
  417. package/src/pose-extract/take.js +0 -131
  418. package/src/pose-extract/video-frames.js +0 -91
  419. package/src/take-recipe.js +0 -165
  420. package/src/trail-key-conflicts.js +0 -39
  421. package/src/trail-pick.js +0 -59
  422. package/src/use-i2v-capture-modal.js +0 -15
  423. package/tools/ardy/BRIDGE.md +0 -37
  424. package/tools/ardy/GVHMR-NPZ.md +0 -68
  425. package/tools/ardy/README.md +0 -124
  426. package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
  427. package/tools/ardy/artifacts.mjs +0 -34
  428. package/tools/ardy/bridge.mjs +0 -1732
  429. package/tools/ardy/bvh-cskel27.mjs +0 -1265
  430. package/tools/ardy/cclay_constrained_generate.py +0 -561
  431. package/tools/ardy/cclay_gvhmr_worker.py +0 -264
  432. package/tools/ardy/cclay_motion_edit.py +0 -510
  433. package/tools/ardy/cclay_sequence_generate.py +0 -705
  434. package/tools/ardy/cclay_worker.py +0 -304
  435. package/tools/ardy/dump-npz.py +0 -205
  436. package/tools/ardy/extract-rest.mjs +0 -299
  437. package/tools/ardy/extract.mjs +0 -507
  438. package/tools/ardy/footage.mjs +0 -467
  439. package/tools/ardy/gvhmr-floor.mjs +0 -102
  440. package/tools/ardy/gvhmr-to-cskel27.mjs +0 -21
  441. package/tools/ardy/gvhmr_fastpath.py +0 -168
  442. package/tools/ardy/gvhmr_trajectory.py +0 -240
  443. package/tools/ardy/merge-text-encoder.py +0 -160
  444. package/tools/ardy/mocap-metrics.mjs +0 -200
  445. package/tools/ardy/mocap-quality-gate.mjs +0 -110
  446. package/tools/ardy/motion-stabilize.mjs +0 -348
  447. package/tools/ardy/npz.mjs +0 -414
  448. package/tools/ardy/pose-to-npz.mjs +0 -106
  449. package/tools/ardy/prompt-limits.mjs +0 -5
  450. package/tools/ardy/run-edit-on-box.sh +0 -73
  451. package/tools/ardy/run-local.mjs +0 -233
  452. package/tools/ardy/run-on-box.sh +0 -593
  453. package/tools/ardy/run-sequence-on-box.sh +0 -177
  454. package/tools/ardy/runners/gvhmr-worker.mjs +0 -206
  455. package/tools/ardy/runners/index.mjs +0 -18
  456. package/tools/ardy/runners/local.mjs +0 -455
  457. package/tools/ardy/runners/proc.mjs +0 -125
  458. package/tools/ardy/runners/remote.mjs +0 -252
  459. package/tools/ardy/setup-local.mjs +0 -189
  460. package/tools/ardy/setup-text-encoder-on-box.sh +0 -93
  461. package/tools/ardy/setup-text-encoder.py +0 -306
  462. package/tools/ardy/smpl-cskel27.mjs +0 -178
  463. package/tools/ardy/visual-qa.mjs +0 -196
  464. package/tools/ardy/vq-car.mjs +0 -52
  465. package/tools/bench/EXP3.md +0 -61
  466. package/tools/bench/cclay_bench_extract_incam.py +0 -125
  467. package/tools/bench/cclay_bench_extract_obs.py +0 -204
  468. package/tools/bench/cclay_bench_runner.py +0 -42
  469. package/tools/bench/cube-contact.mjs +0 -162
  470. package/tools/bench/exp3.mjs +0 -152
  471. package/tools/bench/extract-bench-lib.mjs +0 -216
  472. package/tools/bench/extract-bench.mjs +0 -302
  473. package/tools/bench/fal-generate.mjs +0 -88
  474. package/tools/bench/fit/README.md +0 -189
  475. package/tools/bench/fit/camera.mjs +0 -59
  476. package/tools/bench/fit/contact.mjs +0 -419
  477. package/tools/bench/fit/footlock.mjs +0 -234
  478. package/tools/bench/fit/motion.mjs +0 -85
  479. package/tools/bench/fit/pin.mjs +0 -27
  480. package/tools/bench/fit/remote.mjs +0 -105
  481. package/tools/bench/fit-bench.mjs +0 -126
  482. package/tools/bench/fit-sanity.mjs +0 -45
  483. package/tools/bench/metrics.mjs +0 -313
  484. package/tools/bench/obs/depth.mjs +0 -258
  485. package/tools/bench/obs/extrinsics.mjs +0 -175
  486. package/tools/bench/obs/fit_mannequin_betas.py +0 -340
  487. package/tools/bench/obs/ground.mjs +0 -294
  488. package/tools/bench/obs/heading.mjs +0 -151
  489. package/tools/bench/obs/ladder.mjs +0 -555
  490. package/tools/bench/obs/mannequin-betas.json +0 -124
  491. package/tools/bench/obs/remote.mjs +0 -83
  492. package/tools/bench/obs/rest_joints.py +0 -52
  493. package/tools/bench/obs/ybot-targets.mjs +0 -49
  494. package/tools/bench/obs-bench.mjs +0 -520
  495. package/tools/bench/score.mjs +0 -343
  496. package/tools/bench/summarize.mjs +0 -86
  497. package/tools/demo-worker/README.md +0 -137
  498. package/tools/demo-worker/api-client.mjs +0 -233
  499. package/tools/demo-worker/generate.mjs +0 -301
  500. package/tools/demo-worker/index.mjs +0 -466
  501. package/tools/demo-worker/sign-core.mjs +0 -112
  502. package/tools/demo-worker/sign.mjs +0 -161
  503. package/tools/gt-render/browser.mjs +0 -159
  504. package/tools/gt-render/camera-math.mjs +0 -276
  505. package/tools/gt-render/page.mjs +0 -267
  506. package/tools/gt-render/render.mjs +0 -514
  507. package/tools/gt-render/scene-box.mjs +0 -31
  508. package/tools/gt-render/take-transform.mjs +0 -87
  509. package/tools/kimodo/compare-seam.mjs +0 -108
  510. package/tools/kimodo/constraints.mjs +0 -244
  511. package/tools/kimodo/edit.mjs +0 -259
  512. package/tools/kimodo/effector-constraints.mjs +0 -200
  513. package/tools/kimodo/generate.mjs +0 -646
  514. package/tools/kimodo/local-output.mjs +0 -265
  515. package/tools/kimodo/measure-pose.mjs +0 -65
  516. package/tools/kimodo/measure-preserve.mjs +0 -899
  517. package/tools/kimodo/measure-waypoints.mjs +0 -55
  518. package/tools/kimodo/mlx-generate.py +0 -111
  519. package/tools/kimodo/pose-constraints.mjs +0 -248
  520. package/tools/kimodo/preserve-mask.mjs +0 -543
  521. package/tools/kimodo/read-npz.mjs +0 -189
  522. package/tools/kimodo/run-edit-on-box.mjs +0 -155
  523. package/tools/kimodo/run-sequence-on-box.mjs +0 -178
  524. package/tools/kimodo/runner.mjs +0 -284
  525. package/tools/kimodo/seam-batch.mjs +0 -77
  526. package/tools/kimodo/setup-local.mjs +0 -30
  527. package/tools/kimodo/setup-on-box.sh +0 -339
  528. package/tools/kimodo/soma77-to-cskel27.mjs +0 -305
  529. package/tools/morphgs/assets/gen_truth.py +0 -39
  530. package/tools/morphgs/assets/mesh_ori_rig.txt +0 -27
  531. package/tools/morphgs/assets/playback-check.mjs +0 -30
  532. package/tools/morphgs/demo-gate.sh +0 -26
  533. package/tools/morphgs/fbx2morphgs.mjs +0 -68
  534. package/tools/morphgs/morphgs-to-cskel27.mjs +0 -105
  535. package/tools/morphgs/patches/preprocess_src-none-mode.patch +0 -76
  536. package/tools/morphgs/setup-on-cluster.sh +0 -126
  537. package/tools/projflow/driver.py +0 -1069
  538. package/tools/projflow/generate.mjs +0 -805
  539. package/tools/projflow/hml22-to-cskel27.mjs +0 -735
  540. package/tools/projflow/line-edit-job.mjs +0 -547
  541. package/tools/projflow/replay.mjs +0 -452
  542. package/tools/projflow/runner.mjs +0 -269
  543. package/tools/projflow/service.mjs +0 -742
  544. package/tools/track/DESIGN.md +0 -513
  545. package/tools/track/backfill-provenance.mjs +0 -116
  546. package/tools/track/budget.mjs +0 -8
  547. package/tools/track/check-rig.mjs +0 -209
  548. package/tools/track/diagnostics.schema.json +0 -60
  549. package/tools/track/export-rig.mjs +0 -256
  550. package/tools/track/fallback.mjs +0 -7
  551. package/tools/track/fk-parity-fixture.mjs +0 -161
  552. package/tools/track/gate.mjs +0 -349
  553. package/tools/track/masks.mjs +0 -318
  554. package/tools/track/metrics.mjs +0 -201
  555. package/tools/track/publish-obs.mjs +0 -108
  556. package/tools/track/py/check_env.py +0 -61
  557. package/tools/track/py/eval_lr.py +0 -229
  558. package/tools/track/py/lr_viterbi.py +0 -294
  559. package/tools/track/py/masks.py +0 -405
  560. package/tools/track/py/objective.py +0 -382
  561. package/tools/track/py/rig.py +0 -508
  562. package/tools/track/py/scene.py +0 -326
  563. package/tools/track/py/test_joint_indices.py +0 -123
  564. package/tools/track/py/test_lr_viterbi.py +0 -195
  565. package/tools/track/py/test_masks.py +0 -229
  566. package/tools/track/py/test_rig.py +0 -288
  567. package/tools/track/py/test_scene.py +0 -354
  568. package/tools/track/py/test_track.py +0 -328
  569. package/tools/track/py/track.py +0 -411
  570. package/tools/track/remote.mjs +0 -140
  571. package/tools/track/rig-dump.mjs +0 -227
  572. package/tools/track/run-box-tests.mjs +0 -14
  573. package/tools/track/run-box.mjs +0 -168
  574. package/tools/track/setup-box.sh +0 -59
  575. package/tools/track/study-2d.mjs +0 -728
  576. /package/src/{ardy → motion}/collision-blockers.js +0 -0
  577. /package/src/{ardy → motion}/ground.js +0 -0
  578. /package/src/{ardy → motion}/ik-drag.js +0 -0
  579. /package/src/{ardy → motion}/ik-key-json.js +0 -0
  580. /package/src/{ardy → motion}/ik.js +0 -0
  581. /package/src/{ardy → motion}/physics-surface.js +0 -0
  582. /package/src/{ardy → motion}/playback-clock.js +0 -0
  583. /package/src/{ardy → motion}/range-pin.js +0 -0
@@ -0,0 +1,275 @@
1
+ // The walk's hips, heel rise and swings (walk.js), hips first, the way a person walks:
2
+ // - fitPelvis: the hips' height, as high as the legs holding them allow with the knee a person keeps through
3
+ // stance. A leg reaching for the floor straightens in terminal swing (REACH_IN periods before its strike) and
4
+ // strikes a little bent; it gives as the weight comes on (loading, LOAD_SPAN periods) and is nearly straight at
5
+ // mid-stance. Once the hips pass over a planted foot it no longer holds them down: its heel rises instead
6
+ // (fitTrail). The height runs on a C2 cubic spline (no kink) through a trough at each heel strike and the highest
7
+ // point the limit allows a quarter to a half of the way to the next strike (a person's hips top out as they pass
8
+ // over the foot, then fall with the flat foot's reach); no half-step bends it harder than CURVE, and an extremum
9
+ // comes down until the whole curve stays under the limit. Across the floor the hips
10
+ // keep the walk's path (walk.js keys), blurred XZ_BLUR frames so their speed has no kink.
11
+ // - fitTrail: from the hips passing over a planted foot to its lift-off the heel rises, about the ball, just as
12
+ // far as the leg needs to reach with the stance knee and then the push-off knee (from walk.js's heel-off frame
13
+ // to KNEE_TOEOFF at lift-off), never lowered, never faster than PITCH_RATE a frame and never past PITCH_MAX
14
+ // (beyond about 60 deg the toes sit behind the ankle and QA reads a bent knee as bending backward); out of reach
15
+ // even then, the toe comes off the floor, the foot drawn toward the hip, as a person's does once the other foot
16
+ // is down.
17
+ // - fitSwing: each swing on a clamped cubic spline from that lift-off, through walk.js's swing keys, into the strike;
18
+ // the foot's pitch follows the shank on the way.
19
+ // The knee schedule eases with the walk's pace (brisk): a stroll strikes, loads and pushes off with straighter knees.
20
+ import { bakeChannels } from "./channels.js";
21
+ import { add, sub, scale, len, dot, apply, rx, ry, rz } from "./math.js";
22
+
23
+ // knee flexion (deg): at the strike, the loading peak, mid-stance, and at lift-off (TRAIL_POW shapes the push-off:
24
+ // slow while the other foot is in the air, fast once it lands); a quick walk (period down to FAST frames from SLOW)
25
+ // bends them FAST_MORE further, as a person's knees give more the faster they walk
26
+ const KNEE_STRIKE = 18, KNEE_LOAD = 22, KNEE_MID = 7, KNEE_TOEOFF = 45, TRAIL_POW = 1, LOAD_SPAN = 0.4, PITCH_MAX = 50, PITCH_RATE = 15;
27
+ const SLOW = 13, FAST = 9, FAST_MORE = { strike: 4, load: 4, mid: 3, toeOff: 5 };
28
+ // a slow walk keeps the knees straighter (a person strolling strikes and loads nearly straight-legged): the schedule
29
+ // eases from STROLL at a pace of PACE[0] (walk.js's pace: 1 = 0.5 m per 20 frames) up to the one above at PACE[1]
30
+ const STROLL = { strike: 2, load: 14, mid: 7, toeOff: 48, pow: 1.8 }, PACE = [1, 2.2];
31
+ /** 0 at a stroll's pace, 1 at a brisk walk's, smooth between. */
32
+ export const brisk = pace => { const t = Math.min(1, Math.max(0, ((pace ?? PACE[1]) - PACE[0]) / (PACE[1] - PACE[0]))); return t * t * (3 - 2 * t); };
33
+ const SOFT = 0.004, EASE = 8, BELOW_STAND = 0.008, REACH_IN = 0.35, MARGIN = 0.001, XZ_BLUR = 2;
34
+ // a strike's trough is the lowest limit from 2 frames before it to TROUGH_SPAN periods after (its loading)
35
+ const TROUGH_SPAN = 0.25, PEAK = [0.25, 0.5], CURVE = 0.008, smoother = t => t * t * t * (t * (6 * t - 15) + 10);
36
+ // the keys fitSwing writes, so a second pass reads walk.js's swing keys only
37
+ const SWUNG = new WeakSet();
38
+
39
+ /**
40
+ * Appends a root key per frame between f0 and end to `out` (the walk's keys). ground: per foot, its spans on
41
+ * the ground, { a, b, strike, heel }: from a (a heel strike, or f0) to b (its lift-off), heel the frame its
42
+ * heel starts up before that lift; start the src of keys before the first step. The keys keep the root's path across the
43
+ * floor; only its height changes.
44
+ */
45
+ /** A foot pitched `deg` about a point on the ground (its heel or ball), heading yw: where the ankle goes. */
46
+ export const pivot = (p, yw, deg, from) => add(p, apply(ry(yw), apply(rx(deg), apply(ry(-yw), sub(from, p)))));
47
+
48
+ /** The leg's reach at a knee flexion, the hip joints from the hips, and the knee schedule of a walk of this period. */
49
+ function legOf(sk, period, pace) {
50
+ const I = sk.index, P = sk.restWorldPos;
51
+ const a = len(sub(P[I.LeftLeg], P[I.LeftUpLeg])), b = len(sub(P[I.LeftFoot], P[I.LeftLeg]));
52
+ const q = Math.min(1, Math.max(0, (SLOW - period) / (SLOW - FAST))), u = brisk(pace), mix = (s, k) => s + (k - s) * u;
53
+ const strike = mix(STROLL.strike, KNEE_STRIKE) + q * FAST_MORE.strike, load = mix(STROLL.load, KNEE_LOAD) + q * FAST_MORE.load;
54
+ const mid = mix(STROLL.mid, KNEE_MID) + q * FAST_MORE.mid, toeOff = mix(STROLL.toeOff, KNEE_TOEOFF) + q * FAST_MORE.toeOff;
55
+ return {
56
+ reach: deg => Math.sqrt(a * a + b * b + 2 * a * b * Math.cos((deg * Math.PI) / 180)),
57
+ hipOff: { l: sub(P[I.LeftUpLeg], P[I.Hips]), r: sub(P[I.RightUpLeg], P[I.Hips]) },
58
+ strike, load, mid, trail: u => mid + (toeOff - mid) * Math.min(1, Math.max(0, u)) ** mix(STROLL.pow, TRAIL_POW),
59
+ };
60
+ }
61
+ const hipAt = (ch, f, off) => add(ch.root[f], apply(ry(ch.yaw?.[f] ?? 0), apply(rz(ch.roll?.[f] ?? 0), off)));
62
+
63
+ export function fitPelvis(sk, out, { f0, end, period, pace, ground, start }) {
64
+ const I = sk.index, P = sk.restWorldPos;
65
+ const { reach, hipOff, strike, load, mid, trail } = legOf(sk, period, pace);
66
+ const lead = Math.max(2, Math.round(REACH_IN * period));
67
+ const knee = (g, f) => {
68
+ if (f < g.a) return strike;
69
+ if (g.heel !== undefined && f >= g.heel) return trail((f - g.heel) / Math.max(1, g.b - g.heel));
70
+ // loading: from the strike's knee up to the loading peak half way through LOAD_SPAN, then down to mid-stance's
71
+ const u = (f - g.a) / period / LOAD_SPAN;
72
+ return g.strike && u < 1 ? (u < 0.5 ? strike : mid) + (load - (u < 0.5 ? strike : mid)) * Math.sin(Math.PI * u) : mid;
73
+ };
74
+ const ch = bakeChannels(out, end + 1), top = P[I.Hips][1] - BELOW_STAND;
75
+ // the path across the floor, blurred twice (ends held), so the forward speed has no kink
76
+ let xz = ch.root.map(r => [r[0], r[2]]);
77
+ for (let pass = 0; pass < 2; pass++) xz = xz.map((p, f) => {
78
+ if (f <= f0 || f >= end) return p;
79
+ let sx = 0, sz = 0, w = 0;
80
+ for (let d = -3 * XZ_BLUR; d <= 3 * XZ_BLUR; d++) { const g = Math.min(end, Math.max(f0, f + d)), k = Math.exp(-(d * d) / (2 * XZ_BLUR * XZ_BLUR)); sx += k * xz[g][0]; sz += k * xz[g][1]; w += k; }
81
+ const t = Math.min(1, (f - f0) / EASE, (end - f) / EASE), e = t * t * (3 - 2 * t);
82
+ return [p[0] + (sx / w - p[0]) * e, p[1] + (sz / w - p[1]) * e];
83
+ });
84
+ // the highest the hips may be at frame f, eased in from the pose before the walk and out into the stand after it
85
+ const limit = f => {
86
+ const root = [xz[f][0], ch.root[f][1], xz[f][1]], yaw = ch.yaw?.[f] ?? 0, roll = ch.roll?.[f] ?? 0, ys = [];
87
+ for (const k of ["l", "r"]) {
88
+ const g = ground[k].find(x => f >= x.a - (x.strike ? lead : 0) && f <= x.b);
89
+ // once the hips pass over a planted foot it no longer holds them down: its heel rises as far as it must and
90
+ // past PITCH_MAX its toe leaves the floor (fitTrail)
91
+ if (!g || (g.free !== undefined && f >= g.free)) continue;
92
+ const ankle = ch[`foot_${k}`][f], off = apply(ry(yaw), apply(rz(roll), hipOff[k]));
93
+ const d = reach(knee(g, f)), dh = Math.hypot(root[0] + off[0] - ankle[0], root[2] + off[2] - ankle[2]);
94
+ if (dh < d) ys.push(ankle[1] + Math.sqrt(d * d - dh * dh) - off[1]);
95
+ }
96
+ if (!ys.length) return top;
97
+ const m = Math.min(...ys), fit = Math.min(top, m - SOFT * Math.log(ys.reduce((s, y) => s + Math.exp(-(y - m) / SOFT), 0))) - MARGIN;
98
+ const t = Math.min(1, (f - f0) / EASE, (end - f) / EASE), w = t * t * (3 - 2 * t);
99
+ return root[1] + (fit - root[1]) * w;
100
+ };
101
+ // the frame the hips pass over each planted foot (its ankle along the foot's heading)
102
+ for (const k of ["l", "r"]) for (const g of ground[k]) {
103
+ if (g.heel === undefined || !g.flat) continue;
104
+ const fw = apply(ry(g.yw), [0, 0, 1]);
105
+ let f = Math.max(f0 + 1, g.a);
106
+ while (f < g.heel && (xz[f][0] - g.flat[0]) * fw[0] + (xz[f][1] - g.flat[2]) * fw[2] < 0) f++;
107
+ g.free = f;
108
+ }
109
+ const low = [];
110
+ for (let f = f0; f <= end; f++) low[f] = limit(f);
111
+ // extrema: the pose before the walk, a trough over each strike's loading, the highest point between, the stand after
112
+ const loadSpan = Math.max(2, Math.round(TROUGH_SPAN * period)), lowest = (a, b) => { let m = a; for (let f = a; f <= b; f++) if (low[f] < low[m]) m = f; return m; };
113
+ const strikes = [...new Set(["l", "r"].flatMap(k => ground[k].filter(g => g.strike && g.a > f0 + 2 && g.a < end - 2).map(g => g.a)))].sort((p, q) => p - q);
114
+ const knots = [{ f: f0, y: low[f0] }];
115
+ strikes.forEach((a, n) => {
116
+ const t = lowest(Math.max(knots.at(-1).f + 2, a - 2), Math.min(end - 2, a + loadSpan));
117
+ // the peak between two troughs: of the frames PEAK of the way between them, the one whose highest smootherstep rise
118
+ // from the last trough and fall to this one both stay under the limit is highest (smootherstep stands in for the
119
+ // spline here: it is the flatter of the two at each end)
120
+ if (n > 0) {
121
+ const A = knots.at(-1), B = { f: t, y: low[t] }, span = B.f - A.f;
122
+ let best = null;
123
+ for (let c = Math.ceil(A.f + PEAK[0] * span); c <= Math.floor(A.f + PEAK[1] * span); c++) {
124
+ let top = low[c];
125
+ for (let f = A.f + 1; f < c; f++) { const w = smoother((f - A.f) / (c - A.f)); if (w > 1e-6) top = Math.min(top, A.y + (low[f] - A.y) / w); }
126
+ for (let f = c + 1; f < B.f; f++) { const w = smoother((f - c) / (B.f - c)); if (w < 1 - 1e-6) top = Math.min(top, (low[f] - B.y * w) / (1 - w)); }
127
+ if (!best || top > best.y) best = { f: c, y: top };
128
+ }
129
+ if (best && best.y > Math.max(A.y, B.y)) knots.push(best);
130
+ }
131
+ if (t > knots.at(-1).f) knots.push({ f: t, y: low[t] });
132
+ });
133
+ knots.push({ f: end, y: low[end] });
134
+ // no half-step bends the hips harder than CURVE (a spline's turn is about 2 rise / frames^2): a peak
135
+ // too high for the frames it has to the next trough comes down
136
+ for (let n = 1; n + 1 < knots.length; n++) {
137
+ const K = knots[n], room = Math.min(...[knots[n - 1], knots[n + 1]].map(o => o.y + (CURVE * (K.f - o.f) ** 2) / 2));
138
+ if (K.y > Math.max(knots[n - 1].y, knots[n + 1].y)) K.y = Math.min(K.y, room);
139
+ }
140
+ // the spline through the knots; a segment that rises past the limit lowers its higher knot by the excess
141
+ let y = [];
142
+ for (let pass = 0; pass < 30; pass++) {
143
+ let worst = 0;
144
+ const S = spline(knots.map(K => K.f), knots.map(K => K.y), 0, 0);
145
+ for (let n = 0; n + 1 < knots.length; n++) {
146
+ const A = knots[n], B = knots[n + 1];
147
+ let ex = 0;
148
+ for (let f = A.f; f <= B.f; f++) { y[f] = S(f); ex = Math.max(ex, y[f] - low[f]); }
149
+ if (ex > 0.0003) { const hi = A.y > B.y && n > 0 ? A : B.f === end ? A : B; if (hi.f !== f0 && hi.f !== end) { hi.y -= ex; worst = Math.max(worst, ex); } }
150
+ }
151
+ if (!worst) break;
152
+ }
153
+ for (let f = f0; f <= end; f++) y[f] = Math.min(y[f], low[f]);
154
+ // each key names the step it falls in, so QA's causes and hints still point at that step of the walk
155
+ const steps = out.filter(k => / step \d+$/.test(k.src ?? "")).sort((p, q) => p.f - q.f);
156
+ let i = 0, src = start;
157
+ for (let f = f0 + 1; f < end; f++) {
158
+ while (i < steps.length && steps[i].f <= f) src = steps[i++].src;
159
+ out.push({ f, set: { root: [xz[f][0], y[f], xz[f][1]] }, src });
160
+ }
161
+ }
162
+
163
+ /**
164
+ * The planted foot's heel rise, once the hips are set (fitPelvis): each frame from the hips passing over it to lift-off
165
+ * pitches the foot about its ball just far enough that the leg reaches it with the knee the stance keeps (mid-stance's,
166
+ * then the push-off's from walk.js's heel-off frame), so the heel comes up when the leg needs it to; never lowering the
167
+ * heel again and never past PITCH_MAX. Its keys replace walk.js's scheduled rise on the same frames.
168
+ */
169
+ export function fitTrail(sk, out, { end, period, pace, ground }) {
170
+ const { reach, hipOff, trail } = legOf(sk, period, pace), ch = bakeChannels(out, end + 1);
171
+ const srcAt = new Map(out.map(x => [x.f, x.src]));
172
+ for (const k of ["l", "r"]) for (const g of ground[k]) {
173
+ if (g.free === undefined || !Number.isFinite(g.b) || g.b > end) continue;
174
+ // the pitch each frame needs (never lowering), then raised ahead of any rise faster than PITCH_RATE a frame
175
+ const need = [], frames = [];
176
+ let prev = 0;
177
+ for (let f = g.free; f <= g.b; f++) {
178
+ const hip = hipAt(ch, f, hipOff[k]), want = reach(trail((f - g.heel) / Math.max(1, g.b - g.heel)));
179
+ const far = p => len(sub(hip, pivot(g.ball, g.yw, p, g.flat))) - want;
180
+ let lo = prev, hi = PITCH_MAX;
181
+ if (far(lo) <= 0) hi = lo;
182
+ else if (far(hi) <= 0) for (let n = 0; n < 24; n++) { const m = (lo + hi) / 2; if (far(m) > 0) lo = m; else hi = m; }
183
+ prev = hi; need.push(hi); frames.push({ f, hip, want });
184
+ }
185
+ let pitch = need.map((_, i) => Math.max(...need.slice(i).map((p, j) => p - j * PITCH_RATE)));
186
+ // eased (never under the need): the rise starts and ends without a kink, which would jolt the knee
187
+ for (let pass = 0; pass < 2; pass++) pitch = pitch.map((p, i) => Math.max(need[i], ((pitch[i - 1] ?? 0) + p + (pitch[i + 1] ?? p)) / 3));
188
+ frames.forEach(({ f, hip, want }, i) => {
189
+ // out of reach even at PITCH_MAX: the toe comes off the floor, the foot drawn toward the hip to the push-off knee
190
+ let ankle = pivot(g.ball, g.yw, pitch[i], g.flat);
191
+ const gap = len(sub(hip, ankle)) - want;
192
+ if (gap > 0) ankle = add(ankle, scale(sub(hip, ankle), gap / len(sub(hip, ankle))));
193
+ out.push({ f, set: { [`foot_${k}`]: ankle, [`fpitch_${k}`]: pitch[i], [`fyaw_${k}`]: g.yw }, src: srcAt.get(f) });
194
+ });
195
+ }
196
+ }
197
+
198
+ /** A clamped cubic spline through (xs, ys) with end slopes d0, dn: C2 inside, so a path through it has no kinks. */
199
+ function spline(xs, ys, d0, dn) {
200
+ const n = xs.length - 1, h = xs.slice(1).map((x, i) => x - xs[i]), a = [], b = [], c = [], r = [];
201
+ for (let i = 0; i <= n; i++) {
202
+ if (i === 0) { a[i] = 0; b[i] = 2 * h[0]; c[i] = h[0]; r[i] = 6 * ((ys[1] - ys[0]) / h[0] - d0); }
203
+ else if (i === n) { a[i] = h[n - 1]; b[i] = 2 * h[n - 1]; c[i] = 0; r[i] = 6 * (dn - (ys[n] - ys[n - 1]) / h[n - 1]); }
204
+ else { a[i] = h[i - 1]; b[i] = 2 * (h[i - 1] + h[i]); c[i] = h[i]; r[i] = 6 * ((ys[i + 1] - ys[i]) / h[i] - (ys[i] - ys[i - 1]) / h[i - 1]); }
205
+ }
206
+ for (let i = 1; i <= n; i++) { const m = a[i] / b[i - 1]; b[i] -= m * c[i - 1]; r[i] -= m * r[i - 1]; }
207
+ const M = []; M[n] = r[n] / b[n];
208
+ for (let i = n - 1; i >= 0; i--) M[i] = (r[i] - c[i] * M[i + 1]) / b[i];
209
+ return x => {
210
+ let i = 0; while (i < n - 1 && x > xs[i + 1]) i++;
211
+ const t = x - xs[i], H = h[i], A = (xs[i + 1] - x) / H, B = t / H;
212
+ return A * ys[i] + B * ys[i + 1] + ((A ** 3 - A) * M[i] + (B ** 3 - B) * M[i + 1]) * H * H / 6;
213
+ };
214
+ }
215
+
216
+ // the swing foot keeps the ankle near neutral, as a person's does: its pitch follows the shank (SWING_DORSI deg toes-up
217
+ // of it), eased in from the toe-off's pitch over FOLLOW_IN of the swing and out into the strike's over FOLLOW_OUT, the
218
+ // ball held BALL_CLEAR m over its flat height away from the ends, the pitch no more than PITCH_MAX (a foot kept level would flap at the ankle as the shank swings)
219
+ const SWING_DORSI = 3, FOLLOW_IN = 0.35, FOLLOW_OUT = 0.2, BALL_CLEAR = 0.03, HEEL_FREE = 0.04, HEEL_LOW = 0.025, deg = r => (r * 180) / Math.PI;
220
+ const ease = x => { const t = Math.min(1, Math.max(0, x)); return t * t * (3 - 2 * t); };
221
+
222
+ /** The shank's tilt (deg, + with the ankle behind the knee) of a leg from hip H to ankle A, thigh a, shank b, knee forward fw. */
223
+ function shankTilt(H, A, fw, a, b) {
224
+ const d = sub(A, H), L = Math.min(a + b - 1e-6, Math.max(Math.abs(a - b) + 1e-6, len(d))), u = scale(d, 1 / len(d));
225
+ let v = sub(fw, scale(u, dot(fw, u)));
226
+ v = scale(v, 1 / (len(v) || 1));
227
+ const c = (a * a + L * L - b * b) / (2 * a * L), K = add(H, add(scale(u, a * c), scale(v, a * Math.sqrt(Math.max(0, 1 - c * c))))), sh = sub(A, K);
228
+ return deg(Math.atan2(-dot(sh, fw), -sh[1]));
229
+ }
230
+
231
+ /**
232
+ * Each swing (lift-off to the next strike of that foot), once the toe-off is set (fitTrail): the ankle runs on a
233
+ * clamped cubic spline from the lift-off, at the speed the push-off leaves it with, through walk.js's swing keys
234
+ * (the heel kicked up behind, mid-swing, over the strike) into the strike at the heel rocker's speed, keyed every
235
+ * frame: the knee and the foot move without the kinks per-channel keys leave where the path turns. Given the skeleton
236
+ * (the last pass, the hips set), each frame's foot pitch follows the shank too.
237
+ */
238
+ export function fitSwing(out, { end, ground, sk }) {
239
+ const ch = bakeChannels(out, end + 2), srcAt = new Map(out.map(x => [x.f, x.src]));
240
+ const I = sk?.index, P = sk?.restWorldPos, a = sk && len(sub(P[I.LeftLeg], P[I.LeftUpLeg])), b = sk && len(sub(P[I.LeftFoot], P[I.LeftLeg]));
241
+ for (const k of ["l", "r"]) {
242
+ const spans = ground[k], side = k === "l" ? "Left" : "Right";
243
+ const hipOff = sk && sub(P[I[`${side}UpLeg`]], P[I.Hips]), ball = sk && sub(P[I[`${side}ToeBase`]], P[I[`${side}Foot`]]), ballY = sk && P[I[`${side}ToeBase`]][1];
244
+ // QA's heel point from the ankle (qa/geometry.js solePoints)
245
+ const heel = sk && [0, 0.005 - P[I[`${side}Foot`]][1], -0.045];
246
+ for (let n = 0; n + 1 < spans.length; n++) {
247
+ const g = spans[n], h = spans[n + 1];
248
+ if (!Number.isFinite(g.b) || !h.strike || h.a > end || h.a - g.b < 3) continue;
249
+ const via = [...new Set(out.filter(x => !SWUNG.has(x) && x.f > g.b && x.f < h.a && x.set[`foot_${k}`]).map(x => x.f))].sort((p, q) => p - q);
250
+ const xs = [g.b, ...via, h.a], foot = ch[`foot_${k}`], at = f => foot[Math.max(0, Math.min(foot.length - 1, f))];
251
+ const v0 = sub(at(g.b), at(g.b - 1)), vn = sub(at(h.a + 1), at(h.a));
252
+ const axes = [0, 1, 2].map(i => spline(xs, xs.map(f => at(f)[i]), v0[i], vn[i]));
253
+ const yawAt = f => ch[`fyaw_${k}`]?.[f] ?? 0, pitchAt = f => ch[`fpitch_${k}`]?.[f] ?? 0;
254
+ const follow = (f, p) => shankTilt(hipAt(ch, f, hipOff), p, apply(ry(yawAt(f)), [0, 0, 1]), a, b) - SWING_DORSI;
255
+ const span = h.a - g.b, off0 = sk && pitchAt(g.b) - follow(g.b, at(g.b)), off1 = sk && pitchAt(h.a) - follow(h.a, at(h.a));
256
+ for (let f = g.b + 1; f < h.a; f++) {
257
+ const p = axes.map(e => e(f)), set = { [`foot_${k}`]: p };
258
+ if (sk) {
259
+ // never past PITCH_MAX (QA would read the bent knee as bending backward)
260
+ let pitch = Math.min(PITCH_MAX, follow(f, p) + off0 * (1 - ease((f - g.b) / (FOLLOW_IN * span))) + off1 * (1 - ease((h.a - f) / (FOLLOW_OUT * span))));
261
+ // the ball no lower than its flat height plus BALL_CLEAR (eased in and out over 3 frames at the ends)
262
+ const floor = ballY + BALL_CLEAR * ease(Math.min(f - g.b, h.a - f) / 3), ballAt = d => p[1] + apply(rx(d), ball)[1];
263
+ if (ballAt(pitch) < floor) { let lo = -30, hi = pitch; for (let it = 0; it < 20; it++) { const m = (lo + hi) / 2; if (ballAt(m) < floor) hi = m; else lo = m; } pitch = lo; }
264
+ // toes up lowers the heel: it comes no lower than HEEL_LOW (or the walk's keys, if they had it lower), so the strike
265
+ // lands when it did and the heel never skims the floor
266
+ const was = pitchAt(f), heelAt = d => p[1] + apply(rx(d), heel)[1], low = Math.min(HEEL_FREE, Math.max(heelAt(was), HEEL_LOW));
267
+ if (pitch < was && heelAt(pitch) < low) { let lo = pitch, hi = was; for (let it = 0; it < 20; it++) { const m = (lo + hi) / 2; if (heelAt(m) < low) lo = m; else hi = m; } pitch = hi; }
268
+ set[`fpitch_${k}`] = pitch;
269
+ }
270
+ const key = { f, set, src: srcAt.get(f) };
271
+ SWUNG.add(key); out.push(key);
272
+ }
273
+ }
274
+ }
275
+ }
@@ -0,0 +1,207 @@
1
+ // Generators expand into ordinary keys before baking. "walk" plans footsteps along a curving
2
+ // path from the pose the earlier keys leave at f0: weight shift over the stance foot, pelvis
3
+ // bob and twist, toe-off, arm swing, feet together at the end. The steps keep a natural rhythm
4
+ // (gait-rhythm.js): period, swing, step, lift and armSwing are the averages, vary: 0 makes every
5
+ // step exactly those.
6
+ //
7
+ // { type: "walk", f0, steps: 8, step: 0.5, period: 20, swing: 14, halfWidth: 0.08, lift: 0.08,
8
+ // heading: [h0, h1], turnDistance: 0.4, pelvisHigh: dy, pelvisLow: dy, firstStep: 0.4, armSwing: 14, vary: 1 }
9
+ // heading in degrees (yaw convention); pelvis heights are offsets from the standing hips height.
10
+ // The feet roll as a person's do, heel first (the three rockers of gait): the heel strikes with the
11
+ // toes up and the foot rolls down about the heel to flat; it stays flat while the shin passes over
12
+ // it; the heel comes up well before lift-off and the foot rolls over the ball, toes flat, to toe-off;
13
+ // in the air the foot follows the shin, the ankle near neutral (walk-pelvis.js fitSwing), into the next strike.
14
+ // The heel strikes with the toes up STRIKE_DF (a stroll's, then a brisk walk's), half that on the closing step (a walk generated from motion
15
+ // capture lands with the foot nearly flat: the heel touches a frame or two before the sole); TOE_OFF is the push-off pitch at a full stride (STRIDE_REF m of foot travel), less for
16
+ // a shorter one. A pivot is the very point QA reads (qa/geometry.js solePoints), so a rolling foot
17
+ // never counts as sliding.
18
+ // The upper body leads the legs and its sizes follow the pace (the speed against the default walk's 0.5 m per 20 frames),
19
+ // as a person's do: the slower the walk, the less the arms swing and the more the trunk dips and sways. Each arm peaks
20
+ // forward or back at a step's mid-swing (the arm's flow lag puts the visible peak about a quarter of a step before the
21
+ // strike), ARM_SWING deg a unit of pace (armSwing, when not given); each elbow bends with its swing, most with the arm
22
+ // forward and least behind (ELBOW +- ELBOW_SWING, both growing with the pace), and the hands hang with the wrists a
23
+ // little flexed (WRIST). The pelvis and chest turns, the side sway and the dip each peak at their own phase of the step
24
+ // (the constants below). All of it fades in over the first steps and back out on the closing one.
25
+ import { add, sub, scale, len, apply, ry, clamp } from "./math.js";
26
+ import { walkRhythm } from "./gait-rhythm.js";
27
+ import { fitPelvis, fitTrail, fitSwing, pivot, brisk } from "./walk-pelvis.js";
28
+
29
+ const STRIKE_DF = [6, 4], TOE_OFF = 50, STRIDE_REF = 1.1, ELBOW = [18, 3], ELBOW_SWING = [6, 3], WRIST = 8;
30
+ // The trunk over the stepping hips (degrees at a full stride, less for a shorter one): the pelvis drops on the
31
+ // swing side in single support (LIST), the trunk held over it but for SWAY of it; the pelvis turns with the leading
32
+ // leg (PELVIS_TURN) and the chest the other way (CHEST_TURN); the trunk dips forward before each strike and back in
33
+ // single support (LEAN).
34
+ const LIST = 4.5, SWAY = 0.26, PELVIS_TURN = 4.5, CHEST_TURN = 5.5, MID_SLOW = 0.015, STRIKE_AHEAD = [0.52, 0.64], HEEL_OFF = 0.3, HEEL_MIN = 4;
35
+ // the upper body by the pace p (module doc), measured against motion-capture walks of 0.7 and 1.4 m/s: the arm swing
36
+ // (ARM_SWING deg a unit of pace, at the strike ARM_FALL of its wave: a hanging arm falls back from its peak a little
37
+ // ahead of a sine); the chest turn peaking CHEST_AT(p) steps before the strike and the pelvis turn PELVIS_AT(p)
38
+ // steps after it (at a stroll the two turn nearly together, at a brisk walk against each other); the side sway SIDE(p)
39
+ // deg peaking SIDE_AT steps before the strike (just after the last); the dip LEAN(p) deg with its peaks LEAN_AT steps
40
+ // before the strike, about a forward lean of WALK_LEAN deg a unit of pace
41
+ const ARM_SWING = 6.4, ARM_FALL = 0.8, CHEST_AT = p => clamp(0.66 - 0.18 * p, 0.2, 0.48), PELVIS_AT = p => clamp(0.32 - 0.11 * p, 0, 0.45);
42
+ const SIDE = p => clamp(2.8 - 0.45 * p, 1.2, 3), SIDE_AT = 0.92, LEAN = p => clamp(4.7 - 1.1 * p, 1.8, 4), LEAN_AT = [0.2, 0.6], WALK_LEAN = 0.9;
43
+ // the swing's path: the heel kicks up right after toe-off (EARLY_AT of the swing, 2 frames at least: EARLY_FWD of the
44
+ // way to mid-swing, EARLY_LIFT m up at a full stride and a swing of KICK_AIR frames or more, KICK_STROLL of that at a stroll's
45
+ // pace: a person strolling folds the knee less), so the knee folds as the
46
+ // toe leaves; it comes down LAND_BACK of the stride behind and LAND_ABOVE m over the strike LAND_AT of the swing before
47
+ // it. A swing too short for a key leaves it out (the foot would jolt between keys a frame apart), and the closing step
48
+ // sets down without either. The heel takes HEEL_MIN frames at least to rise and rises no higher than PITCH_MAX: past about 60 deg
49
+ // the toes sit behind the ankle and QA reads a bent knee as bending backward.
50
+ const EARLY_AT = 0.18, EARLY_FWD = 0.35, EARLY_LIFT = 0.18, KICK_STROLL = 0.55, KICK_AIR = 10, LAND_AT = 0.22, LAND_BACK = 0.12, LAND_ABOVE = 0.03, PITCH_MAX = 50;
51
+ const fwd = h => apply(ry(h), [0, 0, 1]);
52
+ const leftOf = h => apply(ry(h), [1, 0, 0]);
53
+ const smoothstep = x => { const t = Math.min(1, Math.max(0, x)); return t * t * (3 - 2 * t); };
54
+
55
+ /** Last keyed value of every channel at or before frame f. */
56
+ export function stateAt(keys, f) {
57
+ const st = {};
58
+ for (const k of [...keys].sort((a, b) => a.f - b.f)) if (k.f <= f) Object.assign(st, k.set);
59
+ return st;
60
+ }
61
+
62
+ /** gen -> keys; every key carries src (tag + step number) so QA can name the step it came from. */
63
+ export function walk(gen, keys, sk, tag = "walk") {
64
+ let src = `${tag} start`;
65
+ const out = [], at = (f, set) => { const key = { f, set, src }; out.push(key); return key; };
66
+ const st = stateAt(keys, gen.f0);
67
+ if (!st.foot_l || !st.foot_r || !st.root) throw new Error("MOTION_SPEC_INVALID: walk needs foot_l, foot_r and root keyed before f0");
68
+ if (gen.vary !== undefined && !(Number.isFinite(gen.vary) && gen.vary >= 0 && gen.vary <= 2)) throw new Error(`MOTION_SPEC_INVALID: ${tag} vary must be a number from 0 (every step the same) to 2, got ${JSON.stringify(gen.vary)}`);
69
+ const I = sk.index, P = sk.restWorldPos;
70
+ const ank = P[I.LeftFoot][1], standY = P[I.Hips][1];
71
+ const footRest = { l: sub(P[I.LeftToeBase], P[I.LeftFoot]), r: sub(P[I.RightToeBase], P[I.RightFoot]) };
72
+ // QA's heel point from the ankle of a flat foot at yaw 0; a foot pitched `deg` about a point on the ground
73
+ const heelOff = [0, 0.005 - ank, -0.045];
74
+ const flatAt = { l: -Infinity, r: -Infinity };
75
+ // Pelvis heights from the leg length: straight-ish over the stance foot, low enough at
76
+ // double support that the trailing leg still reaches (2% knee slack).
77
+ const leg = len(sub(P[I.LeftLeg], P[I.LeftUpLeg])) + len(sub(P[I.LeftFoot], P[I.LeftLeg]));
78
+ const hipDrop = P[I.Hips][1] - P[I.LeftUpLeg][1], step0 = gen.step ?? 0.5, hw = gen.halfWidth ?? 0.08;
79
+ const hipsAt = horiz => Math.sqrt(Math.max(0, (0.98 * leg) ** 2 - horiz ** 2)) + ank + hipDrop - standY;
80
+ const W = { steps: 8, step: step0, period: 20, swing: 14, halfWidth: hw, lift: 0.08, turnDistance: 0.4, firstStep: 0.4,
81
+ pelvisHigh: Math.min(-0.005, hipsAt(Math.hypot(0.03, hw - 0.035))), pelvisLow: hipsAt(Math.hypot(step0 / 2, hw)), vary: 1, ...gen };
82
+ const pace = (W.step / W.period) / (0.5 / 20);
83
+ W.armSwing = gen.armSwing ?? clamp(ARM_SWING * pace, 4, 22);
84
+ const chestAt = CHEST_AT(pace), pelvisAt = PELVIS_AT(pace), sideSway = SIDE(pace), dipBy = LEAN(pace), walkLean = clamp(WALK_LEAN * pace, 0, 3);
85
+ const elbow = ELBOW[0] + ELBOW[1] * Math.min(pace, 2.5), elbowSwing = ELBOW_SWING[0] + ELBOW_SWING[1] * Math.min(pace, 2.5);
86
+ const [h0, h1] = W.heading ?? [st.yaw ?? 0, st.yaw ?? 0];
87
+ const hs = s => h0 + (h1 - h0) * smoothstep(s / W.turnDistance);
88
+ const c0 = scale(add(st.foot_l, st.foot_r), 0.5); c0[1] = 0;
89
+ const path = [c0];
90
+ for (let i = 0; i < 4000; i++) path.push(add(path[i], scale(fwd(hs(i * 0.01)), 0.01)));
91
+ const C = s => path[Math.max(0, Math.min(path.length - 1, Math.round(s / 0.01)))];
92
+ const footAt = (s, k) => { const p = add(C(s), scale(leftOf(hs(s)), (k === "l" ? 1 : -1) * W.halfWidth)); p[1] = ank; return p; };
93
+ const stance = { l: -0.07, r: 0.07 }, state = { foot_l: st.foot_l, foot_r: st.foot_r, fyaw_l: st.fyaw_l ?? h0, fyaw_r: st.fyaw_r ?? h0 };
94
+ const holdAll = ["root", "yaw", "roll", "foot_l", "foot_r", "fyaw_l", "fyaw_r", "spine_yaw", "spine_roll", "spine_pitch", "aswing_l", "aswing_r", "abend_l", "abend_r",
95
+ "wflex_l", "wflex_r", "head_yaw"];
96
+ const r0 = st.roll ?? 0, sr0 = st.spine_roll ?? 0, sp0 = st.spine_pitch ?? 0;
97
+ // each foot's spans on the ground (fitPelvis): from a heel strike (or f0) to its lift-off, heel the frame its heel comes up
98
+ const ground = { l: [{ a: gen.f0, b: Infinity, strike: false }], r: [{ a: gen.f0, b: Infinity, strike: false }] };
99
+ at(gen.f0, Object.fromEntries(holdAll.map(ch => [ch, st[ch] ?? state[ch] ?? (ch === "yaw" ? h0 : 0)])));
100
+ let sLast = stance.r, prev = null;
101
+ const R = walkRhythm(W, { s0: sLast, turn: (a, b) => hs(b) - hs(a),
102
+ seed: [gen.f0, W.steps, W.step, W.period, W.swing, W.firstStep, h0, h1, c0[0].toFixed(3), c0[2].toFixed(3)].join("|") });
103
+ for (let i = 0; i < W.steps; i++) {
104
+ src = `${tag} step ${i + 1}`;
105
+ const k = i % 2 === 0 ? "l" : "r", other = k === "l" ? "r" : "l", last = i === W.steps - 1;
106
+ const sNew = last ? sLast : sLast + R.inc[i];
107
+ const ts = gen.f0 + 16 + R.lift[i], tl = ts + R.air[i], tm = ts + Math.floor(R.air[i] / 2);
108
+ // gait initiation: move the weight over the stance foot before the first heel comes up
109
+ if (i === 0) {
110
+ const pre = add(C(stance[other]), scale(leftOf(h0), (other === "l" ? 1 : -1) * W.halfWidth * 0.75)); pre[1] = st.root[1];
111
+ at(ts - 4, { root: pre, yaw: h0 });
112
+ at(ts, { arm_hang: 1 });
113
+ }
114
+ // a longer stride pushes off more; the closing step pushes and strikes less
115
+ const size = clamp(Math.abs(sNew - stance[k]) / STRIDE_REF, 0.3, 1.15), toeOff = TOE_OFF * (0.55 + 0.45 * size) * (last ? 0.8 : 1);
116
+ // forefoot rocker: the heel comes up HEEL_OFF of a step before lift-off (a little sooner on the first
117
+ // step, the weight only just over the other foot) and the foot rolls over the ball, slowly
118
+ // while the other foot is still in the air, then fast to toe-off
119
+ const ankle = state[`foot_${k}`], yw = state[`fyaw_${k}`], ball = add(ankle, apply(ry(yw), footRest[k]));
120
+ const tHeel = Math.max(ts - Math.max(HEEL_MIN, Math.round((i === 0 ? 0.35 : HEEL_OFF) * W.period)), flatAt[k] + 1, gen.f0 + 1);
121
+ Object.assign(ground[k].at(-1), { b: ts, heel: tHeel, ball, yw, flat: ankle });
122
+ for (let f = tHeel; f <= ts; f++) {
123
+ const d = toeOff * ((f - tHeel) / Math.max(1, ts - tHeel)) ** 1.8;
124
+ at(f, { [`foot_${k}`]: pivot(ball, yw, d, ankle), [`fpitch_${k}`]: d, [`fyaw_${k}`]: yw });
125
+ }
126
+ // swing: the foot rises behind first (the knee folds right after toe-off), the toes trailing down, level at
127
+ // mid-swing, and comes down onto the heel from above (the toes up) instead of skimming the floor
128
+ const lead = Math.max(1, Math.round(LAND_AT * R.air[i]));
129
+ const mid = footAt((stance[k] + sNew) / 2, k); mid[1] += R.height[i];
130
+ const off = pivot(ball, yw, Math.min(toeOff, PITCH_MAX), ankle), rise = add(off, scale(sub(mid, off), EARLY_FWD));
131
+ // a shorter swing kicks lower (the same rise in fewer frames would jolt the foot): by its length against KICK_AIR
132
+ // and with the step's own lift (gait-rhythm), so no two swings fold the knee alike
133
+ rise[1] = off[1] + EARLY_LIFT * size * Math.min(1, (R.air[i] / KICK_AIR) ** 2) * (KICK_STROLL + (1 - KICK_STROLL) * brisk(pace)) * (R.height[i] / W.lift);
134
+ // in a turn the line from the toe-off to mid-swing cuts in toward the other foot: the kick stays out at mid-swing's side
135
+ const outward = scale(leftOf(hs(stance[other])), k === "l" ? 1 : -1), foot = state[`foot_${other}`];
136
+ const side = p => (p[0] - foot[0]) * outward[0] + (p[2] - foot[2]) * outward[2], inside = side(mid) - side(rise);
137
+ if (inside > 0) { rise[0] += outward[0] * inside; rise[2] += outward[2] * inside; }
138
+ const early = ts + Math.max(2, Math.round(EARLY_AT * R.air[i]));
139
+ if (!last && early < tm - 1) at(early, { [`foot_${k}`]: rise, [`fpitch_${k}`]: 0.7 * Math.min(toeOff, PITCH_MAX) });
140
+ at(tm, { [`foot_${k}`]: mid, [`fyaw_${k}`]: hs((stance[k] + sNew) / 2), [`fpitch_${k}`]: 0 });
141
+ // at the strike the hips are past the middle of the stride (STRIKE_AHEAD: about half way at a stroll, well past it
142
+ // at a brisk walk, so a slow walk's hips dip between spread legs), over the landing foot's side;
143
+ // between strikes they cross the floor at an even pace, a little slower over mid-stance (MID_SLOW of the step)
144
+ const sLand = stance[other] + (STRIKE_AHEAD[0] + (STRIKE_AHEAD[1] - STRIKE_AHEAD[0]) * brisk(pace)) * (sNew - stance[other]);
145
+ const sEven = prev ? prev.s + (sLand - prev.s) * ((tm - prev.f) / (tl - prev.f)) : null;
146
+ const sMid = sEven === null ? stance[other] + 0.03 : sEven - MID_SLOW * Math.abs(sNew - stance[k]) / 2, shift = i === 0 ? 0.015 : 0.032;
147
+ const pm = add(C(sMid), scale(leftOf(hs(sMid)), (other === "l" ? 1 : -1) * shift)); pm[1] = standY + W.pelvisHigh;
148
+ // a stride longer than the nominal one drops the hips a little more at double support, so the trailing leg still reaches
149
+ const low = gen.pelvisLow ?? (R.inc[i] > step0 ? Math.min(W.pelvisLow, hipsAt(Math.hypot(R.inc[i] / 2, hw))) : W.pelvisLow);
150
+ const pl = C(sLand).slice(); pl[1] = standY + (last ? W.pelvisHigh : low);
151
+ prev = { f: tl, s: sLand };
152
+ // heel rocker: the toes come up before the strike, then the foot rolls down about the heel to flat
153
+ const land = footAt(sNew, k), lyaw = hs(sNew), heel = add(land, apply(ry(lyaw), heelOff));
154
+ const strike = -(STRIKE_DF[0] + (STRIKE_DF[1] - STRIKE_DF[0]) * brisk(pace)) * (last ? 0.5 : 1), nFlat = Math.max(2, Math.round(0.2 * W.period));
155
+ const touch = pivot(heel, lyaw, strike, land), above = add(touch, scale(fwd(lyaw), -LAND_BACK * Math.abs(sNew - stance[k])));
156
+ above[1] += LAND_ABOVE;
157
+ if (!last && lead > 1 && tl - lead > tm + 1) at(tl - lead, { [`foot_${k}`]: above, [`fpitch_${k}`]: 0.6 * strike });
158
+ for (let j = 0; j <= nFlat; j++) {
159
+ const d = strike * (1 - smoothstep(j / nFlat));
160
+ at(tl + j, { [`foot_${k}`]: pivot(heel, lyaw, d, land), [`fpitch_${k}`]: d, [`fyaw_${k}`]: lyaw });
161
+ }
162
+ flatAt[k] = tl + nFlat;
163
+ ground[k].push({ a: tl, b: Infinity, strike: true });
164
+ state[`foot_${k}`] = land; state[`fyaw_${k}`] = lyaw; stance[k] = sNew;
165
+ const swing = (last ? 0 : i < 3 ? [0, 0.3, 0.7][i] : 1) * R.arm[i], sg = k === "l" ? 1 : -1;
166
+ const sp = clamp(size, 0.4, 1.1), ramp = last ? 0 : i === 0 ? 0.5 : 1, list = LIST * sp * ramp * (other === "l" ? 1 : -1);
167
+ // the upper body leads the legs (module doc): the arms, the turns and the side sway are waves keyed at mid-swing (tm, dm
168
+ // of a step before the strike) and at the strike, the arms peaking at tm: a wave peaking `peak` steps before the strike,
169
+ // scaled so the larger of its two keys is 1 (the interpolation does not overshoot its keys); the dip is keyed at its peaks
170
+ const dm = (tl - tm) / W.period, wave = peak => { const v = [Math.cos(Math.PI * (peak - dm)), Math.cos(Math.PI * peak)]; return v.map(x => x / Math.max(...v.map(Math.abs))); };
171
+ const pelvis = wave(-pelvisAt).map(v => -PELVIS_TURN * sp * sg * swing * v), chest = wave(chestAt).map(v => CHEST_TURN * sp * sg * swing * v);
172
+ const sway = wave(SIDE_AT).map(v => -Math.sign(list) * sideSway * sp * ramp * v);
173
+ const arm = wave(dm).map((v, j) => (j ? ARM_FALL * v : v)), armAt = v => ({ aswing_l: -0.85 * W.armSwing * sg * swing * v,
174
+ aswing_r: W.armSwing * sg * swing * v, abend_l: swing * (elbow - sg * elbowSwing * v), abend_r: swing * (elbow + sg * elbowSwing * v) });
175
+ at(tm, { root: pm, yaw: hs(sMid) + pelvis[0], roll: r0 + list, spine_roll: sr0 - list * (1 + SWAY) + sway[0], spine_yaw: chest[0] - pelvis[0],
176
+ ...armAt(arm[0]), wflex_l: WRIST * swing, wflex_r: WRIST * swing });
177
+ at(tl, { root: pl, yaw: hs(sLand) + pelvis[1], roll: r0, spine_roll: sr0 + sway[1], spine_yaw: chest[1] - pelvis[1], ...armAt(arm[1]), head_yaw: 0 });
178
+ const lean = sp0 + walkLean * ramp, dip = dipBy * sp * ramp;
179
+ at(Math.max(gen.f0 + 1, tl - Math.round(LEAN_AT[1] * W.period)), { spine_pitch: lean - 0.6 * dip });
180
+ at(Math.max(gen.f0 + 1, tl - Math.round(LEAN_AT[0] * W.period)), { spine_pitch: lean + dip });
181
+ sLast = sNew;
182
+ }
183
+ const tend = gen.f0 + 16 + W.steps * W.period + 18;
184
+ // the hips' height from the legs on the ground, unless the walk gives its own pelvis heights
185
+ // (only up to the walk's last key: after it the settle just holds, and a take may end there, walkTo)
186
+ const settle = Math.max(...out.map(k => k.f));
187
+ // each swing's path (from the planned toe-off), the hips over it, the heel rise they leave the trailing foot, and
188
+ // each swing again from where that foot now lifts (its approach to the strike, which the hips saw, unchanged)
189
+ if (gen.pelvisHigh === undefined && gen.pelvisLow === undefined) {
190
+ fitSwing(out, { end: settle, ground });
191
+ fitPelvis(sk, out, { f0: gen.f0, end: settle, period: W.period, pace, ground, start: `${tag} start` });
192
+ fitTrail(sk, out, { end: settle, period: W.period, pace, ground });
193
+ fitSwing(out, { end: settle, ground, sk });
194
+ }
195
+ src = `${tag} end`;
196
+ at(tend, stateAt(out, tend));
197
+ return out;
198
+ }
199
+
200
+ export function expandGenerators(spec, sk) {
201
+ let keys = (spec.keys ?? []).map((k, i) => (k.src ? k : { ...k, src: `keys[${i}]` }));
202
+ (spec.generators ?? []).forEach((gen, i) => {
203
+ if (gen.type !== "walk") throw new Error(`MOTION_SPEC_INVALID: generators[${i}] has unknown type "${gen.type}" (only "walk")`);
204
+ keys = keys.concat(walk(gen, keys, sk, `generators[${i}] walk`));
205
+ });
206
+ return keys;
207
+ }
@@ -0,0 +1,32 @@
1
+ // The wrist an elbow turn leaves (fix.self, lane selfwrist). fix/self.js turns an elbow about the shoulder-wrist
2
+ // line to take an arm out of the body, and limbs.js swivelElbow keeps the hand's world turn while it does. With
3
+ // the elbow bent (a cigarette at the lips) that line runs well off the forearm, so the turn lands on the wrist
4
+ // as flex and deviation: the car exit's smoking hand went 57 deg toward the thumb (limit 20) on 88 frames, the
5
+ // squat smoke 56, the kits 26-57 (QA WRIST). No turn of the elbow both clears the body and keeps that wrist in
6
+ // range (the car exit at f500: 45 deg leaves the forearm 2.8 cm in the chest at 31 deg, 75 deg clears it at 57).
7
+ // So the wrist stops at its limits and the hand turns on with the elbow for the rest, as a wrist does; a fit
8
+ // (the filter at the lips) then moves the wrist until its point is back on its target (compile.js runs the turn
9
+ // in the fit's passes too). A wrist the turn leaves within its limits is not touched: the hand keeps its world
10
+ // turn exactly as before.
11
+ import { add, sub, scale, norm, mul, T, apply, axisRot } from "./math.js";
12
+ import { rotateSubtree } from "./skeleton.js";
13
+ import { palmAxes } from "./limbs.js";
14
+ import { WRIST_LIMITS, wristNeed } from "./arm-orient.js";
15
+
16
+ const clampTo = (v, [lo, hi]) => Math.min(hi, Math.max(lo, v));
17
+
18
+ /**
19
+ * clampWrist(sk, s, side): the hand of `side` turned back within WRIST_LIMITS on its forearm (twist, flex and
20
+ * deviation each clamped, the hand alone: the forearm bone keeps its turn). Returns { need, got } in degrees
21
+ * when it turned the hand, null when the wrist was within its limits.
22
+ */
23
+ export function clampWrist(sk, s, side) {
24
+ const I = sk.index, h = I[`${side}Hand`], fa = I[`${side}ForeArm`], rw = sk.restWorldRot;
25
+ const axT = norm(sub(s.P[h], s.P[fa])), neutral = mul(s.R[fa], mul(T(rw[fa]), rw[h]));
26
+ const { axF, axD, ...need } = wristNeed(neutral, s.R[h], axT, apply(mul(neutral, T(s.R[h])), palmAxes(sk, s, side).l));
27
+ const got = Object.fromEntries(Object.entries(need).map(([k, v]) => [k, clampTo(v, WRIST_LIMITS[k])]));
28
+ if (Object.keys(need).every(k => Math.abs(need[k] - got[k]) < 1e-6)) return null;
29
+ const want = mul(mul(axisRot(add(scale(axF, got.flex), scale(axD, got.dev)), Math.hypot(got.flex, got.dev)), axisRot(axT, got.twist)), neutral);
30
+ rotateSubtree(sk, s, h, s.P[h].slice(), mul(want, T(s.R[h])));
31
+ return { need, got };
32
+ }
@@ -0,0 +1,47 @@
1
+ // C7. What changed against a base spec's QA (lane D2). Groups match by severity.js groupKey
2
+ // (kind + subject with the numbers as "#"); groups that share a key are summed into one entry.
3
+ // resolved a key in the base only
4
+ // new a key in this compile only
5
+ // worse a key in both whose severity rose by DELTA_STEP (5 %) or more of the base severity
6
+ // better a key in both whose severity fell by DELTA_STEP or more
7
+ // keys lists at most DELTA_KEYS groupKeys of resolved, new and worse, most severe first; the
8
+ // counts are always complete.
9
+ import { groupKey, severityOf } from "./qa/severity.js";
10
+
11
+ const DELTA_STEP = 0.05;
12
+ const DELTA_KEYS = 6;
13
+
14
+ /** groups -> Map(groupKey -> summed severity), in first-seen order. */
15
+ function byKey(groups) {
16
+ const m = new Map();
17
+ for (const g of groups ?? []) {
18
+ const k = groupKey(g);
19
+ m.set(k, (m.get(k) ?? 0) + (Number.isFinite(g.severity) ? g.severity : severityOf(g)));
20
+ }
21
+ return m;
22
+ }
23
+
24
+ /** The first DELTA_KEYS keys of [key, weight] rows, the heaviest first (ties keep their order). */
25
+ const top = rows => rows.map((r, i) => [...r, i]).sort((a, b) => b[1] - a[1] || a[2] - b[2]).slice(0, DELTA_KEYS).map(r => r[0]);
26
+
27
+ /**
28
+ * deltaOf(baseGroups, groups, baseId) ->
29
+ * { base, resolved, new, worse, better, keys: { resolved: [], new: [], worse: [] } }
30
+ * @param {object[]} baseGroups the base spec's full qa.groups (spec store parts)
31
+ * @param {object[]} groups this compile's qa.groups
32
+ * @param {string} baseId
33
+ */
34
+ export function deltaOf(baseGroups, groups, baseId) {
35
+ const before = byKey(baseGroups), after = byKey(groups);
36
+ const resolved = [], added = [], worse = [];
37
+ let better = 0;
38
+ for (const [k, s] of before) if (!after.has(k)) resolved.push([k, s]);
39
+ for (const [k, s] of after) {
40
+ if (!before.has(k)) { added.push([k, s]); continue; }
41
+ const b = before.get(k), step = DELTA_STEP * Math.max(Math.abs(b), 0.01);
42
+ if (s - b >= step - 1e-9) worse.push([k, s - b]);
43
+ else if (b - s >= step - 1e-9) better += 1;
44
+ }
45
+ return { base: baseId, resolved: resolved.length, new: added.length, worse: worse.length, better,
46
+ keys: { resolved: top(resolved), new: top(added), worse: top(worse) } };
47
+ }