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
@@ -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
+ }