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,128 @@
1
+ // The arm turns a held object, not the wrist alone (docs/motion-api-v3.1.md C14, lane K8). When a
2
+ // hand must take a world rotation (the object leads, hold.js), most of a held object's angle comes
3
+ // from the elbow's swivel about the shoulder-wrist axis and from the forearm's turn about its own
4
+ // axis (pronation and supination, the twist); the wrist adds the rest (flex and deviation).
5
+ // swivel the elbow on the circle twoBone can put it on for this wrist: POLES poles are scored
6
+ // and their soft minimum (a Boltzmann-weighted mean pole) taken, so two near-equal
7
+ // poles blend instead of flipping; ../hold-plan.js smooths it over the frames
8
+ // score each turn's squared share of its range, plus a steep price past the limits, plus a
9
+ // light pull toward the pole the channels gave (a keyed elbow_<side> is a preference)
10
+ // and the same steep price past SWING degrees from it (the upper arm's own roll limit:
11
+ // a straight arm swung half a turn would twist the shoulder, not hold the object)
12
+ // twist the forearm bone takes FOREARM_SHARE of the turn about the forearm and the hand the
13
+ // rest, so the skin twists along the forearm instead of at the wrist alone
14
+ // Everything here is analytic on the pose (no bone moves): twoBone's result does not depend on the
15
+ // arm it starts from, so the forearm's frame for a pole is its rest frame aligned onto it.
16
+ import { add, sub, scale, norm, dot, cross, len, mul, T, apply, align, clamp, quatFromMat, matFromQuat } from "./math.js";
17
+ import { qmul, qconj, qlog, qexp } from "./quat.js";
18
+ import { palmAxes, foldPoint, backOf } from "./limbs.js";
19
+
20
+ /** Wrist limits in degrees, relative to the hand at rest on the forearm (twist: the forearm's turn). */
21
+ export const WRIST_LIMITS = { twist: [-85, 85], flex: [-70, 70], dev: [-35, 20] };
22
+ /**
23
+ * The comfortable part of each range, what observations.wrist counts frames past (qa/wrist-obs.js):
24
+ * daily tasks keep the wrist within about 40 deg of flex and extension and 25 ulnar / 15 radial
25
+ * deviation (Ryu et al.; Gates et al. 2016) and the forearm within 50 deg of pronation and supination
26
+ * (Morrey 1981).
27
+ */
28
+ export const WRIST_COMFORT = { twist: [-50, 50], flex: [-40, 40], dev: [-25, 15] };
29
+ /** The share of the turn about the forearm the forearm bone takes (the hand takes the rest). */
30
+ export const FOREARM_SHARE = 0.5;
31
+ /** The direction an arm's elbow points at rest, carried by the bones (pose.js, twoBone). */
32
+ export const ELBOW_REST = [0, 0, -1];
33
+ const POLES = 72, TEMP = 0.02, OVER = 400, PULL = 0.5, SWING = 100 / (180 / Math.PI), DEG = 180 / Math.PI;
34
+ /**
35
+ * The shoulder's comfort (lane elbow): upper-arm elevation, degrees from hanging straight down (90 level, more
36
+ * the elbow over the shoulder). Daily handling (drinking, eating, a cup or a can at the mouth) keeps the upper
37
+ * arm low and forward, well below the level (Magermans et al. 2005); only overhead work lifts it. A pole pays
38
+ * SHOULDER_PRICE times the squared share of SHOULDER_SPAN it lifts the elbow past SHOULDER_COMFORT on the comfort
39
+ * landscape only (a carry whose aim keys may lean, key-aim.js): priced on every hold, it moved the bench's recompiled
40
+ * cup and can takes into wrist limits and QA (their keys were written for the elbow-up hold).
41
+ */
42
+ const SHOULDER_COMFORT = 70, SHOULDER_SPAN = 30, SHOULDER_PRICE = 2;
43
+ /** A wrist need's price against the comfortable range first and the limits second (the shape grip-choice.js prices a grip with): 1 at the comfortable edge, steep past it. */
44
+ export function comfortPrice(need, steep = 8, over = 400) {
45
+ let cost = 0;
46
+ for (const k of ["twist", "flex", "dev"]) {
47
+ const v = need[k], c = v >= 0 ? WRIST_COMFORT[k][1] : -WRIST_COMFORT[k][0], L = v >= 0 ? WRIST_LIMITS[k][1] : -WRIST_LIMITS[k][0], a = Math.abs(v);
48
+ cost += a <= c ? (a / c) ** 2 : 1 + steep * ((Math.min(a, L) - c) / Math.max(1e-6, L - c)) ** 2 + (a > L ? over * ((a - L) / L) ** 2 : 0);
49
+ }
50
+ return cost;
51
+ }
52
+
53
+ /** The shoulder's price for an elbow at E with the shoulder at S (SHOULDER_COMFORT above). */
54
+ export function shoulderPrice(S, E) {
55
+ const u = norm(sub(E, S)), el = Math.acos(clamp(-u[1], -1, 1)) * DEG;
56
+ return SHOULDER_PRICE * (Math.max(0, el - SHOULDER_COMFORT) / SHOULDER_SPAN) ** 2;
57
+ }
58
+
59
+ /** What the hand needs from neutral (its rest on the forearm) to goal, degrees { twist, flex, dev }; axT the forearm axis, l the palm's l axis at neutral. */
60
+ export function wristNeed(neutral, goal, axT, l) {
61
+ let q = quatFromMat(mul(goal, T(neutral)));
62
+ if (q[3] < 0) q = q.map(v => -v);
63
+ const twist = 2 * Math.atan2(dot([q[0], q[1], q[2]], axT), q[3]), spin = qexp(scale(axT, twist));
64
+ const swing = qlog(qmul(q, qconj(spin))), lTwisted = apply(matFromQuat(spin), l);
65
+ const axF = norm(sub(lTwisted, scale(axT, dot(lTwisted, axT)))), axD = cross(axT, axF);
66
+ return { twist: twist * DEG, flex: dot(swing, axF) * DEG, dev: dot(swing, axD) * DEG, axF, axD };
67
+ }
68
+
69
+ /**
70
+ * The strain of a wrist need: each turn's squared share of its half range, plus OVER times the
71
+ * squared share past a limit; `margin` (a share of the half range) moves the limits that far in.
72
+ */
73
+ function strainOf(need, margin = 0) {
74
+ let cost = 0;
75
+ for (const [k, [lo, hi]] of Object.entries(WRIST_LIMITS)) {
76
+ const z = (need[k] - (lo + hi) / 2) / ((hi - lo) / 2), over = Math.max(0, Math.abs(z) - 1 + margin);
77
+ cost += z * z + OVER * over * over;
78
+ }
79
+ return cost;
80
+ }
81
+
82
+ /**
83
+ * The arm's swivel landscape for hand rotation `goal` with the wrist at W, on pose s (the plain
84
+ * arm of `side`): at(th) -> { pv, need, elbow, cost } for the elbow th radians about the shoulder-wrist
85
+ * axis from `ref` (the bend direction the channels gave), angleOf(world direction) -> th,
86
+ * softmin() -> the th of the Boltzmann-weighted mean of POLES poles, miss the metres the arm is
87
+ * short of W (a wrist nearer the shoulder than the elbow folds is no miss here; hold.js holdCost
88
+ * prices it). `margin` tightens the wrist limits the strain prices (strainOf).
89
+ */
90
+ export function landscape(sk, s, side, goal, W, ref, restBend, margin = 0, comfort = false) {
91
+ const I = sk.index, a = I[`${side}Arm`], fa = I[`${side}ForeArm`], h = I[`${side}Hand`], S = s.P[a];
92
+ const A = len(sub(s.P[fa], S)), B = len(sub(s.P[h], s.P[fa]));
93
+ // the elbow where twoBone puts it: the wrist no further folded than limbs.js foldPoint lets it
94
+ const v = sub(foldPoint(S, W, A, B, backOf(sk, s)), S), d = len(v), u = scale(v, 1 / Math.max(d, 1e-9)), dc = clamp(d, Math.abs(A - B) + 1e-4, (A + B) * 0.9995);
95
+ const along = (A * A - B * B + dc * dc) / (2 * dc), rad = Math.sqrt(Math.max(A * A - along * along, 0));
96
+ const off = x => sub(x, scale(u, dot(x, u))), e1 = norm(len(off(ref)) > 1e-6 ? off(ref) : cross(u, Math.abs(u[1]) < 0.9 ? [0, 1, 0] : [1, 0, 0]));
97
+ const e2 = cross(u, e1), Wn = add(S, scale(u, dc));
98
+ const rw = sk.restWorldRot, faDir = apply(T(rw[fa]), sub(sk.restWorldPos[h], sk.restWorldPos[fa])), faBend = apply(T(rw[fa]), restBend);
99
+ const neutralRel = mul(T(rw[fa]), rw[h]), lLocal = apply(T(s.R[h]), palmAxes(sk, s, side).l);
100
+ const at = th => {
101
+ const pv = add(scale(e1, Math.cos(th)), scale(e2, Math.sin(th))), fdir = sub(Wn, add(S, add(scale(u, along), scale(pv, rad))));
102
+ const neutral = mul(align(faDir, faBend, fdir, pv), neutralRel), need = wristNeed(neutral, goal, norm(fdir), apply(neutral, lLocal));
103
+ const z = Math.abs(Math.atan2(Math.sin(th), Math.cos(th))) / SWING;
104
+ const elbow = add(S, add(scale(u, along), scale(pv, rad)));
105
+ // comfort (a carry that lets its aim lean, key-aim.js): the wrist priced against its comfortable range and the shoulder's price
106
+ return { pv, need, elbow, cost: (comfort ? comfortPrice(need) + shoulderPrice(S, elbow) : strainOf(need, margin)) + PULL * (1 - Math.cos(th)) + OVER * Math.max(0, z - 1) ** 2 };
107
+ };
108
+ const angleOf = x => (len(off(x)) > 1e-6 ? Math.atan2(dot(x, e2), dot(x, e1)) : 0);
109
+ const softmin = () => {
110
+ const all = Array.from({ length: POLES }, (_, i) => at((2 * Math.PI * i) / POLES)), low = Math.min(...all.map(p => p.cost));
111
+ const mean = all.reduce((m, p) => add(m, scale(p.pv, Math.exp(-(p.cost - low) / TEMP))), [0, 0, 0]);
112
+ return len(mean) > 1e-3 ? angleOf(mean) : (2 * Math.PI * all.findIndex(p => p.cost === low)) / POLES;
113
+ };
114
+ return { at, angleOf, softmin, miss: Math.max(0, d - dc) };
115
+ }
116
+
117
+ /**
118
+ * The elbow pole for hand rotation `goal` with the wrist at W (landscape above). opts.toward, a
119
+ * world direction, sets the pole instead of the soft minimum (a planned pole, ../hold-plan.js);
120
+ * opts.w (0..1, the keys' blend weight) scales the swing from ref, so the elbow eases out of and
121
+ * back into the plain arm; opts.margin as for landscape. Returns { pole, need, cost, miss }: pole
122
+ * a world direction for twoBone.
123
+ */
124
+ export function swivel(sk, s, side, goal, W, ref, restBend, { toward, w = 1, margin = 0, comfort = false } = {}) {
125
+ const land = landscape(sk, s, side, goal, W, ref, restBend, margin, comfort), best = toward ? land.angleOf(toward) : land.softmin();
126
+ const pick = land.at(w * Math.atan2(Math.sin(best), Math.cos(best)));
127
+ return { pole: pick.pv, need: pick.need, cost: pick.cost, miss: land.miss, elbow: pick.elbow };
128
+ }
@@ -0,0 +1,125 @@
1
+ // Keyed channels -> per-frame values. A channel is interpolated only through the keys that
2
+ // set it; before its first key and after its last it holds. Numbers, number arrays and
3
+ // {preset: weight} objects are supported. `step` keys jump at that frame (visibility switches
4
+ // and the like).
5
+ // Flow (spec.flow): the channels that place the body against the world or a target (feet,
6
+ // root, yaw, hand targets and weights, poles, fingers, objects) are monotone cubic / PCHIP and on
7
+ // time, so a held value never overshoots and a contact lands on its key. The body's own swing
8
+ // (FLOW_SMOOTH: spine, head, arm swing and bend, wrist) carries its speed through a key instead of
9
+ // stopping on it (Catmull-Rom, the overshoot past a key bounded), and trails the body
10
+ // (FLOW_LAG frames: the head behind the body, the arms behind both), the trail easing out over the
11
+ // take's last frames so every value is reached. An arm on its IK (armw) reaches for a target, so
12
+ // flow is gone by the time the arm is a quarter of the way onto it: an arm's swing and bend by its
13
+ // own armw, the spine (which carries the shoulders) and the head (the hands work near it) by the
14
+ // larger of the two; a reach or a held prop lands as keyed. A hold (two equal keys in a row) stays
15
+ // exactly flat either way.
16
+
17
+ function pchipSlopes(x, y) {
18
+ const n = y.length, m = new Array(n).fill(0);
19
+ for (let i = 1; i < n - 1; i++) {
20
+ const h0 = x[i] - x[i - 1], h1 = x[i + 1] - x[i];
21
+ const d0 = (y[i] - y[i - 1]) / h0, d1 = (y[i + 1] - y[i]) / h1;
22
+ if (d0 * d1 > 0) {
23
+ const w1 = 2 * h1 + h0, w2 = h1 + 2 * h0;
24
+ m[i] = (w1 + w2) / (w1 / d0 + w2 / d1);
25
+ }
26
+ }
27
+ return m;
28
+ }
29
+
30
+ function pchipEval(x, y, m, q) {
31
+ if (x.length === 1 || q <= x[0]) return y[0];
32
+ if (q >= x[x.length - 1]) return y[y.length - 1];
33
+ let i = 0;
34
+ while (q > x[i + 1]) i++;
35
+ const h = x[i + 1] - x[i], t = (q - x[i]) / h;
36
+ const t2 = t * t, t3 = t2 * t;
37
+ return (2 * t3 - 3 * t2 + 1) * y[i] + (t3 - 2 * t2 + t) * h * m[i] + (-2 * t3 + 3 * t2) * y[i + 1] + (t3 - t2) * h * m[i + 1];
38
+ }
39
+
40
+ const FLOW_SMOOTH = /^(spine|head)_(yaw|pitch|roll)$|^(aswing|abend)_[lr]$/;
41
+ const FLOW_LAG = { spine: 0, head: 2, arms: 3 };
42
+ const OVER_FRAC = 0.15, OVER_MAX = 6; // a smooth segment's overshoot: at most this share of its change, and degrees
43
+ const ikSides = ch => (/^(spine|head)_/.test(ch) ? ["l", "r"] : /^(aswing|abend)_([lr])$/.test(ch) ? [ch.slice(-1)] : []);
44
+ const lagFamily = ch => (/^spine_/.test(ch) ? "spine" : /^head_/.test(ch) ? "head" : /^(aswing|abend)_[lr]$/.test(ch) ? "arms" : null);
45
+
46
+ /** spec.flow -> { smooth, lag: { spine, head, arms } }: undefined is the default, false turns both off. */
47
+ export function flowOptions(flow) {
48
+ if (flow === false) return { smooth: false, lag: { spine: 0, head: 0, arms: 0 } };
49
+ const f = flow ?? {}, lag = f.lag === false ? 0 : f.lag;
50
+ const each = typeof lag === "number" ? { spine: lag, head: lag, arms: lag } : { ...FLOW_LAG, ...(lag ?? {}) };
51
+ return { smooth: f.smooth !== false, lag: each };
52
+ }
53
+
54
+ /** Catmull-Rom slopes, flat at a hold and at the ends, each segment's overshoot bounded. */
55
+ function smoothSlopes(x, y) {
56
+ const n = y.length, m = new Array(n).fill(0);
57
+ for (let i = 1; i < n - 1; i++) if (y[i] !== y[i - 1] && y[i] !== y[i + 1]) m[i] = (y[i + 1] - y[i - 1]) / (x[i + 1] - x[i - 1]);
58
+ for (let i = 0; i < n - 1; i++) {
59
+ const h = x[i + 1] - x[i], lo = Math.min(y[i], y[i + 1]), hi = Math.max(y[i], y[i + 1]), limit = Math.min(OVER_MAX, OVER_FRAC * (hi - lo));
60
+ for (let tries = 0; tries < 12; tries++) {
61
+ let over = 0;
62
+ for (let k = 1; k < 16; k++) {
63
+ const v = pchipEval([x[i], x[i + 1]], [y[i], y[i + 1]], [m[i], m[i + 1]], x[i] + (h * k) / 16);
64
+ over = Math.max(over, v - hi, lo - v);
65
+ }
66
+ if (over <= limit + 1e-9) break;
67
+ m[i] *= 0.7; m[i + 1] *= 0.7;
68
+ }
69
+ }
70
+ return m;
71
+ }
72
+
73
+ const shapeOf = v => (Array.isArray(v) ? "vec" : v && typeof v === "object" ? "map" : "num");
74
+
75
+ /** keys: [{ f, set: { channel: value }, step?: [channel...] }] -> { channel: Array(frameCount) }; flow: flowOptions(spec.flow). */
76
+ export function bakeChannels(keys, frameCount, flow = flowOptions()) {
77
+ const byChannel = new Map();
78
+ for (const key of [...keys].sort((a, b) => a.f - b.f)) {
79
+ for (const [ch, value] of Object.entries(key.set ?? {})) {
80
+ if (!byChannel.has(ch)) byChannel.set(ch, []);
81
+ byChannel.get(ch).push({ f: key.f, value, step: key.step?.includes(ch) });
82
+ }
83
+ }
84
+ const out = {}, plain = {};
85
+ for (const [ch, list] of byChannel) {
86
+ // later keys at the same frame win
87
+ const pts = [...new Map(list.map(k => [k.f, k])).values()];
88
+ const shape = shapeOf(pts[0].value);
89
+ const comps = shape === "vec" ? pts[0].value.length : 0;
90
+ const series = shape === "num" ? [pts.map(p => p.value)]
91
+ : shape === "vec" ? Array.from({ length: comps }, (_, i) => pts.map(p => p.value[i]))
92
+ : [...new Set(pts.flatMap(p => Object.keys(p.value)))].map(name => ({ name, y: pts.map(p => p.value[name] ?? 0) }));
93
+ const x = pts.map(p => p.f), smooth = flow.smooth && FLOW_SMOOTH.test(ch), lag = flow.lag[lagFamily(ch)] ?? 0;
94
+ // the trail eases out over the last 2 * lag frames, so the take ends on the last value
95
+ const at = f => (lag > 0 ? f - lag * Math.min(1, (frameCount - 1 - f) / (2 * lag)) : f);
96
+ const evalSeries = (y, flowing = true) => {
97
+ const m = smooth && flowing ? smoothSlopes(x, y) : pchipSlopes(x, y);
98
+ return q => {
99
+ const f = flowing ? at(q) : q;
100
+ // a step key holds the previous value right up to its frame
101
+ for (let i = 1; i < pts.length; i++) if (pts[i].step && f >= x[i - 1] && f < x[i]) return y[i - 1];
102
+ return pchipEval(x, y, m, f);
103
+ };
104
+ };
105
+ const frames = new Array(frameCount);
106
+ if (shape === "num") { const e = evalSeries(series[0]); for (let f = 0; f < frameCount; f++) frames[f] = e(f); }
107
+ else if (shape === "vec") { const es = series.map(evalSeries); for (let f = 0; f < frameCount; f++) frames[f] = es.map(e => e(f)); }
108
+ else {
109
+ const es = series.map(({ name, y }) => [name, evalSeries(y)]);
110
+ for (let f = 0; f < frameCount; f++) frames[f] = Object.fromEntries(es.map(([n, e]) => [n, e(f)]));
111
+ }
112
+ out[ch] = frames;
113
+ if (shape === "num" && (smooth || lag > 0) && ikSides(ch).length) { const e = evalSeries(series[0], false); plain[ch] = Array.from({ length: frameCount }, (_, f) => e(f)); }
114
+ }
115
+ // flow fades out as the arm goes onto its IK (the spine with either arm)
116
+ for (const [ch, keyed] of Object.entries(plain)) {
117
+ const ws = ikSides(ch).map(k => out[`armw_${k}`]).filter(Boolean);
118
+ if (!ws.length) continue;
119
+ out[ch] = out[ch].map((v, f) => { const w = Math.min(1, 4 * Math.max(0, ...ws.map(a => a[f]))); return v + (keyed[f] - v) * w * (2 - w); });
120
+ }
121
+ return out;
122
+ }
123
+
124
+ /** Frame f's channel dict. */
125
+ export const channelsAt = (baked, f) => Object.fromEntries(Object.entries(baked).map(([k, v]) => [k, v[f]]));
@@ -0,0 +1,205 @@
1
+ // Motion spec -> baked frames. Deterministic and pure: the same spec and scene give the same
2
+ // frames, so the agent can edit keys and recompile in a loop.
3
+ //
4
+ // spec = { schema: "cozyclay.motion-spec.v1", fps, frames, keys, anchors, props, fits, objects, generators, checks }
5
+ // keys: [{ f, set: { channel: value }, step?: [channel] }] (see pose.js for channel names)
6
+ // hand mix: hand_<l|r> (world point) blended with w_<anchor>_<l|r> weights (anchors.js)
7
+ // fits: [{ side, anchor, effector: { prop, point } | { joints: [...] }, offset? }]
8
+ // moves the wrist so the effector lands on the anchor whenever that weight is on
9
+ // objects: { id: { pivot: [x,y,z], axis: [x,y,z], channel: "door" } } hinge angle in degrees
10
+ // { id: { prop, grab?, release?, at?, along?, visible?, keys? } } carried (../object-pose.js);
11
+ // keys lead the holding hand (../object-keys.js, hold.js)
12
+ // { id: { visible?, level? } } a switch: shown/hidden and dimmed, never moved (C15)
13
+ // { id: { follow, offset?, aim?, visible?, level? } } rides a point of the body (C16, ../follow.js)
14
+ // { id: { keys?, fall?, visible?, level? } } moves on its own: keys and gravity falls (C17, ../free-motion.js)
15
+ // id is a body or a group (Empty); a group moves every body under it
16
+ // generators: [{ type: "walk", f0, ... }] (walk.js)
17
+ // fix: { reach, slide, penetration, retime } deterministic auto-fixes (../fix/)
18
+ // flow: { smooth, lag } | false the body's swing carries through keys and trails (channels.js)
19
+ // idle: false turns off the idle layer (idle.js: a held pose breathes, drifts its head, shifts its weight)
20
+ // ctx = { sk, scene: { objects: { id: { pos, R, half, ancestors } } } }; out.ctx.scene keeps the scene (object-pose.js)
21
+ import { add, sub, scale, norm, len, dot, apply, axisRot } from "./math.js";
22
+ import { isCarried, isFollow, hingePose, movesRecord, compose, relative } from "../carried.js";
23
+ import { isFree, freePoseAt } from "../free-motion.js";
24
+ import { groupsOf } from "../scene.js";
25
+ import { pose, SIDES } from "./pose.js";
26
+ import { gripHand } from "./grip.js";
27
+ import { bakeChannels, channelsAt, flowOptions } from "./channels.js";
28
+ import { idleLayer, withIdle, busyHands } from "./idle.js";
29
+ import { resolveAnchor, measureProps } from "./anchors.js";
30
+ import { expandGenerators } from "./walk.js";
31
+ import { steadyHead } from "./head-steady.js";
32
+ import { fixOptions, applyFixes } from "../fix/index.js";
33
+ import { fitWeight } from "../fix/self.js";
34
+ import { holdHook } from "../object-keys.js";
35
+ import { HAND_CLEAR, keepOut, wristBody } from "../body.js";
36
+ import { objectPoseAt, handOf, restingAt } from "../object-pose.js";
37
+
38
+ const smooth = x => x * x * (3 - 2 * x);
39
+
40
+ /**
41
+ * Scene boxes by frame. A body moves with its own spec.objects entry or its nearest group's (a
42
+ * switch entry moves nothing; hinge: turned; free (C17): where its keys and falls put it, under the time map `mapOf()`;
43
+ * carry and follow: left at its scene pose and marked `carried` with the moving id). `obstacles` drops the carried
44
+ * ones: QA, refs and object anchors read a carry where it rides (object-pose.js objectPoseAt, qa/object-checks.js);
45
+ * a follow entry rides the body and is no obstacle.
46
+ */
47
+ function objectTracks(spec, scene, baked, mapOf = () => undefined) {
48
+ const cache = new Map(), kept = new Map();
49
+ let map = mapOf();
50
+ const all = f => {
51
+ // a retime maps free keys and falls too: frames read before it are read again
52
+ if (mapOf() !== map) { map = mapOf(); cache.clear(); kept.clear(); }
53
+ if (cache.has(f)) return cache.get(f);
54
+ const out = {};
55
+ for (const [id, o] of Object.entries(scene?.objects ?? {})) {
56
+ const mover = [id, ...(o.ancestors ?? [])].find(k => movesRecord(spec.objects?.[k])), entry = spec.objects?.[mover];
57
+ if (!entry) out[id] = o;
58
+ else if (isCarried(entry) || isFollow(entry)) out[id] = { ...o, carried: mover };
59
+ else if (isFree(entry)) out[id] = { ...o, ...compose(freePoseAt({ spec, scene, timeMap: map }, mover, f), relative(scene.objects[mover] ?? groupsOf(scene)[mover], o)) };
60
+ else out[id] = { ...o, ...hingePose(entry, o, baked, f) };
61
+ }
62
+ cache.set(f, out);
63
+ return out;
64
+ };
65
+ const obstacles = f => {
66
+ if (mapOf() !== map) all(f);
67
+ if (!kept.has(f)) kept.set(f, Object.fromEntries(Object.entries(all(f)).filter(([, box]) => !box.carried)));
68
+ return kept.get(f);
69
+ };
70
+ return { all, obstacles };
71
+ }
72
+
73
+ function effectorPoint(sk, s, diag, eff) {
74
+ if (eff.prop) return diag.props?.[eff.prop]?.[eff.point];
75
+ const js = eff.joints ?? ["RightHandIndex2", "RightHandMiddle2"];
76
+ return scale(js.map(j => s.P[sk.index[j]]).reduce((a, p) => add(a, p), [0, 0, 0]), 1 / js.length);
77
+ }
78
+
79
+ /** `lead` false solves without the object keys; a fit's own hand is solved without them (the keys override it). */
80
+ function hooksFor(ctx, c, f, inFit = false, lead = true, fitSide = null) {
81
+ const { sk } = ctx;
82
+ return {
83
+ softReach: ctx.softReach,
84
+ // a fit's passes find where the wrist must go with the elbow unstopped; the final pose stops its fold (limbs.js) and leaves the residual
85
+ foldFree: inFit,
86
+ hold: lead && ctx.hold ? (k, s) => (k === fitSide ? null : ctx.hold.lead(k, f, s, c)) : undefined,
87
+ // auto-fix offsets ride on the final pose only, so a fit never cancels them
88
+ push: inFit ? undefined : k => ctx.push?.[k]?.get(f),
89
+ swivel: k => ctx.swivel?.[k]?.get(f), // in a fit's passes too: a turn that stops at the wrist's limit moves the fit's point (wrist-clamp.js)
90
+ skinPush: inFit ? undefined : k => ctx.skinPush?.[k]?.get(f),
91
+ handTarget(k, s, diag) {
92
+ diag.frame = f;
93
+ const fkHand = s.P[sk.index[k === "l" ? "LeftHand" : "RightHand"]];
94
+ const terms = [];
95
+ for (const [ch, w] of Object.entries(c)) {
96
+ const m = /^w_(.+)_([lr])$/.exec(ch);
97
+ if (!m || m[2] !== k || !(w > 1e-6)) continue;
98
+ terms.push([w, add(resolveAnchor(ctx, m[1], s, diag, c, k), c[`fit_${m[1]}_${k}`] ?? [0, 0, 0])]);
99
+ }
100
+ let tot = terms.reduce((a, [w]) => a + w, 0);
101
+ if (tot > 1) { for (const t of terms) t[0] /= tot; tot = 1; }
102
+ let target = scale(c[`hand_${k}`] ?? fkHand, 1 - tot);
103
+ for (const [w, p] of terms) target = add(target, scale(p, w));
104
+ // Between targets, arc around the torso instead of cutting through it.
105
+ const trans = Math.max(0, ...terms.map(([w]) => smooth(Math.min(1, Math.min(w, 1 - w) * 3))));
106
+ if (trans > 0) {
107
+ const a = s.P[sk.index.Spine], b = s.P[sk.index.Neck], u = sub(b, a);
108
+ const t = Math.min(1, Math.max(0, dot(sub(target, a), u) / dot(u, u))), q = add(a, scale(u, t));
109
+ const r = ctx.spec.detourRadius ?? 0.25, dv = sub(target, q), d = len(dv);
110
+ if (d < r) {
111
+ const fwd = apply(axisRot([0, 1, 0], c.yaw ?? 0), [0, 0, 1]);
112
+ const dir = norm(add(d > 1e-6 ? scale(dv, 1 / d) : fwd, scale(fwd, 0.6)));
113
+ target = add(target, scale(sub(add(q, scale(dir, r)), target), trans));
114
+ }
115
+ }
116
+ // a wrist target inside the body (a pocket on a squatting thigh, a path through the chest) goes out onto its skin
117
+ return target;
118
+ },
119
+ // the wrist target, blended with the FK hand, inside the body goes out onto its skin, twice at most (out of
120
+ // one bone, then the next): a pocket the squat moved into the thigh, a path through the chest, a hand leaving
121
+ // a thigh for its FK rest. A measured target (a fit's anchor: the mouth; or a prop another hand's fit is on:
122
+ // the cigarette under the flame) stays where its goal needs it, and the move fades in and out with its weight;
123
+ // an arm coming in from FK moves it no more than its IK weight.
124
+ keepOut: ctx.keepOut && !inFit ? (k, s, target) => {
125
+ const free = (1 - fitWeight(ctx.spec, c, k)) * Math.min(1, Math.max(0, c[`armw_${k}`] ?? 0));
126
+ for (let i = 0; i < 2 && free > 1e-3; i++) {
127
+ const moved = keepOut(ctx.keepOut.body, sk, s, target, HAND_CLEAR, ctx.keepOut.skip);
128
+ if (moved === target) break;
129
+ target = add(target, scale(sub(moved, target), free));
130
+ }
131
+ return target;
132
+ } : undefined,
133
+ afterArm: (k, s, diag) => measureProps(ctx, k, s, diag),
134
+ // a fit's passes only place the wrist; the final pose (and a plain one) turns the hand onto the body
135
+ grip: inFit ? undefined : (k, s, diag) => gripHand(ctx, sk, s, SIDES[k], k, c, f, diag),
136
+ };
137
+ }
138
+
139
+ /** One frame with every active fit solved (fits run in spec order); lead false leaves the object keys out. */
140
+ export function solveFrame(ctx, c0, f, lead = true) {
141
+ const c = { ...withIdle(c0, ctx.idle, f) };
142
+ // a working hand keeps its forearm's slant (pose.js arm_hang)
143
+ for (const k of ["l", "r"]) if (ctx.busy?.[k]) c[`hangoff_${k}`] = ctx.busy[k][f];
144
+ for (const fit of ctx.spec.fits ?? []) {
145
+ const wch = `w_${fit.anchor}_${fit.side}`;
146
+ if (!(c[wch] > 1e-4)) continue;
147
+ const full = { ...c, [wch]: 1, [`armw_${fit.side}`]: 1 };
148
+ for (const ch of Object.keys(full)) if (ch !== wch && new RegExp(`^w_.+_${fit.side}$`).test(ch)) full[ch] = 0;
149
+ let corr = [0, 0, 0];
150
+ for (let i = 0; i < 6; i++) {
151
+ full[`fit_${fit.anchor}_${fit.side}`] = corr;
152
+ const { s, diag } = pose(ctx.sk, full, hooksFor(ctx, full, f, true, lead, fit.side));
153
+ const eff = effectorPoint(ctx.sk, s, diag, fit.effector);
154
+ const goal = add(resolveAnchor(ctx, fit.anchor, s, diag, full, fit.side), fit.offset ?? [0, 0, 0]);
155
+ if (!eff) break;
156
+ corr = add(corr, sub(goal, eff));
157
+ }
158
+ c[`fit_${fit.anchor}_${fit.side}`] = corr;
159
+ }
160
+ const { s, diag } = pose(ctx.sk, c, hooksFor(ctx, c, f, false, lead));
161
+ // readers of the solved frame (refs, checks, hints) resolve its anchors at f, IK arms or not
162
+ diag.frame = f;
163
+ return { s, diag, c };
164
+ }
165
+
166
+ /**
167
+ * opts.window = [a, b] solves only frames a..b (out.f0 = a, out.frames[i] is frame a + i);
168
+ * opts.keys replaces the generator expansion; opts.fixes = false skips the auto-fix pass.
169
+ */
170
+ export function compileMotion(spec, { sk, scene }, opts = {}) {
171
+ if (spec?.schema !== "cozyclay.motion-spec.v1") throw new Error("MOTION_SPEC_INVALID: schema must be cozyclay.motion-spec.v1");
172
+ const frameCount = Math.round(spec.frames);
173
+ if (!(frameCount >= 1 && frameCount <= 20000)) throw new Error("MOTION_SPEC_INVALID: frames must be 1..20000");
174
+ const fix = fixOptions(spec.fix);
175
+ const [a, b] = opts.window ? [Math.max(0, opts.window[0]), Math.min(frameCount - 1, opts.window[1])] : [0, frameCount - 1];
176
+ const ctx = { sk, spec, scene, softReach: fix.reach, push: { l: new Map(), r: new Map() }, swivel: { l: new Map(), r: new Map() }, skinPush: { l: new Map(), r: new Map() }, keepOut: fix.self ? wristBody(sk) : null };
177
+ const out = { ctx, fps: spec.fps ?? 30, frameCount, f0: a, frames: [] };
178
+ // object anchors read the pose refs read (object-pose.js), on the frame being solved
179
+ ctx.objectPose = (id, f, now) => objectPoseAt(out, id, f, now);
180
+ ctx.objectHand = (id, f) => handOf(out, id, f);
181
+ // a keyed carry's grab offset is taken where it rests at grab (object-keys.js; a fall before grab: where it landed)
182
+ ctx.restingAt = (id, f) => restingAt(out, id, f);
183
+ const engine = {
184
+ rebake(keys) {
185
+ out.keys = keys;
186
+ out.baked = bakeChannels(keys, frameCount, flowOptions(spec.flow));
187
+ steadyHead(out.baked, keys, frameCount);
188
+ // a held pose breathes and shifts its weight (idle.js): offsets solveFrame adds, contacts and out.baked stay as keyed
189
+ ctx.idle = idleLayer(out.baked, { spec, sk, frameCount });
190
+ ctx.busy = busyHands(out.baked, spec, frameCount, out.fps);
191
+ const tracks = objectTracks(spec, scene, out.baked, () => out.timeMap);
192
+ ctx.objectsAt = tracks.all;
193
+ out.objectsAt = a ? f => tracks.obstacles(f + a) : tracks.obstacles;
194
+ },
195
+ solve(frames) { for (const f of frames) if (f >= a && f <= b) out.frames[f - a] = solveFrame(ctx, channelsAt(out.baked, f), f); },
196
+ all: () => Array.from({ length: b - a + 1 }, (_, i) => a + i),
197
+ };
198
+ ctx.hold = holdHook(ctx, out, f => solveFrame(ctx, channelsAt(out.baked, f), f, false), f => solveFrame(ctx, channelsAt(out.baked, f), f));
199
+ engine.rebake(opts.keys ?? expandGenerators(spec, sk));
200
+ engine.solve(engine.all());
201
+ out.fixed = opts.fixes === false ? {} : applyFixes(out, fix, engine, { whole: !opts.window });
202
+ // a retime maps the key frames only once it is done: solve the keyed frames again on the final map
203
+ if (ctx.hold && out.timeMap) engine.solve(engine.all());
204
+ return out;
205
+ }
@@ -0,0 +1,97 @@
1
+ // The walk's rhythm (walk.js): when each step lifts off, how long the foot is in the air, how far
2
+ // it goes, how high it lifts and how much the arms swing. A person does not give every step the
3
+ // same time: the first steps build up, the last one slows into the stop, one side is a little
4
+ // quicker than the other, no two steps are alike, and a turn takes slower, shorter steps.
5
+ // Deterministic (a hash of the walk's own parameters seeds the step-to-step part) and zero-sum:
6
+ // the lift-offs are moved inside the walk's span (the walk still ends 16 + steps * period + 18
7
+ // frames after f0) and the middle strides keep their sum (the walk ends where it did). The first
8
+ // stride stays firstStep and the last step still brings the feet together.
9
+ // A person's strides also differ in length from one another as wholes (both steps a little longer or shorter),
10
+ // which the step-to-step terms alone do not show: their left/right part cancels over a stride. LENGTH.stride gives
11
+ // each middle stride its own length. (A stride time term is left out: any more time variation lowers the hips'
12
+ // rise and fall that walk-pelvis.js fits under the legs below a person's ~5 cm.)
13
+ // vary scales every term: 0 is the metronome (every step period frames apart, swing frames in the
14
+ // air, step metres long), 1 the default, 2 twice as uneven.
15
+ const clamp = (x, a, b) => Math.min(b, Math.max(a, x));
16
+
17
+ /** text -> a generator of numbers in [-1, 1) (FNV-1a hash, then mulberry32). */
18
+ function noiseOf(text) {
19
+ let h = 2166136261;
20
+ for (let i = 0; i < text.length; i++) h = Math.imul(h ^ text.charCodeAt(i), 16777619);
21
+ let a = h >>> 0;
22
+ return () => {
23
+ a = (a + 0x6d2b79f5) >>> 0;
24
+ let t = Math.imul(a ^ (a >>> 15), a | 1);
25
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
26
+ return (((t ^ (t >>> 14)) >>> 0) / 4294967296) * 2 - 1;
27
+ };
28
+ }
29
+
30
+ // Sizes at vary 1, as fractions of the step's time or length.
31
+ const TIME = { noise: 0.02, side: 0.03, first: 0.22, second: 0.08, last: 0.22, beforeLast: 0.08, turn: 0 };
32
+ const LENGTH = { noise: 0.025, side: 0.012, stride: 0.035, second: 0.05, beforeLast: 0.06, turn: 0.06 };
33
+ const LIFT = { noise: 0.1, first: 0.1, last: 0.25 }, ARM = 0.1;
34
+ const CLOSE = 3; // frames the closing step may swing longer than swing
35
+ const TURN_FULL = 60; // degrees turned in one step at which a step is as slow and short as a turn makes it
36
+
37
+ /**
38
+ * walkRhythm(W, { s0, turn, seed }) -> per step i (0-based, W.steps of them):
39
+ * lift[i] frames after f0 + 16 at which the foot lifts off (lift[0] = 0)
40
+ * air[i] frames that foot is in the air
41
+ * inc[i] metres it advances along the path (inc[0] = firstStep, the last one 0)
42
+ * height[i] its lift, m; arm[i] a factor on the arm swing it lands with
43
+ * W: the walk's fields with defaults; s0 the path position the first step starts from; turn(sa, sb)
44
+ * the degrees the heading turns between two path positions; seed a text naming this walk.
45
+ */
46
+ export function walkRhythm(W, { s0, turn, seed }) {
47
+ const n = W.steps, v = clamp(W.vary ?? 1, 0, 2), next = noiseOf(seed);
48
+ const rows = Array.from({ length: n }, () => ({ time: next(), length: next(), lift: next(), arm: next() }));
49
+ const quick = next() < 0 ? 1 : -1, side = i => (i % 2 === 0 ? quick : -quick);
50
+ // one draw per stride (steps 1-2, 3-4, ...), after the step draws so those stay as they were
51
+ const strides = Array.from({ length: Math.ceil(n / 2) }, () => next()), strideOf = i => strides[Math.floor((i - 1) / 2)];
52
+ const inc0 = Array.from({ length: n }, (_, i) => (i === n - 1 ? 0 : i === 0 ? W.firstStep : W.step));
53
+ let s = s0;
54
+ const turned = inc0.map(d => { const deg = Math.abs(turn(s, s + d)); s += d; return Math.min(1, deg / TURN_FULL); });
55
+ const long = n >= 4;
56
+ // time: a share of the walk's steps * period frames per step, the shares summing to steps
57
+ const share = rows.map((r, i) => {
58
+ if (n < 2) return 1;
59
+ // the first and the last step vary half as much: their build-up and slow-down should show
60
+ const ends = i === 0 || i === n - 1 ? 0.5 : 1;
61
+ let x = ends * (TIME.noise * r.time + TIME.side * side(i)) + TIME.turn * turned[i];
62
+ if (i === 0) x += TIME.first * (1 - turned[0]);
63
+ if (i === 1 && long) x += TIME.second;
64
+ if (i === n - 1) x += TIME.last;
65
+ if (i === n - 2 && long) x += TIME.beforeLast;
66
+ return 1 + v * x;
67
+ });
68
+ const total = share.reduce((a, b) => a + b, 0), span = n * W.period;
69
+ const lift = [];
70
+ for (let i = 0, t = 0; i < n; i++) { lift.push(Math.round(t)); t += (span * share[i]) / total; }
71
+ // air: a long step swings slower, keeping the double support the walk gives every step (period -
72
+ // swing), so the trailing foot leaves the ground no further behind the hips than in an even walk (its
73
+ // knee would snap straight at toe-off); a short step keeps its swing and shortens the double
74
+ // support instead, the trailing heel rising before the other foot lands. The closing step swings at
75
+ // most CLOSE frames longer: the hips go ahead over the other foot while it swings, and a slower
76
+ // trailing foot falls out of the leg's reach; the time left over is standing still
77
+ const support = W.period - W.swing;
78
+ const air = lift.map((t, i) => {
79
+ if (v === 0) return W.swing;
80
+ const gap = (i < n - 1 ? lift[i + 1] : span) - t;
81
+ return Math.min(gap - 2, Math.max(W.swing, gap - support), i === n - 1 ? W.swing + CLOSE : Infinity);
82
+ });
83
+ // length: the middle strides (not the first, not the closing one) keep their sum
84
+ const mid = inc0.map((_, i) => i > 0 && i < n - 1);
85
+ const g = rows.map((r, i) => {
86
+ if (!mid[i]) return 1;
87
+ let x = LENGTH.noise * (0.7 * r.length + 0.3 * r.time) + LENGTH.side * side(i) + LENGTH.stride * strideOf(i) - LENGTH.turn * turned[i];
88
+ if (i === 1 && long) x -= LENGTH.second;
89
+ if (i === n - 2 && long) x -= LENGTH.beforeLast;
90
+ return 1 + v * x;
91
+ });
92
+ const count = mid.filter(Boolean).length, sum = g.reduce((a, b, i) => a + (mid[i] ? b : 0), 0);
93
+ const inc = inc0.map((d, i) => (mid[i] && v > 0 ? (d * g[i] * count) / sum : d));
94
+ const height = rows.map((r, i) => W.lift * (1 + v * (LIFT.noise * r.lift - (i === 0 ? LIFT.first : 0) - (i === n - 1 && n > 1 ? LIFT.last : 0))));
95
+ const arm = rows.map(r => 1 + v * ARM * r.arm);
96
+ return { lift, air, inc, height, arm };
97
+ }