cozyclay 2.1.1 → 3.0.1

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 (585) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/README.md +12 -30
  3. package/bin/agent/agent-routes.mjs +43 -116
  4. package/bin/agent/agent-runner.mjs +14 -15
  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/session-store.mjs +2 -2
  14. package/bin/agent/studio-context-budget.mjs +93 -0
  15. package/bin/agent/studio-motion-reference.mjs +23 -0
  16. package/bin/agent/studio-prompt.mjs +1 -1
  17. package/bin/agent/studio-rebase-text.mjs +32 -0
  18. package/bin/agent/studio-rebase.mjs +26 -39
  19. package/bin/agent/studio-tools.mjs +9 -28
  20. package/bin/agent/tool-arguments.mjs +49 -0
  21. package/bin/agent/turn-frames.mjs +66 -0
  22. package/bin/codex-auth.mjs +9 -2
  23. package/bin/cozyclay.mjs +10 -116
  24. package/bin/live/cli.mjs +3 -3
  25. package/bin/mcp-runtime.mjs +2 -12
  26. package/bin/package-signature.mjs +0 -4
  27. package/dist/app/index.html +7 -6
  28. package/dist/assets/EXRLoader-CvVIvGVE.js +1 -0
  29. package/dist/assets/analytics-BWCqdgWA.js +1 -0
  30. package/dist/assets/app-DfTCxaKk.js +5087 -0
  31. package/dist/assets/app-f-fkV5uP.css +1 -0
  32. package/dist/assets/compile-worker-BxKXh07s.js +5 -0
  33. package/dist/assets/fflate.module-KbGj5-H6.js +5 -0
  34. package/dist/assets/{first-shot-handoff-msLI71g1.js → first-shot-handoff-DNO8kv_N.js} +1 -1
  35. package/dist/assets/{landing-ChUvB_zL.js → landing-yfZaJkWc.js} +1 -1
  36. package/dist/assets/scene-asset-sync-U3kS2rvK.js +20 -0
  37. package/dist/assets/scene-asset-sync-_S8NQl5B.css +1 -0
  38. package/dist/assets/workflow-D78eiKTa.js +179 -0
  39. package/dist/cozyclay-package.json +1 -1
  40. package/dist/index.html +3 -3
  41. package/dist/sw.js +3 -6
  42. package/dist/workflow/index.html +5 -4
  43. package/mcp/LIVE-PROTOCOL.md +7 -47
  44. package/mcp/README.md +16 -26
  45. package/mcp/live-hub.mjs +6 -10
  46. package/mcp/package.json +1 -3
  47. package/mcp/tool-handlers.mjs +4 -168
  48. package/package.json +3 -22
  49. package/src/App.jsx +151 -2625
  50. package/src/agent-progress.js +80 -0
  51. package/src/agent-turn-mark.js +30 -0
  52. package/src/analytics.js +1 -19
  53. package/src/app-context.js +12 -2
  54. package/src/app-stage.jsx +2 -34
  55. package/src/asset-pane.jsx +6 -5
  56. package/src/camera-block.js +21 -0
  57. package/src/camera-direction.js +120 -0
  58. package/src/character-kind.js +3 -9
  59. package/src/command-bus.js +10 -8
  60. package/src/commands/cast.js +5 -34
  61. package/src/commands/elements/character.js +1 -1
  62. package/src/commands/elements/motion.js +1 -6
  63. package/src/commands/elements/object.js +6 -2
  64. package/src/commands/elements/shot.js +4 -1
  65. package/src/commands/elements.js +38 -14
  66. package/src/commands/index.js +3 -1
  67. package/src/commands/kits.js +37 -0
  68. package/src/commands/motion-timing.js +57 -0
  69. package/src/commands/motion.js +9 -152
  70. package/src/commands/motion3-compile.js +65 -0
  71. package/src/commands/motion3.js +214 -0
  72. package/src/commands/objects.js +6 -4
  73. package/src/commands/shot.js +55 -13
  74. package/src/commands/stage.js +14 -1
  75. package/src/commands/view.js +4 -1
  76. package/src/document-mirrors.js +42 -0
  77. package/src/document-store.js +4 -0
  78. package/src/domains/cast.js +19 -194
  79. package/src/domains/motion/motion-range-pin-rules.js +1 -1
  80. package/src/domains/motion/take-source.js +53 -0
  81. package/src/domains/motion.js +60 -1838
  82. package/src/domains/scenes.js +12 -55
  83. package/src/domains/shots.js +2 -2
  84. package/src/domains/stage.js +4 -2
  85. package/src/draft-ghost.js +47 -0
  86. package/src/draft-ghost.jsx +129 -0
  87. package/src/dualview.jsx +4 -3
  88. package/src/effects/EffectsLayer.jsx +112 -0
  89. package/src/effects/effect-meshes.js +232 -0
  90. package/src/effects/effect-record.js +103 -0
  91. package/src/effects/emitter.js +59 -0
  92. package/src/effects/simulate.js +261 -0
  93. package/src/effects/state.js +59 -0
  94. package/src/execution-telemetry.js +2 -3
  95. package/src/first-success-guide.jsx +5 -5
  96. package/src/ghost-set.js +257 -0
  97. package/src/held-object-edit.jsx +113 -0
  98. package/src/held-object-pose.js +175 -0
  99. package/src/hierarchy-model.js +1 -1
  100. package/src/hierarchy-panel.jsx +1 -1
  101. package/src/imported-mesh.jsx +103 -0
  102. package/src/kits/held.js +84 -0
  103. package/src/kits/index.js +70 -0
  104. package/src/kits/smoking.js +57 -0
  105. package/src/light-blockers.js +93 -0
  106. package/src/motion/beat-board-model.js +84 -0
  107. package/src/motion/beat-board.css +124 -0
  108. package/src/motion/beat-board.jsx +60 -0
  109. package/src/motion/center-of-mass.js +66 -0
  110. package/src/motion/fix-collisions.js +850 -0
  111. package/src/motion/physics-review.js +118 -0
  112. package/src/{ardy → motion}/physics-support.js +1 -76
  113. package/src/motion/rig-snapshot.js +22 -0
  114. package/src/motion/take-segments.jsx +140 -0
  115. package/src/{ardy → motion}/timeline-coordinates.js +0 -21
  116. package/src/{ardy → motion}/timeline.css +1 -2
  117. package/src/{ardy → motion}/timeline.jsx +42 -413
  118. package/src/motion/waypoints.js +76 -0
  119. package/src/motion3/beats.js +83 -0
  120. package/src/motion3/body.js +158 -0
  121. package/src/motion3/bones.js +47 -0
  122. package/src/motion3/carried.js +47 -0
  123. package/src/motion3/compile-job.js +79 -0
  124. package/src/motion3/compile-worker.js +10 -0
  125. package/src/motion3/compiler/anchors.js +113 -0
  126. package/src/motion3/compiler/arm-orient.js +128 -0
  127. package/src/motion3/compiler/channels.js +125 -0
  128. package/src/motion3/compiler/compile.js +205 -0
  129. package/src/motion3/compiler/gait-rhythm.js +97 -0
  130. package/src/motion3/compiler/grip.js +171 -0
  131. package/src/motion3/compiler/head-steady.js +48 -0
  132. package/src/motion3/compiler/hold.js +134 -0
  133. package/src/motion3/compiler/idle.js +113 -0
  134. package/src/motion3/compiler/limbs.js +197 -0
  135. package/src/motion3/compiler/math.js +72 -0
  136. package/src/motion3/compiler/pose.js +177 -0
  137. package/src/motion3/compiler/quat.js +49 -0
  138. package/src/motion3/compiler/skeleton.js +74 -0
  139. package/src/motion3/compiler/validate-common.js +32 -0
  140. package/src/motion3/compiler/validate-goals.js +118 -0
  141. package/src/motion3/compiler/validate-objects.js +144 -0
  142. package/src/motion3/compiler/validate-parts.js +118 -0
  143. package/src/motion3/compiler/validate.js +122 -0
  144. package/src/motion3/compiler/vocab.js +74 -0
  145. package/src/motion3/compiler/walk-pelvis.js +275 -0
  146. package/src/motion3/compiler/walk.js +207 -0
  147. package/src/motion3/compiler/wrist-clamp.js +32 -0
  148. package/src/motion3/delta.js +47 -0
  149. package/src/motion3/describe.js +177 -0
  150. package/src/motion3/docs.js +27 -0
  151. package/src/motion3/edit-carry.js +21 -0
  152. package/src/motion3/fix/ease.js +16 -0
  153. package/src/motion3/fix/ground.js +37 -0
  154. package/src/motion3/fix/index.js +52 -0
  155. package/src/motion3/fix/push.js +71 -0
  156. package/src/motion3/fix/reach.js +46 -0
  157. package/src/motion3/fix/retime.js +79 -0
  158. package/src/motion3/fix/self.js +211 -0
  159. package/src/motion3/fix/slide.js +70 -0
  160. package/src/motion3/fix/turn.js +51 -0
  161. package/src/motion3/follow.js +39 -0
  162. package/src/motion3/free-motion.js +204 -0
  163. package/src/motion3/grip-choice.js +130 -0
  164. package/src/motion3/hold-plan.js +157 -0
  165. package/src/motion3/json-patch.js +77 -0
  166. package/src/motion3/key-aim.js +105 -0
  167. package/src/motion3/key-edit.js +82 -0
  168. package/src/motion3/lenient.js +127 -0
  169. package/src/motion3/object-keys.js +236 -0
  170. package/src/motion3/object-pose.js +182 -0
  171. package/src/motion3/object-tracks.js +12 -0
  172. package/src/motion3/people.js +82 -0
  173. package/src/motion3/pipeline.js +102 -0
  174. package/src/motion3/playback.js +51 -0
  175. package/src/motion3/probes.js +91 -0
  176. package/src/motion3/qa/body-checks.js +34 -0
  177. package/src/motion3/qa/check-names.js +14 -0
  178. package/src/motion3/qa/checks.js +125 -0
  179. package/src/motion3/qa/contact-checks.js +51 -0
  180. package/src/motion3/qa/explain.js +80 -0
  181. package/src/motion3/qa/fmt.js +5 -0
  182. package/src/motion3/qa/foot-checks.js +72 -0
  183. package/src/motion3/qa/geometry.js +77 -0
  184. package/src/motion3/qa/goals.js +191 -0
  185. package/src/motion3/qa/ground.js +60 -0
  186. package/src/motion3/qa/hints-contact.js +195 -0
  187. package/src/motion3/qa/hints-goal.js +160 -0
  188. package/src/motion3/qa/hints-knee.js +24 -0
  189. package/src/motion3/qa/hints-motion.js +209 -0
  190. package/src/motion3/qa/hints-object.js +91 -0
  191. package/src/motion3/qa/index.js +44 -0
  192. package/src/motion3/qa/key-checks.js +82 -0
  193. package/src/motion3/qa/near.js +96 -0
  194. package/src/motion3/qa/object-checks.js +135 -0
  195. package/src/motion3/qa/patch.js +161 -0
  196. package/src/motion3/qa/people-checks.js +37 -0
  197. package/src/motion3/qa/severity.js +38 -0
  198. package/src/motion3/qa/spec-checks.js +65 -0
  199. package/src/motion3/qa/style.js +228 -0
  200. package/src/motion3/qa/wrist-checks.js +69 -0
  201. package/src/motion3/qa/wrist-obs.js +60 -0
  202. package/src/motion3/refs.js +129 -0
  203. package/src/motion3/repair-tune.js +121 -0
  204. package/src/motion3/repair.js +83 -0
  205. package/src/motion3/report.js +67 -0
  206. package/src/motion3/rigs/index.js +14 -0
  207. package/src/motion3/rigs/x-bot-tpose-body.json +1 -0
  208. package/src/motion3/rigs/x-bot-tpose.json +1 -0
  209. package/src/motion3/rigs/y-bot-tpose-body.json +1 -0
  210. package/src/motion3/rigs/y-bot-tpose.json +1 -0
  211. package/src/motion3/scene.js +60 -0
  212. package/src/motion3/schema/brief.js +89 -0
  213. package/src/motion3/schema/fields.js +44 -0
  214. package/src/motion3/schema/spec-examples.js +46 -0
  215. package/src/motion3/schema/topics.js +113 -0
  216. package/src/motion3/schema/types.js +149 -0
  217. package/src/motion3/search.js +117 -0
  218. package/src/motion3/second-hand.js +112 -0
  219. package/src/motion3/settle.js +136 -0
  220. package/src/motion3/skills/basic.js +83 -0
  221. package/src/motion3/skills/book.js +76 -0
  222. package/src/motion3/skills/car.js +145 -0
  223. package/src/motion3/skills/flip.js +200 -0
  224. package/src/motion3/skills/frames-auto.js +29 -0
  225. package/src/motion3/skills/impact.js +109 -0
  226. package/src/motion3/skills/index.js +85 -0
  227. package/src/motion3/skills/info.js +80 -0
  228. package/src/motion3/skills/smoke-kit.js +109 -0
  229. package/src/motion3/skills/smoke.js +160 -0
  230. package/src/motion3/spec-store.js +59 -0
  231. package/src/motion3/take-edit.js +155 -0
  232. package/src/motion3/take.js +63 -0
  233. package/src/motion3/timeline.js +127 -0
  234. package/src/motion3/views.js +121 -0
  235. package/src/motion3/walk-plan.js +119 -0
  236. package/src/object-catalog.jsx +7 -1
  237. package/src/object-colors.js +98 -0
  238. package/src/object-gizmo-parts.js +115 -0
  239. package/src/object-gizmo.jsx +37 -122
  240. package/src/object-glow.js +139 -0
  241. package/src/object-hidden.js +33 -0
  242. package/src/object-look.js +196 -0
  243. package/src/object-size.js +10 -0
  244. package/src/object-travel.js +71 -6
  245. package/src/panels/CameraPanel.jsx +7 -7
  246. package/src/panels/HdrEnvironmentControls.jsx +59 -0
  247. package/src/panels/LightPanel.jsx +25 -4
  248. package/src/panels/ObjectLookPanel.jsx +88 -0
  249. package/src/panels/PosePanel.jsx +2 -17
  250. package/src/panels/RigControlPanel.jsx +5 -53
  251. package/src/panels/SubjectBox.jsx +4 -1
  252. package/src/panels/details.css +14 -0
  253. package/src/posestudio.jsx +4 -27
  254. package/src/project-browser.jsx +3 -4
  255. package/src/project-resources.js +18 -31
  256. package/src/project.js +13 -104
  257. package/src/props.jsx +25 -280
  258. package/src/qa-label.js +25 -0
  259. package/src/rail-dot-drag.js +23 -0
  260. package/src/render-passes.js +10 -0
  261. package/src/resource-status.jsx +1 -0
  262. package/src/result-modal.jsx +4 -19
  263. package/src/room.jsx +20 -12
  264. package/src/scene-assets.js +8 -6
  265. package/src/scene-light-place.js +72 -0
  266. package/src/scene-lights.jsx +148 -0
  267. package/src/scene-objects.js +31 -139
  268. package/src/scenes.js +15 -105
  269. package/src/semantic-edit.js +2 -5
  270. package/src/set-pieces.jsx +183 -0
  271. package/src/settings-menu.jsx +3 -11
  272. package/src/shell/BottomDock.jsx +9 -51
  273. package/src/shell/DetailsSlot.jsx +16 -200
  274. package/src/shell/GhostsMenuGroup.jsx +74 -0
  275. package/src/shell/LibrarySlot.jsx +1 -3
  276. package/src/shell/MenuBar.jsx +3 -3
  277. package/src/shell/PreferencesDialog.jsx +15 -264
  278. package/src/shell/PreferencesSlot.jsx +2 -8
  279. package/src/shell/TopBar.jsx +5 -84
  280. package/src/shell/ViewportToolbar.jsx +10 -70
  281. package/src/shell/agent-glass.css +19 -6
  282. package/src/shell/details-selection.js +42 -0
  283. package/src/shell/ghosts-menu.css +22 -0
  284. package/src/shell/glass-regions.css +29 -3
  285. package/src/shell/preferences.css +6 -1
  286. package/src/shot-authoring.js +4 -7
  287. package/src/soft-shadows.js +37 -0
  288. package/src/stage-attach-frames.js +12 -12
  289. package/src/stage-canvas-rigs.jsx +3 -3
  290. package/src/stage-capture.jsx +42 -8
  291. package/src/stage-characters.jsx +22 -216
  292. package/src/stage-editor-gizmos.jsx +6 -6
  293. package/src/stage-environment.js +93 -0
  294. package/src/stage-hdr-builtins.js +107 -0
  295. package/src/stage-hdr-image.js +116 -0
  296. package/src/stage-hdr-library.js +70 -0
  297. package/src/stage-hdr-map.js +94 -0
  298. package/src/stage-hdr.js +130 -0
  299. package/src/stage-lighting.js +149 -0
  300. package/src/stage-occlusion.js +210 -0
  301. package/src/stage-sky.jsx +159 -0
  302. package/src/stage-startup-defaults.js +5 -9
  303. package/src/studio-actions.js +16 -14
  304. package/src/studio-agent-commands.js +19 -16
  305. package/src/studio-agent-motion.js +9 -9
  306. package/src/studio-agent-protocol.js +47 -77
  307. package/src/studio-app-binding.js +27 -47
  308. package/src/studio-asset-list.js +17 -0
  309. package/src/studio-contact-sheet.js +37 -5
  310. package/src/studio-elements.js +27 -7
  311. package/src/studio-entity-bounds.js +18 -0
  312. package/src/studio-error-codes.js +15 -0
  313. package/src/studio-export-framing.js +152 -0
  314. package/src/studio-lighting-check.js +103 -0
  315. package/src/studio-motion-visual.js +150 -0
  316. package/src/studio-schema-errors.js +80 -0
  317. package/src/studio-support-gap.js +74 -0
  318. package/src/styles.css +1 -19
  319. package/src/timeline-defaults.js +5 -0
  320. package/src/timeline-extent.js +8 -20
  321. package/src/tutorial-walk.js +17 -0
  322. package/src/ui-scrub.js +1 -1
  323. package/src/use-shot-camera-tracks.js +12 -0
  324. package/src/view-ghosts.js +97 -0
  325. package/src/view-ghosts.jsx +49 -0
  326. package/src/workflow/AgentPanel.jsx +47 -166
  327. package/src/workflow/AgentProgressCard.jsx +109 -0
  328. package/src/workflow/AgentReplyText.jsx +37 -0
  329. package/src/workflow/AgentTurnSteps.jsx +84 -0
  330. package/src/workflow/LoginPanel.jsx +140 -0
  331. package/src/workflow/WorkflowBuilder.jsx +7 -23
  332. package/src/workflow/agent-client.js +54 -51
  333. package/src/workflow/agent-panel.css +71 -1
  334. package/src/workflow/agent-progress-card.css +167 -0
  335. package/src/workflow/agent-progress-view.js +47 -0
  336. package/src/workflow/agent-turn-steps.css +113 -0
  337. package/src/workflow/agent-turn.js +191 -0
  338. package/src/workflow/login-client.js +34 -0
  339. package/src/workflow/motion-input.js +18 -31
  340. package/src/workflow/node-schema.js +1 -1
  341. package/src/workflow/reply-markdown.js +69 -0
  342. package/src/workflow/scene-asset-sync.js +8 -8
  343. package/tools/agent-session-stats.mjs +61 -0
  344. package/tools/dev-full.mjs +5 -56
  345. package/tools/eslint-baseline.json +12 -13
  346. package/tools/knip-baseline.json +0 -179
  347. package/tools/loc-baseline.json +31 -81
  348. package/tools/motion3/extract-body.mjs +103 -0
  349. package/tools/motion3/extract-rig.mjs +85 -0
  350. package/tools/process-supervisor.mjs +0 -88
  351. package/tools/run-tests.mjs +9 -125
  352. package/bin/agent/motion-runtime.mjs +0 -76
  353. package/dist/ardy/cskel27-rest.json +0 -557
  354. package/dist/assets/analytics-C9pMpW7s.js +0 -1
  355. package/dist/assets/app-B_2XB9rw.css +0 -1
  356. package/dist/assets/app-DAuvKPP8.js +0 -4920
  357. package/dist/assets/config-Bx7pYsD9.js +0 -1
  358. package/dist/assets/config-DeoFTYzb.css +0 -1
  359. package/dist/assets/demo-CvKuBgy5.js +0 -1
  360. package/dist/assets/scene-asset-sync-DKkpjKBV.js +0 -19
  361. package/dist/assets/scene-asset-sync-DjJ_cJ2R.css +0 -1
  362. package/dist/assets/ticket-BPk2Ni95.js +0 -1
  363. package/dist/assets/workflow-jJRBR94Z.js +0 -179
  364. package/dist/demo/walk-then-stop.npz +0 -0
  365. package/mcp/ardy-prompts.mjs +0 -218
  366. package/src/ardy/auto-fix-panel.css +0 -108
  367. package/src/ardy/auto-physics.js +0 -1194
  368. package/src/ardy/client.js +0 -153
  369. package/src/ardy/convert.js +0 -313
  370. package/src/ardy/cskel27-neutral.js +0 -39
  371. package/src/ardy/cskel27.js +0 -77
  372. package/src/ardy/export.js +0 -165
  373. package/src/ardy/fix-collisions.js +0 -2257
  374. package/src/ardy/foot-lock.js +0 -230
  375. package/src/ardy/motion-calibration.js +0 -118
  376. package/src/ardy/motion-edit.js +0 -213
  377. package/src/ardy/motion-url.js +0 -55
  378. package/src/ardy/mp4-duration.js +0 -102
  379. package/src/ardy/npz.js +0 -595
  380. package/src/ardy/physics-panel.css +0 -21
  381. package/src/ardy/physics-panel.jsx +0 -89
  382. package/src/ardy/physics-refine.js +0 -111
  383. package/src/ardy/physics-review.js +0 -567
  384. package/src/ardy/physics-temporal.js +0 -54
  385. package/src/ardy/platform-fit-panel.css +0 -64
  386. package/src/ardy/platform-fit-panel.jsx +0 -41
  387. package/src/ardy/platform-fit.js +0 -326
  388. package/src/ardy/playback.js +0 -711
  389. package/src/ardy/pose-pin.js +0 -97
  390. package/src/ardy/prompt-clips.js +0 -30
  391. package/src/ardy/retime.js +0 -211
  392. package/src/ardy/root-drop.js +0 -369
  393. package/src/ardy/timeline-resize.js +0 -13
  394. package/src/ardy/to-cskel27.js +0 -225
  395. package/src/ardy/trim.js +0 -33
  396. package/src/ardy/waypoints.js +0 -369
  397. package/src/ardy-bridge-contract.js +0 -281
  398. package/src/domains/motion/motion-video-capture.js +0 -664
  399. package/src/i2v-motion-client.js +0 -77
  400. package/src/i2v-motion-studio.jsx +0 -180
  401. package/src/line-edit.js +0 -1348
  402. package/src/motion/generation.js +0 -105
  403. package/src/motion-readiness-ui.jsx +0 -67
  404. package/src/motion-readiness.js +0 -40
  405. package/src/motion-resources.js +0 -177
  406. package/src/motion-store.js +0 -65
  407. package/src/motion-trail.js +0 -601
  408. package/src/multimodel-ingest.js +0 -394
  409. package/src/panels/PromptBlocksPanel.jsx +0 -444
  410. package/src/panels/VideoCapturePanel.jsx +0 -181
  411. package/src/pose-extract/browser.js +0 -42
  412. package/src/pose-extract/detector.js +0 -100
  413. package/src/pose-extract/fit.js +0 -227
  414. package/src/pose-extract/image-frame.js +0 -51
  415. package/src/pose-extract/index.js +0 -58
  416. package/src/pose-extract/keyframes.js +0 -74
  417. package/src/pose-extract/landmarks.js +0 -162
  418. package/src/pose-extract/mirror.js +0 -153
  419. package/src/pose-extract/take.js +0 -131
  420. package/src/pose-extract/video-frames.js +0 -91
  421. package/src/take-recipe.js +0 -165
  422. package/src/trail-key-conflicts.js +0 -39
  423. package/src/trail-pick.js +0 -59
  424. package/src/use-i2v-capture-modal.js +0 -15
  425. package/tools/ardy/BRIDGE.md +0 -37
  426. package/tools/ardy/GVHMR-NPZ.md +0 -68
  427. package/tools/ardy/README.md +0 -124
  428. package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
  429. package/tools/ardy/artifacts.mjs +0 -34
  430. package/tools/ardy/bridge.mjs +0 -1732
  431. package/tools/ardy/bvh-cskel27.mjs +0 -1265
  432. package/tools/ardy/cclay_constrained_generate.py +0 -561
  433. package/tools/ardy/cclay_gvhmr_worker.py +0 -264
  434. package/tools/ardy/cclay_motion_edit.py +0 -510
  435. package/tools/ardy/cclay_sequence_generate.py +0 -705
  436. package/tools/ardy/cclay_worker.py +0 -304
  437. package/tools/ardy/dump-npz.py +0 -205
  438. package/tools/ardy/extract-rest.mjs +0 -299
  439. package/tools/ardy/extract.mjs +0 -507
  440. package/tools/ardy/footage.mjs +0 -467
  441. package/tools/ardy/gvhmr-floor.mjs +0 -102
  442. package/tools/ardy/gvhmr-to-cskel27.mjs +0 -21
  443. package/tools/ardy/gvhmr_fastpath.py +0 -168
  444. package/tools/ardy/gvhmr_trajectory.py +0 -240
  445. package/tools/ardy/merge-text-encoder.py +0 -160
  446. package/tools/ardy/mocap-metrics.mjs +0 -200
  447. package/tools/ardy/mocap-quality-gate.mjs +0 -110
  448. package/tools/ardy/motion-stabilize.mjs +0 -348
  449. package/tools/ardy/npz.mjs +0 -414
  450. package/tools/ardy/pose-to-npz.mjs +0 -106
  451. package/tools/ardy/prompt-limits.mjs +0 -5
  452. package/tools/ardy/run-edit-on-box.sh +0 -73
  453. package/tools/ardy/run-local.mjs +0 -233
  454. package/tools/ardy/run-on-box.sh +0 -593
  455. package/tools/ardy/run-sequence-on-box.sh +0 -177
  456. package/tools/ardy/runners/gvhmr-worker.mjs +0 -206
  457. package/tools/ardy/runners/index.mjs +0 -18
  458. package/tools/ardy/runners/local.mjs +0 -455
  459. package/tools/ardy/runners/proc.mjs +0 -125
  460. package/tools/ardy/runners/remote.mjs +0 -252
  461. package/tools/ardy/setup-local.mjs +0 -189
  462. package/tools/ardy/setup-text-encoder-on-box.sh +0 -93
  463. package/tools/ardy/setup-text-encoder.py +0 -306
  464. package/tools/ardy/smpl-cskel27.mjs +0 -178
  465. package/tools/ardy/visual-qa.mjs +0 -196
  466. package/tools/ardy/vq-car.mjs +0 -52
  467. package/tools/bench/EXP3.md +0 -61
  468. package/tools/bench/cclay_bench_extract_incam.py +0 -125
  469. package/tools/bench/cclay_bench_extract_obs.py +0 -204
  470. package/tools/bench/cclay_bench_runner.py +0 -42
  471. package/tools/bench/cube-contact.mjs +0 -162
  472. package/tools/bench/exp3.mjs +0 -152
  473. package/tools/bench/extract-bench-lib.mjs +0 -216
  474. package/tools/bench/extract-bench.mjs +0 -302
  475. package/tools/bench/fal-generate.mjs +0 -88
  476. package/tools/bench/fit/README.md +0 -189
  477. package/tools/bench/fit/camera.mjs +0 -59
  478. package/tools/bench/fit/contact.mjs +0 -419
  479. package/tools/bench/fit/footlock.mjs +0 -234
  480. package/tools/bench/fit/motion.mjs +0 -85
  481. package/tools/bench/fit/pin.mjs +0 -27
  482. package/tools/bench/fit/remote.mjs +0 -105
  483. package/tools/bench/fit-bench.mjs +0 -126
  484. package/tools/bench/fit-sanity.mjs +0 -45
  485. package/tools/bench/metrics.mjs +0 -313
  486. package/tools/bench/obs/depth.mjs +0 -258
  487. package/tools/bench/obs/extrinsics.mjs +0 -175
  488. package/tools/bench/obs/fit_mannequin_betas.py +0 -340
  489. package/tools/bench/obs/ground.mjs +0 -294
  490. package/tools/bench/obs/heading.mjs +0 -151
  491. package/tools/bench/obs/ladder.mjs +0 -555
  492. package/tools/bench/obs/mannequin-betas.json +0 -124
  493. package/tools/bench/obs/remote.mjs +0 -83
  494. package/tools/bench/obs/rest_joints.py +0 -52
  495. package/tools/bench/obs/ybot-targets.mjs +0 -49
  496. package/tools/bench/obs-bench.mjs +0 -520
  497. package/tools/bench/score.mjs +0 -343
  498. package/tools/bench/summarize.mjs +0 -86
  499. package/tools/demo-worker/README.md +0 -137
  500. package/tools/demo-worker/api-client.mjs +0 -233
  501. package/tools/demo-worker/generate.mjs +0 -301
  502. package/tools/demo-worker/index.mjs +0 -466
  503. package/tools/demo-worker/sign-core.mjs +0 -112
  504. package/tools/demo-worker/sign.mjs +0 -161
  505. package/tools/gt-render/browser.mjs +0 -182
  506. package/tools/gt-render/camera-math.mjs +0 -276
  507. package/tools/gt-render/page.mjs +0 -267
  508. package/tools/gt-render/render.mjs +0 -514
  509. package/tools/gt-render/scene-box.mjs +0 -31
  510. package/tools/gt-render/take-transform.mjs +0 -87
  511. package/tools/kimodo/compare-seam.mjs +0 -108
  512. package/tools/kimodo/constraints.mjs +0 -244
  513. package/tools/kimodo/edit.mjs +0 -259
  514. package/tools/kimodo/effector-constraints.mjs +0 -200
  515. package/tools/kimodo/generate.mjs +0 -646
  516. package/tools/kimodo/local-output.mjs +0 -265
  517. package/tools/kimodo/measure-pose.mjs +0 -65
  518. package/tools/kimodo/measure-preserve.mjs +0 -899
  519. package/tools/kimodo/measure-waypoints.mjs +0 -55
  520. package/tools/kimodo/mlx-generate.py +0 -111
  521. package/tools/kimodo/pose-constraints.mjs +0 -248
  522. package/tools/kimodo/preserve-mask.mjs +0 -543
  523. package/tools/kimodo/read-npz.mjs +0 -189
  524. package/tools/kimodo/run-edit-on-box.mjs +0 -155
  525. package/tools/kimodo/run-sequence-on-box.mjs +0 -178
  526. package/tools/kimodo/runner.mjs +0 -284
  527. package/tools/kimodo/seam-batch.mjs +0 -77
  528. package/tools/kimodo/setup-local.mjs +0 -30
  529. package/tools/kimodo/setup-on-box.sh +0 -339
  530. package/tools/kimodo/soma77-to-cskel27.mjs +0 -305
  531. package/tools/morphgs/assets/gen_truth.py +0 -39
  532. package/tools/morphgs/assets/mesh_ori_rig.txt +0 -27
  533. package/tools/morphgs/assets/playback-check.mjs +0 -30
  534. package/tools/morphgs/demo-gate.sh +0 -26
  535. package/tools/morphgs/fbx2morphgs.mjs +0 -68
  536. package/tools/morphgs/morphgs-to-cskel27.mjs +0 -105
  537. package/tools/morphgs/patches/preprocess_src-none-mode.patch +0 -76
  538. package/tools/morphgs/setup-on-cluster.sh +0 -126
  539. package/tools/projflow/driver.py +0 -1069
  540. package/tools/projflow/generate.mjs +0 -805
  541. package/tools/projflow/hml22-to-cskel27.mjs +0 -735
  542. package/tools/projflow/line-edit-job.mjs +0 -547
  543. package/tools/projflow/replay.mjs +0 -452
  544. package/tools/projflow/runner.mjs +0 -269
  545. package/tools/projflow/service.mjs +0 -742
  546. package/tools/track/DESIGN.md +0 -513
  547. package/tools/track/backfill-provenance.mjs +0 -116
  548. package/tools/track/budget.mjs +0 -8
  549. package/tools/track/check-rig.mjs +0 -209
  550. package/tools/track/diagnostics.schema.json +0 -60
  551. package/tools/track/export-rig.mjs +0 -256
  552. package/tools/track/fallback.mjs +0 -7
  553. package/tools/track/fk-parity-fixture.mjs +0 -161
  554. package/tools/track/gate.mjs +0 -349
  555. package/tools/track/masks.mjs +0 -318
  556. package/tools/track/metrics.mjs +0 -201
  557. package/tools/track/publish-obs.mjs +0 -108
  558. package/tools/track/py/check_env.py +0 -61
  559. package/tools/track/py/eval_lr.py +0 -229
  560. package/tools/track/py/lr_viterbi.py +0 -294
  561. package/tools/track/py/masks.py +0 -405
  562. package/tools/track/py/objective.py +0 -382
  563. package/tools/track/py/rig.py +0 -508
  564. package/tools/track/py/scene.py +0 -326
  565. package/tools/track/py/test_joint_indices.py +0 -123
  566. package/tools/track/py/test_lr_viterbi.py +0 -195
  567. package/tools/track/py/test_masks.py +0 -229
  568. package/tools/track/py/test_rig.py +0 -288
  569. package/tools/track/py/test_scene.py +0 -354
  570. package/tools/track/py/test_track.py +0 -328
  571. package/tools/track/py/track.py +0 -411
  572. package/tools/track/remote.mjs +0 -140
  573. package/tools/track/rig-dump.mjs +0 -227
  574. package/tools/track/run-box-tests.mjs +0 -14
  575. package/tools/track/run-box.mjs +0 -168
  576. package/tools/track/setup-box.sh +0 -59
  577. package/tools/track/study-2d.mjs +0 -728
  578. /package/src/{ardy → motion}/collision-blockers.js +0 -0
  579. /package/src/{ardy → motion}/ground.js +0 -0
  580. /package/src/{ardy → motion}/ik-drag.js +0 -0
  581. /package/src/{ardy → motion}/ik-key-json.js +0 -0
  582. /package/src/{ardy → motion}/ik.js +0 -0
  583. /package/src/{ardy → motion}/physics-surface.js +0 -0
  584. /package/src/{ardy → motion}/playback-clock.js +0 -0
  585. /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
+ }