cozyclay 2.1.0 → 3.0.0

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