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,117 @@
1
+ // Open parameters: any number in a spec (generator params, key channel values or vector
2
+ // components, anchor offsets, skill args) may be written as { range: [lo, hi] } (or
3
+ // { search: [lo, hi] }, + int: true). motion.compile then searches the values deterministically:
4
+ // the midpoint first, a grid (one parameter) or a Halton set (several), then a shrinking pattern
5
+ // search around the best, at most spec.search.budget (default 32, max 64) compiles, stopping at
6
+ // the first value with QA 0. Each trial compiles only the frames the parameters can change.
7
+ import { compileMotion } from "./compiler/compile.js";
8
+ import { bakeChannels, flowOptions } from "./compiler/channels.js";
9
+ import { expandGenerators } from "./compiler/walk.js";
10
+ import { runMotionQa } from "./qa/index.js";
11
+ import { weightOf } from "./qa/severity.js";
12
+
13
+ const INT_FIELDS = new Set(["f", "f0", "steps", "period", "swing", "until", "reach", "hold", "release"]);
14
+ const isOpen = v => v && typeof v === "object" && !Array.isArray(v) && (v.range ?? v.search) !== undefined && Object.keys(v).every(k => ["range", "search", "int"].includes(k));
15
+ const pathText = path => path.reduce((s, p) => (typeof p === "number" ? `${s}[${p}]` : s ? `${s}.${p}` : p), "");
16
+
17
+ /** Every { range } in the spec: [{ path, lo, hi, int }]. */
18
+ export function findParams(spec) {
19
+ const out = [];
20
+ const walk = (v, path) => {
21
+ if (isOpen(v)) {
22
+ const r = v.range ?? v.search;
23
+ if (!(Array.isArray(r) && r.length === 2 && r.every(Number.isFinite) && r[0] < r[1])) throw new Error(`MOTION_SPEC_INVALID: ${pathText(path)} range must be [lo, hi] with lo < hi, got ${JSON.stringify(r)}`);
24
+ out.push({ path, lo: r[0], hi: r[1], int: v.int ?? INT_FIELDS.has(path[path.length - 1]) });
25
+ return;
26
+ }
27
+ if (Array.isArray(v)) v.forEach((x, i) => walk(x, [...path, i]));
28
+ else if (v && typeof v === "object") for (const [k, x] of Object.entries(v)) walk(x, [...path, k]);
29
+ };
30
+ for (const [k, v] of Object.entries(spec ?? {})) if (!["schema", "fps", "frames", "search"].includes(k)) walk(v, [k]);
31
+ return out;
32
+ }
33
+
34
+ const snap = (p, x) => { const v = Math.min(p.hi, Math.max(p.lo, x)); return p.int ? Math.round(v) : Math.round(v * 1e4) / 1e4; };
35
+
36
+ function resolveParams(spec, params, values) {
37
+ const s = structuredClone(spec);
38
+ params.forEach((p, i) => { let o = s; for (const k of p.path.slice(0, -1)) o = o[k]; o[p.path[p.path.length - 1]] = values[i]; });
39
+ return s;
40
+ }
41
+
42
+ /** QA groups -> one number: the sum of their severities (qa/severity.js, unrounded). */
43
+ const costOf = qa => qa.groups.reduce((c, g) => c + weightOf(g), 0);
44
+
45
+ /** Frames a parameter can change (baked channels differ between its ends), or null for all. */
46
+ function dirtyWindow(spec, params, mid, prepare, sk) {
47
+ const expand = values => prepare(resolveParams(spec, params, values));
48
+ const base = expand(mid), parts = s => JSON.stringify({ ...s, keys: undefined, generators: undefined });
49
+ const bake = s => bakeChannels(expandGenerators(s, sk), s.frames, flowOptions(s.flow));
50
+ const b0 = bake(base);
51
+ let a = Infinity, b = -Infinity;
52
+ for (let i = 0; i < params.length; i++) for (const end of [params[i].lo, params[i].hi]) {
53
+ const s = expand(mid.map((v, j) => (j === i ? snap(params[i], end) : v)));
54
+ if (parts(s) !== parts(base)) return null;
55
+ const b1 = bake(s);
56
+ for (const ch of new Set([...Object.keys(b0), ...Object.keys(b1)])) {
57
+ const x = b0[ch], y = b1[ch];
58
+ for (let f = 0; f < spec.frames; f++) if (JSON.stringify(x?.[f]) !== JSON.stringify(y?.[f])) { a = Math.min(a, f); b = Math.max(b, f); }
59
+ }
60
+ }
61
+ return a > b ? null : [Math.max(0, a - 8), Math.min(spec.frames - 1, b + 8)];
62
+ }
63
+
64
+ /** Halton point i (1-based) in [0, 1]^k. */
65
+ const PRIMES = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29];
66
+ const halton = (i, k) => Array.from({ length: k }, (_, d) => { let f = 1, r = 0, n = i; const p = PRIMES[d % PRIMES.length]; while (n > 0) { f /= p; r += f * (n % p); n = Math.floor(n / p); } return r; });
67
+
68
+ /**
69
+ * prepare(spec) -> expanded + validated plain spec (pipeline.js). Returns the spec with the
70
+ * chosen values and a report { values: { path: value }, evals, cost (0 = clean), window, ms }.
71
+ */
72
+ export function searchParams(spec, params, { sk, scene }, prepare) {
73
+ const t0 = performance.now(), budget = Math.max(1, Math.min(64, Math.round(spec.search?.budget ?? 32)));
74
+ const mid = params.map(p => snap(p, (p.lo + p.hi) / 2));
75
+ const window = dirtyWindow(spec, params, mid, prepare, sk);
76
+ const cache = new Map();
77
+ let evals = 0, best = null;
78
+ const evaluate = values => {
79
+ const key = values.join(",");
80
+ if (cache.has(key)) return cache.get(key);
81
+ if (evals >= budget) return Infinity;
82
+ evals++;
83
+ let cost;
84
+ try {
85
+ const exp = prepare(resolveParams(spec, params, values));
86
+ cost = costOf(runMotionQa(sk, compileMotion({ fps: 24, ...exp }, { sk, scene }, { window }), exp, { explain: false }));
87
+ } catch (error) {
88
+ if (!/MOTION_SPEC_INVALID/.test(error.message)) throw error;
89
+ cost = 1e9;
90
+ }
91
+ cache.set(key, cost);
92
+ if (!best || cost < best.cost) best = { values, cost };
93
+ return cost;
94
+ };
95
+ const done = () => best?.cost === 0 || evals >= budget;
96
+ const at = u => params.map((p, d) => snap(p, p.lo + (p.hi - p.lo) * u[d]));
97
+ evaluate(mid);
98
+ // coarse pass: a 9-point grid from the middle outward (one parameter) or Halton points
99
+ const coarse = params.length === 1 ? [4, 3, 5, 2, 6, 1, 7, 0, 8].map(i => [i / 8]) : Array.from({ length: Math.min(budget >> 1, 8 * params.length) }, (_, i) => halton(i + 1, params.length));
100
+ for (const u of coarse) { if (done()) break; evaluate(at(u)); }
101
+ // pattern search around the best, halving the step
102
+ let step = params.map(p => (p.hi - p.lo) / 8);
103
+ while (!done() && step.some((s, d) => s > (params[d].int ? 0.5 : (params[d].hi - params[d].lo) * 1e-3))) {
104
+ let moved = false;
105
+ for (let d = 0; d < params.length && !done(); d++) for (const sg of [-1, 1]) {
106
+ const v = best.values.slice(); v[d] = snap(params[d], v[d] + sg * step[d]);
107
+ const before = best.cost;
108
+ evaluate(v);
109
+ if (best.cost < before) moved = true;
110
+ }
111
+ if (!moved) step = step.map(s => s / 2);
112
+ }
113
+ return {
114
+ spec: resolveParams(spec, params, best.values),
115
+ search: { values: Object.fromEntries(params.map((p, i) => [pathText(p.path), best.values[i]])), evals, cost: Math.round(best.cost * 100) / 100, window, ms: Math.round(performance.now() - t0) },
116
+ };
117
+ }
@@ -0,0 +1,112 @@
1
+ // The second hand on a held object (C14, lane K8). While keys lead a carry the right hand holds
2
+ // (object-keys.js), a left hand anchored to that prop (w_<anchor>_l on a kind "prop" anchor of
3
+ // the same prop, with or without a fit) turns with the object too. The wrist stays where the
4
+ // anchor puts it (with a fit, the palm stays where the fit puts it). The hand takes the holding
5
+ // hand's grip mirrored across the plane of the object's axis and the holding fingers (the object's
6
+ // +Y runs through the left palm frame as through the right one, knuckles in line), turned about
7
+ // the axis to the roll its arm holds most easily (planned over the frames, object-keys.js and
8
+ // hold-plan.js). The arm follows as for the holding hand (compiler/hold.js): the elbow swivels
9
+ // about the shoulder-wrist axis (lane N1; it stayed in the plane the anchor's IK gave it, and the
10
+ // default pole, down and back, bent a left arm reaching across the chest into the torso), the
11
+ // forearm and the wrist turn. Each roll is scored by the wrist's strain at the best of POLES poles,
12
+ // plus CLEAR per squared centimetre the upper arm, the forearm or the hand goes into the torso
13
+ // capsules (the BODY check's), and DEEP per squared centimetre the hand goes past what QA allows a
14
+ // hand: into the torso past the BODY allowance, into the holding hand past the SELF one (lane Q1:
15
+ // at CLEAR alone, session 4db5d447 put the hand 0.9 cm into torso_lo at f35 to save 1 of strain,
16
+ // QA BODY). The elbow's path is planned like the holding elbow's. The hold
17
+ // eases in with the keys' weight times the anchor weight, and a grip the arm cannot hold fades out.
18
+ // Only a hand solved after the holding hand can follow it (pose.js solves the right arm first), so
19
+ // the second hand is the left.
20
+ import { palmAxes } from "./compiler/limbs.js";
21
+ import { SIDES } from "./compiler/pose.js";
22
+ import { add, sub, scale, norm, dot, cross, mul, T, apply, axisRot, len, clamp } from "./compiler/math.js";
23
+ import { landscape, ELBOW_REST } from "./compiler/arm-orient.js";
24
+ import { bendPlane } from "./compiler/hold.js";
25
+ import { CAPSULES } from "./qa/geometry.js";
26
+ import { QA_LIMITS } from "./qa/checks.js";
27
+ import { bodyFor, segmentDepth } from "./body.js";
28
+
29
+ const ROLLS = 36, TEMP = 0.05, PULL = 0.3, EASY = 3, SLACK = 10, POLES = 36, CLEAR = 1, DEEP = 100;
30
+ const TORSO = CAPSULES.filter(c => c[0].startsWith("torso")), RADIUS = Object.fromEntries(CAPSULES.map(c => [c[0], c[3]]));
31
+ const cols = (a, b, c) => [[a[0], b[0], c[0]], [a[1], b[1], c[1]], [a[2], b[2], c[2]]];
32
+ const col = (R, i) => [R[0][i], R[1][i], R[2][i]];
33
+
34
+ /** The distance between segments p1-q1 and p2-q2 (closest points, clamped to both). */
35
+ function segSeg(p1, q1, p2, q2) {
36
+ const d1 = sub(q1, p1), d2 = sub(q2, p2), r = sub(p1, p2), a = dot(d1, d1), e = dot(d2, d2), f = dot(d2, r);
37
+ if (a < 1e-12 || e < 1e-12) return len(r);
38
+ const c = dot(d1, r), b = dot(d1, d2), den = a * e - b * b;
39
+ let t = den > 1e-12 ? clamp((b * f - c * e) / den, 0, 1) : 0, u = (b * t + f) / e;
40
+ if (u < 0) { u = 0; t = clamp(-c / a, 0, 1); } else if (u > 1) { u = 1; t = clamp((b - c) / a, 0, 1); }
41
+ return len(sub(add(p1, scale(d1, t)), add(p2, scale(d2, u))));
42
+ }
43
+
44
+ /** The palm frame of `side` on pose s: the finger middles, axes d, l, n as columns. */
45
+ function palmFrame(sk, s, side) {
46
+ const I = sk.index, ax = palmAxes(sk, s, side);
47
+ return { pos: scale(add(s.P[I[`${side}HandIndex2`]], s.P[I[`${side}HandMiddle2`]]), 0.5), R: cols(ax.d, ax.l, ax.n) };
48
+ }
49
+
50
+ /**
51
+ * The left hand's grip on carry `id` at this frame, or null when the left hand is not anchored to
52
+ * its prop: s is the pose with the holding hand done and the left arm on its plain target, c the
53
+ * frame's channels, axis the object's +Y as drawn. The grip is the holding hand's mirrored (knuckles
54
+ * in line), turned `roll` radians about the axis; arm(roll) is the left arm holding it: at(th) ->
55
+ * { pv, elbow, cost } for the elbow th radians about the shoulder-wrist axis from its plane (the
56
+ * swivel landscape plus the torso price) and best { th, pole, cost }; cost(roll) is best's cost
57
+ * (pulled lightly toward the mirror), point(roll) the end of the hand that moves (the palm with the
58
+ * wrist pinned, the wrist with the palm pinned), softmin() the easiest roll.
59
+ */
60
+ export function secondGrip(sk, spec, id, s, c, axis) {
61
+ const o = spec.objects[id];
62
+ if (spec.props[o.prop].hand !== "r") return null;
63
+ const weight = Object.entries(spec.anchors ?? {}).reduce((sum, [name, a]) => sum + (a.kind === "prop" && a.prop === o.prop ? Math.max(0, c[`w_${name}_l`] ?? 0) : 0), 0);
64
+ if (!(weight > 1e-4)) return null;
65
+ // the mirror of the holding palm frame across the plane of the axis and the holding fingers
66
+ const R = palmFrame(sk, s, SIDES.r).R, d = col(R, 0), p = norm(cross(axis, sub(d, scale(axis, dot(d, axis))))), mirror = v => sub(v, scale(p, 2 * dot(v, p)));
67
+ const base = cols(mirror(d), mirror(col(R, 1)), scale(mirror(col(R, 2)), -1));
68
+ const left = palmFrame(sk, s, SIDES.l), I = sk.index, h = I.LeftHand, wrist = s.P[h], local = mul(T(s.R[h]), left.R), localP = apply(T(s.R[h]), sub(left.pos, wrist));
69
+ // with a fit the palm (its effector) stays where the fit put it, else the wrist stays on the anchor
70
+ const pin = (spec.fits ?? []).some(x => x.side === "l" && spec.anchors?.[x.anchor]?.kind === "prop" && spec.anchors[x.anchor].prop === o.prop && c[`w_${x.anchor}_l`] > 1e-4) ? "palm" : "wrist";
71
+ const rolled = r => mul(axisRot(axis, r * 180 / Math.PI), base), hand = r => mul(rolled(r), T(local));
72
+ const point = r => (pin === "wrist" ? add(wrist, apply(hand(r), localP)) : sub(left.pos, apply(hand(r), localP)));
73
+ // the wrist for a roll, the knuckles off it, and how deep a segment goes into the torso (m)
74
+ const wristAt = r => (pin === "wrist" ? wrist : point(r)), knuckle = apply(T(s.R[h]), sub(s.P[I.LeftHandMiddle3], wrist)), S = s.P[I.LeftArm], plane = bendPlane(sk, s, SIDES.l);
75
+ // on the fitted skin when the rig has one and spec.fix.self is on (body.js, as QA's BODY measures it), else on the torso capsules
76
+ const skin = spec.fix === false || spec.fix?.self === false ? null : bodyFor(sk), BONE = { uarm_l: I.LeftArm, farm_l: I.LeftForeArm, hand_l: I.LeftHand };
77
+ const into = (a, b, part) => (skin ? segmentDepth(skin, sk, s, BONE[part], a, b) : Math.max(0, ...TORSO.map(([, ta, tb, rt]) => RADIUS[part] + rt - segSeg(a, b, s.P[I[ta]], s.P[I[tb]]))));
78
+ const arms = new Map();
79
+ const arm = r => {
80
+ if (arms.has(r)) return arms.get(r);
81
+ const W = wristAt(r), land = landscape(sk, s, SIDES.l, hand(r), W, plane, ELBOW_REST);
82
+ const at = th => { const q = land.at(th); return { pv: q.pv, elbow: q.elbow, cost: q.cost + CLEAR * ((100 * into(S, q.elbow, "uarm_l")) ** 2 + (100 * into(q.elbow, W, "farm_l")) ** 2) }; };
83
+ let best = { th: 0, cost: Infinity };
84
+ for (let i = 0; i < POLES; i++) { const th = (2 * Math.PI * i) / POLES - Math.PI, v = at(th).cost; if (v < best.cost) best = { th, cost: v }; }
85
+ const K = add(W, apply(hand(r), knuckle)), deep = into(W, K, "hand_l"), past = Math.max(0, deep - QA_LIMITS.body);
86
+ // the holding hand is done: how far this hand's capsule goes into it (QA SELF "hands overlap")
87
+ const touch = 2 * RADIUS.hand_l - segSeg(W, K, s.P[I.RightHand], s.P[I.RightHandMiddle3]), over = Math.max(0, touch - QA_LIMITS.hands);
88
+ const fixed = CLEAR * ((100 * deep) ** 2 + (100 * land.miss) ** 2) + (past > 0 ? DEEP * (100 * past) ** 2 : 0) + (over > 0 ? DEEP * (100 * over) ** 2 : 0);
89
+ const a = { at, best: { th: best.th, pole: at(best.th).pv, cost: best.cost + fixed } };
90
+ arms.set(r, a);
91
+ return a;
92
+ };
93
+ const cost = r => arm(r).best.cost + PULL * (1 - Math.cos(r));
94
+ const softmin = () => {
95
+ const all = Array.from({ length: ROLLS }, (_, i) => { const r = (2 * Math.PI * i) / ROLLS - Math.PI; return [r, cost(r)]; }), low = Math.min(...all.map(x => x[1]));
96
+ const m = all.reduce((acc, [r, v]) => { const e = Math.exp(-(v - low) / TEMP); return [acc[0] + e * Math.cos(r), acc[1] + e * Math.sin(r)]; }, [0, 0]);
97
+ return Math.atan2(m[1], m[0]);
98
+ };
99
+ return { id, weight, pin, at: left.pos, rolled, arm, cost, point, softmin, measure: s2 => palmFrame(sk, s2, SIDES.l) };
100
+ }
101
+
102
+ /** How much of a grip at `roll` the arm can take: 1 while its cost is under EASY, fading by SLACK past it. */
103
+ export const gripGate = (grip, roll) => Math.exp(-Math.max(0, grip.cost(roll) - EASY) / SLACK);
104
+
105
+ /**
106
+ * The hold ({ w, palm, measure, pole, pin, second } for compiler/hold.js) of a grip at `roll`
107
+ * radians with the elbow toward `pole` (a world direction), w the keys' lead weight, gate the
108
+ * planned gripGate: a grip the arm cannot hold fades out, so the hand stays nearer where the plain
109
+ * arm put it instead of turning to a limit and flipping there.
110
+ */
111
+ export const secondHold = (grip, w, roll, gate, pole) => ({ w: w * Math.min(1, grip.weight) * gate, second: grip.id, pin: grip.pin,
112
+ pole, palm: { pos: grip.at, R: grip.rolled(roll) }, measure: grip.measure });
@@ -0,0 +1,82 @@
1
+ // Basic skills: stand, walk, reach-and-grip. Each reads the channel state the earlier keys
2
+ // leave at its f0 and returns keys plus whatever anchors, props, fits and checks it needs.
3
+ import { add, apply, ry } from "../compiler/math.js";
4
+ import { walk } from "../compiler/walk.js";
5
+ import { keyBook, skillError } from "./book.js";
6
+ import { walkEnd } from "./info.js";
7
+
8
+ /** standIdle({ at: [x, z], heading = 0, until }): a neutral standing pose, arms down (FK). */
9
+ export function standIdle(ctx) {
10
+ const { sk, args, state } = ctx, I = sk.index, P = sk.restWorldPos, ank = P[I.LeftFoot][1];
11
+ const h = args.heading ?? state.yaw ?? 0, base = args.at ?? (state.root ? [state.root[0], state.root[2]] : null);
12
+ if (!base) skillError(ctx.tag, "needs args.at: [x, z] (no root is keyed before f0)");
13
+ const c = [base[0], 0, base[base.length - 1]], R = ry(h), foot = v => { const p = add(c, apply(R, v)); p[1] = ank; return p; };
14
+ const set = { root: [c[0], P[I.Hips][1] - 0.01, c[2]], yaw: h, pitch: 0, roll: 0, spine_yaw: 0, spine_pitch: 0, spine_roll: 0, head_yaw: 0, head_pitch: 0,
15
+ foot_l: foot([0.09, 0, -0.03]), foot_r: foot([-0.09, 0, 0.03]), fyaw_l: h, fyaw_r: h, fpitch_l: 0, fpitch_r: 0, knee_l: [0.05, 0, 1], knee_r: [-0.05, 0, 1],
16
+ armw_l: 0, armw_r: 0, aswing_l: 0, aswing_r: 0, abend_l: 0, abend_r: 0, arm_hang: 1 };
17
+ const keys = [{ f: ctx.f0, set }];
18
+ if (args.until !== undefined) keys.push({ f: args.until, set: structuredClone(set) });
19
+ return { keys, end: args.until ?? ctx.f0 };
20
+ }
21
+
22
+ /**
23
+ * walkTo({ to: [x, z] } | { heading: h | [h0, h1] }, steps, step = 0.5, turnDistance, ...): the
24
+ * walk generator from the pose at f0. With `to` the heading turns toward the point and the step
25
+ * count covers the distance. Ends 16 + steps * period + 18 frames after f0, feet together; a
26
+ * take that ends after the last step holds that pose to its end. The steps keep a natural rhythm
27
+ * (compiler/gait-rhythm.js); vary: 0 makes every step the same.
28
+ */
29
+ export function walkTo(ctx) {
30
+ const { args, state } = ctx;
31
+ if (!state.foot_l || !state.foot_r || !state.root) skillError(ctx.tag, "needs a standing pose keyed before f0 (use standIdle or another skill first)");
32
+ const gen = { type: "walk", f0: ctx.f0, ...args };
33
+ delete gen.to; delete gen.arrive;
34
+ const h0 = state.yaw ?? 0, step = args.step ?? 0.5, first = args.firstStep ?? 0.4;
35
+ if (args.to) {
36
+ const c0 = [(state.foot_l[0] + state.foot_r[0]) / 2, (state.foot_l[2] + state.foot_r[2]) / 2];
37
+ const dx = args.to[0] - c0[0], dz = args.to[args.to.length - 1] - c0[1], d = Math.hypot(dx, dz);
38
+ let h1 = Math.atan2(dx, dz) * 180 / Math.PI;
39
+ while (h1 - h0 > 180) h1 -= 360;
40
+ while (h1 - h0 < -180) h1 += 360;
41
+ gen.heading = args.heading ?? [h0, h1];
42
+ gen.steps = args.steps ?? Math.max(2, 2 + Math.round((d - first) / step));
43
+ }
44
+ if (typeof gen.heading === "number") gen.heading = [h0, gen.heading];
45
+ const keys = walk(gen, ctx.keys, ctx.sk, ctx.tag), end = walkEnd(gen), last = ctx.frames - 1;
46
+ // the settle after the last step only holds the pose: a take that ends in it ends the walk there
47
+ if (end > last && Math.max(...keys.filter(k => k.f < end).map(k => k.f)) <= last) return { keys: keys.filter(k => k.f <= last), end: last, cut: `settle cut at f${last} (hold to the end)` };
48
+ return { keys, end };
49
+ }
50
+
51
+ /**
52
+ * reachGrip({ anchor, side, reach = 20, hold = 22, release = 12, elbow, fingers = "grip",
53
+ * define, orient = true }): the hand goes to the anchor (defined elsewhere, or by `define`) over
54
+ * `reach` frames with the fingers closing, holds, and lets go. Adds a fit of the fingertips on the
55
+ * anchor (on a body it also turns the hand into a grip, compiler/grip.js; orient: false leaves the
56
+ * hand's turn alone) and a contact check. A take that ends during the release holds the grip to its end.
57
+ */
58
+ export function reachGrip(ctx) {
59
+ const { args, state } = ctx, k = args.side, name = args.anchor;
60
+ if (k !== "l" && k !== "r") skillError(ctx.tag, `args.side must be "l" or "r", got ${JSON.stringify(k)}`);
61
+ if (typeof name !== "string" || /_[lr]$/.test(name)) skillError(ctx.tag, "args.anchor must name an anchor (not ending in _l/_r)");
62
+ const reach = args.reach ?? 20, hold = args.hold ?? 22, release = args.release ?? 12, preset = args.fingers ?? "grip";
63
+ const w = `w_${name}_${k}`, fg = `fingers_${k}`, armw = `armw_${k}`, el = `elbow_${k}`, wasFk = !(state[armw] > 0.5);
64
+ // a take that ends after the hold but before the hand lets go cuts the release: the grip holds to the last frame
65
+ const last = ctx.frames - 1, cut = ctx.f0 + reach + hold <= last && ctx.f0 + reach + hold + release > last;
66
+ for (const [c, v] of [[w, 0], [fg, { [preset]: 0 }], [armw, 0]]) if (state[c] === undefined) state[c] = v;
67
+ const b = keyBook(state, ctx.f0);
68
+ b.hold(0, w, fg, armw, el);
69
+ b.at(reach, { [w]: 1, [fg]: { ...state[fg], [preset]: 1 }, [armw]: 1, ...(args.elbow ? { [el]: args.elbow } : {}) });
70
+ b.hold(reach + hold, w, fg, armw, el);
71
+ if (!cut) b.at(reach + hold + release, { [w]: 0, [fg]: { ...state[fg], [preset]: 0 }, ...(wasFk ? { [armw]: 0 } : {}) });
72
+ const S = k === "l" ? "Left" : "Right", prop = `${name}Grip`;
73
+ const def = args.define ? { [name]: args.define } : {};
74
+ const obj = args.define?.kind === "object" ? args.define.object : ctx.anchors?.[name]?.kind === "object" ? ctx.anchors[name].object : null;
75
+ return {
76
+ keys: b.keys, end: cut ? last : ctx.f0 + reach + hold + release, ...(cut ? { cut: `release cut at f${last} (hold to the end)` } : {}), anchors: def,
77
+ props: { [prop]: { hand: k, origin: [`${S}HandIndex2`, `${S}HandMiddle2`], points: { fingers: [0, 0, 0] } } },
78
+ fits: [{ side: k, anchor: name, effector: { prop, point: "fingers" }, ...(args.orient === false ? { orient: false } : {}) }],
79
+ checks: { contacts: [{ name: `fingers on ${name}`, point: `prop:${prop}.fingers`, target: `anchor:${name}`, tol: 0.04, when: w }],
80
+ allow: obj ? [{ part: "hand", object: obj, depth: 0.04 }] : [] },
81
+ };
82
+ }
@@ -0,0 +1,76 @@
1
+ // Skill plumbing: a key book that tracks the channel state while a skill writes keys, and the
2
+ // placement of choreography written in a template frame (e.g. a car's own frame, driver side
3
+ // on +X) into the scene: translate, turn by a yaw, and mirror left/right for the other side.
4
+ import { add, apply, ry } from "../compiler/math.js";
5
+
6
+ const SIDE_SWAP = { _l: "_r", _r: "_l" };
7
+ const NEGATE_ON_MIRROR = /^(yaw|roll|spine_yaw|spine_roll|head_yaw|head_roll|fyaw_[lr]|wtwist_[lr]|wflex_[lr])$/;
8
+ const POINT = /^(root|foot_[lr]|hand_[lr])$/, POLE = /^(knee|elbow)_[lr]$/, YAW = /^(yaw|fyaw_[lr])$/;
9
+
10
+ const clone = v => (v && typeof v === "object" ? structuredClone(v) : v);
11
+ export const swapSide = name => name.replace(/_(l|r)$/, m => SIDE_SWAP[m]).replace(/^(Left|Right)/, m => (m === "Left" ? "Right" : "Left"));
12
+
13
+ /**
14
+ * A frame: { origin: [x,y,z], yaw (deg), mirror (bool), negate: Set of extra channels (hinge
15
+ * angles) to negate, names: { anchor: mirroredAnchor } }. Template points p map to
16
+ * origin + ry(yaw)(mirror ? [-x, y, z] : p).
17
+ */
18
+ export const identityFrame = () => ({ origin: [0, 0, 0], yaw: 0, mirror: false, negate: new Set(), names: {} });
19
+
20
+ const placePoint = (fr, p) => add(fr.origin, apply(ry(fr.yaw), fr.mirror ? [-p[0], p[1], p[2]] : p));
21
+ export const placeDir = (fr, v) => (fr.mirror ? [-v[0], v[1], v[2]] : v.slice());
22
+
23
+ /** A channel name in the placed frame (sides swap and anchor names rename when mirrored). */
24
+ export function placeChannel(fr, ch) {
25
+ if (!fr.mirror) return ch;
26
+ const w = /^w_(.+)_([lr])$/.exec(ch);
27
+ if (w) return `w_${fr.names[w[1]] ?? w[1]}_${w[2] === "l" ? "r" : "l"}`;
28
+ return /_[lr]$/.test(ch) ? swapSide(ch) : ch;
29
+ }
30
+
31
+ function placeSet(fr, set) {
32
+ const out = {};
33
+ for (const [ch, v0] of Object.entries(set)) {
34
+ let v = clone(v0);
35
+ if (POINT.test(ch)) v = placePoint(fr, v);
36
+ else if (POLE.test(ch)) v = placeDir(fr, v);
37
+ else if (typeof v === "number") {
38
+ if (fr.mirror && (NEGATE_ON_MIRROR.test(ch) || fr.negate.has(ch))) v = -v;
39
+ if (YAW.test(ch)) v += fr.yaw;
40
+ }
41
+ out[placeChannel(fr, ch)] = v;
42
+ }
43
+ return out;
44
+ }
45
+
46
+ /**
47
+ * Keys relative to f0 (book.at(12, ...) writes frame f0 + 12). `at` and `step` take template
48
+ * values and place them; `hold` re-keys the current (already placed) values.
49
+ */
50
+ export function keyBook(state, f0 = 0, fr = identityFrame()) {
51
+ const keys = [], name = ch => placeChannel(fr, ch);
52
+ return {
53
+ state, keys, frame: fr,
54
+ at(f, set) { const placed = placeSet(fr, set); Object.assign(state, clone(placed)); keys.push({ f: f0 + f, set: placed }); },
55
+ hold(f, ...names) { keys.push({ f: f0 + f, set: Object.fromEntries(names.filter(n => state[name(n)] !== undefined).map(n => [name(n), clone(state[name(n)])])) }); },
56
+ step(f, ch, value) {
57
+ const n = name(ch);
58
+ keys.push({ f: f0 + f - 1, set: { [n]: state[n] } });
59
+ state[n] = value;
60
+ keys.push({ f: f0 + f, set: { [n]: value }, step: [n] });
61
+ },
62
+ };
63
+ }
64
+
65
+ /** Throws the skill's own MOTION_SPEC_INVALID message. */
66
+ export const skillError = (tag, msg) => { throw new Error(`MOTION_SPEC_INVALID: ${tag}: ${msg}`); };
67
+
68
+ /** Scene box by id or a skill error naming the arg. */
69
+ export function sceneBox(ctx, arg, id) {
70
+ const box = ctx.scene?.objects?.[id];
71
+ if (!box) skillError(ctx.tag, `args.${arg} "${id}" is not a scene object`);
72
+ return box;
73
+ }
74
+
75
+ /** Yaw (deg) of a box's rotation matrix about +Y. */
76
+ export const yawOf = R => Math.atan2(R[0][2], R[2][2]) * 180 / Math.PI;
@@ -0,0 +1,145 @@
1
+ // Car skills, extracted from the car-exit fixture (test/motion3/fixtures/exitcar-scene.mjs).
2
+ // The choreography is written in the template car's frame: the car centred on the origin facing
3
+ // +Z, its driver door on +X, seat base at y 0.4. The skills place it from the scene: the seat
4
+ // gives the car's position and yaw, the door gives the hinge, side "r" mirrors everything.
5
+ // Tuned on the X Bot: seated heights are the template's, standing heights follow the rig.
6
+ import { add, sub, apply, ry } from "../compiler/math.js";
7
+ import { keyBook, identityFrame, placeChannel, placeDir, sceneBox, skillError, yawOf, swapSide } from "./book.js";
8
+
9
+ const SEAT_REF = [0.54, 0.4, -0.075], FLOOR = 0.4, X_HIPS = 1.0427487182617188;
10
+ const SIDES = new Set(["l", "r"]);
11
+
12
+ /** Seat, door and handle ids from args ({ car: "Car" } fills the template's names). */
13
+ function carIds(args, side = "l") {
14
+ const car = args.car;
15
+ return {
16
+ seat: args.seat ?? (car && `${car}_Seat_Driver`), seatBack: args.seatBack ?? (car && `${car}_Seat_Driver_Back`),
17
+ door: args.door ?? (car && `${car}_Door_${side.toUpperCase()}`), handle: args.handle ?? (car && `${car}_Door_Handle`),
18
+ wheel: args.wheel ?? (car && `${car}_Steering_Wheel`),
19
+ };
20
+ }
21
+
22
+ /** The template-to-scene frame for a car whose driver seat is `seat`. */
23
+ function carFrame(ctx, seat, side) {
24
+ const box = sceneBox(ctx, "seat", seat);
25
+ if (Math.abs(box.pos[1] - FLOOR) > 0.05) skillError(ctx.tag, `seat "${seat}" stands at y ${box.pos[1]}; the car skills are tuned for a seat base at y ${FLOOR} (a car on the ground)`);
26
+ const fr = identityFrame();
27
+ fr.yaw = yawOf(box.R); fr.mirror = side === "r";
28
+ const o = sub(box.pos, apply(ry(fr.yaw), fr.mirror ? [-SEAT_REF[0], SEAT_REF[1], SEAT_REF[2]] : SEAT_REF));
29
+ fr.origin = [o[0], 0, o[2]];
30
+ fr.names = { lthigh: "rthigh", rthigh: "lthigh" };
31
+ return fr;
32
+ }
33
+
34
+ /** The hands on the thighs, pushing to stand up; letting go as the thighs turn under them, the palms and forearms
35
+ * sweep a few centimetres into them for a few frames (the open case of fix/self.js: it moves wrists, not palms);
36
+ * the hands, both on the thighs over knees close together, touch. */
37
+ const THIGH_CONTACT = [["hand", "thigh*", 0.07], ["farm", "thigh*", 0.07], ["hand", "hips", 0.07], ["farm", "hips", 0.07], ["hand_l", "hand_r", 0.035]]
38
+ .map(([part, object, depth]) => ({ part, object, depth }));
39
+
40
+ const rigHeights = sk => ({ A: sk.restWorldPos[sk.index.LeftFoot][1], H: y => y + (sk.restWorldPos[sk.index.Hips][1] - X_HIPS) });
41
+
42
+ /** sitInSeat({ car | seat (+ seatBack, wheel), offset = [0, 0.17, 0.02], floor }): seated, hands forward at the wheel. */
43
+ export function sitInSeat(ctx) {
44
+ const { args, sk } = ctx, ids = carIds(args), box = sceneBox(ctx, "seat", ids.seat), { A } = rigHeights(sk);
45
+ const Y = yawOf(box.R), turn = v => apply(ry(Y), v), off = args.offset ?? [0, 0.17, 0.02], floor = args.floor ?? box.pos[1];
46
+ const root = add(add(box.pos, apply(box.R, [0, 2 * box.half[1], 0])), turn(off));
47
+ const foot = x => { const p = add(root, turn([x, 0, 0.452])); p[1] = floor + A; return p; };
48
+ const hand = x => add(root, turn([x, 0.348, 0.324]));
49
+ const set = {
50
+ root, yaw: Y, pitch: 0, spine_pitch: -12, spine_yaw: 0, spine_roll: 0, head_yaw: 0, head_pitch: 10,
51
+ foot_l: foot(0.17), foot_r: foot(-0.17), fyaw_l: Y, fyaw_r: Y, fpitch_l: 0, fpitch_r: 0, knee_l: [0.15, 0.2, 1], knee_r: [-0.15, 0.2, 1],
52
+ armw_l: 1, armw_r: 1, hand_l: hand(0.22), hand_r: hand(-0.22), elbow_l: [0.6, -1, -0.3], elbow_r: [-0.6, -1, -0.3],
53
+ aswing_l: 0, aswing_r: 0, abend_l: 0, abend_r: 0, wtwist_l: 0, wtwist_r: 0, wflex_l: 0, wflex_r: 0,
54
+ };
55
+ const allow = [{ part: "torso_lo", object: ids.seat, depth: 0.04 }, { part: "hips", object: ids.seat, depth: 0.04 }, { part: "thigh", object: ids.seat, depth: 0.045 }];
56
+ if (ids.seatBack && ctx.scene.objects[ids.seatBack]) allow.push({ part: "torso", object: ids.seatBack, depth: 0.03 });
57
+ if (ids.wheel && ctx.scene.objects[ids.wheel]) allow.push({ part: "hand", object: ids.wheel, depth: 0.05 });
58
+ return { keys: [{ f: ctx.f0, set }], checks: { supports: [{ object: ids.seat, part: "hips", tol: 0.04 }], allow }, end: ctx.f0 };
59
+ }
60
+
61
+ /**
62
+ * exitCar({ car | seat+door+handle, side = "l", channel = "door" }): reach the inner handle, pull
63
+ * and push the door, let it swing open, swivel, legs out, stand up with the hands on the thighs,
64
+ * two steps away; ends at f0 + 246 standing on a walk start stance heading car yaw + 125.
65
+ */
66
+ export function exitCar(ctx) {
67
+ const side = ctx.args.side ?? "l";
68
+ if (!SIDES.has(side)) skillError(ctx.tag, `args.side must be "l" or "r", got ${JSON.stringify(side)}`);
69
+ const ids = carIds(ctx.args, side), fr = carFrame(ctx, ids.seat, side), ch = ctx.args.channel ?? "door";
70
+ for (const [arg, id] of [["door", ids.door], ["handle", ids.handle]]) sceneBox(ctx, arg, id);
71
+ fr.negate.add(ch);
72
+ const defaults = { [ch]: 0, w_handle_l: 0, w_lthigh_l: 0, w_rthigh_r: 0, fingers_l: { grip: 0, fist: 0 } };
73
+ const k = keyBook(ctx.state, ctx.f0, fr), fresh = {};
74
+ for (const [c, v] of Object.entries(defaults)) if (ctx.state[placeChannel(fr, c)] === undefined) fresh[c] = v;
75
+ if (Object.keys(fresh).length) k.at(0, fresh);
76
+ choreography(k, ch, rigHeights(ctx.sk));
77
+ const door = ctx.scene.objects[ids.door], hx = (fr.mirror ? -1 : 1) * door.half[0];
78
+ const hinge = { pivot: add(door.pos, apply(door.R, [hx, 0, door.half[2]])), axis: [0, 1, 0], channel: ch };
79
+ const L = s => (fr.mirror ? swapSide(s) : s), n = s => (fr.mirror ? { lthigh: "rthigh", rthigh: "lthigh" }[s] ?? s : s);
80
+ const seg = (S, reach) => ({ kind: "segment", from: L(`${S}UpLeg`), to: L(`${S}Leg`), t: 0.45, tMin: 0.15, offset: [0, 0.07, 0], reach: fr.mirror ? (reach === "l" ? "r" : "l") : reach });
81
+ const hand = fr.mirror ? "r" : "l";
82
+ return {
83
+ keys: k.keys,
84
+ objects: { [ids.door]: hinge, [ids.handle]: hinge },
85
+ anchors: { handle: { kind: "object", object: ids.handle, local: placeDir(fr, [-0.005, 0.015, 0]) }, [n("lthigh")]: seg("Left", "l"), [n("rthigh")]: seg("Right", "r") },
86
+ props: { grip: { hand, origin: [L("LeftHandIndex2"), L("LeftHandMiddle2")], points: { fingers: [0, 0, 0] } } },
87
+ fits: [{ side: hand, anchor: "handle", effector: { prop: "grip", point: "fingers" } }],
88
+ checks: {
89
+ contacts: [{ name: "fingers on the door handle", point: "prop:grip.fingers", target: "anchor:handle", tol: 0.04, when: `w_handle_${hand}` }],
90
+ // the hands rest on the thighs and push on them to stand up: a contact the body check allows (qa/body-checks.js)
91
+ allow: [{ part: "hand", object: ids.door, depth: 0.03 }, { part: "hand", object: ids.handle, depth: 0.04 }, ...THIGH_CONTACT],
92
+ },
93
+ end: ctx.f0 + 246,
94
+ };
95
+ }
96
+
97
+ function choreography(k, door, { A, H }) {
98
+ // 1. reach for the inner handle, pull, push the door a little (f12-50)
99
+ k.hold(12, "w_handle_l", "fingers_l", "head_yaw", "spine_yaw", "spine_roll", "elbow_l", "hand_l");
100
+ k.at(32, { w_handle_l: 1, fingers_l: { grip: 1, fist: 0 }, head_yaw: 35, spine_yaw: 10, spine_roll: -8, elbow_l: [0.3, -1, -0.6], hand_l: [1.0, 0.96, 0.06] });
101
+ k.hold(32, door);
102
+ k.at(38, { [door]: -3 });
103
+ k.at(50, { [door]: -10, spine_roll: -16, spine_yaw: 14 });
104
+ // 2. let go (the door holds still), then it swings open; hands rest on the thighs (f54-84)
105
+ k.hold(54, "w_handle_l", "fingers_l", "w_lthigh_l", "elbow_l", door);
106
+ k.at(66, { w_handle_l: 0, fingers_l: { grip: 0, fist: 0 }, [door]: -10 });
107
+ k.at(70, { w_lthigh_l: 1, elbow_l: [0.5, -0.3, -1], spine_roll: -3, spine_yaw: 6 });
108
+ k.at(84, { [door]: -68, head_yaw: 40 });
109
+ k.hold(64, "w_rthigh_r", "elbow_r", "spine_pitch");
110
+ k.at(80, { w_rthigh_r: 1, elbow_r: [-0.5, -0.3, -1], spine_roll: 0, spine_yaw: 0, spine_pitch: 0 });
111
+ // 3. swivel; the left leg goes out over the sill and down to the ground (f84-118)
112
+ k.hold(84, "yaw", "root", "foot_l", "foot_r", "fyaw_l", "fyaw_r", "knee_l", "knee_r");
113
+ k.at(92, { foot_r: [0.4, FLOOR + A + 0.03, 0.36], fyaw_r: 15 });
114
+ k.at(97, { foot_l: [0.96, FLOOR + 0.22, 0.32], fyaw_l: 40, knee_l: [0.5, 0.4, 1], yaw: 30, root: [0.64, 0.915, -0.06] });
115
+ k.at(101, { foot_r: [0.56, FLOOR + A, 0.34], fyaw_r: 35 });
116
+ k.at(105, { foot_l: [1.18, FLOOR + 0.1, 0.1], fyaw_l: 60 });
117
+ k.at(116, { foot_l: [1.28, A, -0.12], fyaw_l: 75, knee_l: [0.4, 0.5, 1], yaw: 62, root: [0.82, 0.9, -0.06], head_yaw: 20, knee_r: [0.1, 0.6, 1], spine_pitch: 10 });
118
+ // 4. right leg out (f120-144)
119
+ k.hold(120, "foot_r", "fyaw_r");
120
+ k.at(129, { foot_r: [0.94, FLOOR + 0.24, 0.36], fyaw_r: 65, knee_r: [0, 0.5, 1], yaw: 80, root: [0.86, 0.9, -0.03] });
121
+ k.at(135, { foot_r: [1.18, FLOOR + 0.1, 0.26], fyaw_r: 85 });
122
+ k.hold(142, "foot_l", "fyaw_l");
123
+ k.at(144, { foot_r: [1.29, A, 0.22], fyaw_r: 90, knee_r: [-0.1, 0.4, 1], yaw: 90, root: [0.88, 0.9, 0.02], head_yaw: 0, knee_l: [0.1, 0.4, 1], spine_pitch: 16 });
124
+ // 5. lean the weight over the feet with the hands pushing on the thighs, stand up (f144-196)
125
+ k.hold(144, "head_pitch", "pitch", "elbow_l", "elbow_r");
126
+ k.at(152, { spine_pitch: 28, head_pitch: -8, root: [0.91, 0.9, 0.02], elbow_l: [0.6, -0.2, -1], elbow_r: [-0.6, -0.2, -1] });
127
+ k.at(162, { spine_pitch: 50, pitch: 22, head_pitch: -36, root: [0.99, 0.91, 0.02] });
128
+ k.at(174, { root: [1.09, 0.96, 0.02], spine_pitch: 36, pitch: 12, head_pitch: -24 });
129
+ k.hold(166, "armw_l", "armw_r");
130
+ k.at(186, { root: [1.19, H(1.0), 0.02], spine_pitch: 12, pitch: 3, head_pitch: -8, armw_l: 0, armw_r: 0 });
131
+ k.hold(187, "w_lthigh_l", "w_rthigh_r");
132
+ k.at(190, { w_lthigh_l: 0, w_rthigh_r: 0 });
133
+ k.at(196, { root: [1.21, H(1.005), 0.02], spine_pitch: 0, pitch: 0, head_pitch: 0 });
134
+ // 6. two steps away from the car with a weight shift (f196-246), onto the walk's start stance
135
+ k.at(205, { root: [1.26, H(0.985), 0.165] });
136
+ k.hold(208, "foot_l", "fyaw_l");
137
+ k.at(212, { root: [1.3, H(0.985), 0.16] });
138
+ k.at(214, { foot_l: [1.4, A + 0.06, -0.12] });
139
+ k.at(220, { foot_l: [1.535, A, -0.132], fyaw_l: 110, root: [1.38, H(0.98), 0.05] });
140
+ k.at(230, { root: [1.5, H(0.975), -0.08] });
141
+ k.hold(232, "foot_r", "fyaw_r");
142
+ k.at(236, { root: [1.52, H(0.975), -0.085] });
143
+ k.at(239, { foot_r: [1.53, A + 0.07, 0.09] });
144
+ k.at(246, { foot_r: [1.765, A, -0.048], fyaw_r: 125, yaw: 110, root: [1.62, H(0.98), -0.08] });
145
+ }
@@ -0,0 +1,29 @@
1
+ // frames: "auto". The spec's length becomes the latest of every skill end, generator end and key
2
+ // f, plus 1. Open parameters ({ range: [lo, hi] }) count at their upper bound, so every value the
3
+ // search tries fits. The answer is a copy with the number, so the stored spec (and a saved take)
4
+ // keeps it; the spec store remembers the "auto" intent beside it (spec-store.js markFramesAuto).
5
+ import { findParams } from "../search.js";
6
+ import { expandUse } from "./index.js";
7
+ import { walkEnd } from "./info.js";
8
+
9
+ const MAX_FRAMES = 20000;
10
+
11
+ /** The spec with every open parameter at its upper bound. */
12
+ function atUpperBound(spec) {
13
+ const s = structuredClone(spec);
14
+ for (const p of findParams(spec)) {
15
+ let o = s;
16
+ for (const k of p.path.slice(0, -1)) o = o[k];
17
+ o[p.path[p.path.length - 1]] = p.hi;
18
+ }
19
+ return s;
20
+ }
21
+
22
+ /** resolveFrames(spec, { sk, scene, lenient }) -> spec (a copy when frames was "auto", else spec itself). */
23
+ export function resolveFrames(spec, { sk, scene, lenient = false } = {}) {
24
+ if (spec?.frames !== "auto") return spec;
25
+ const { spec: full, used } = expandUse({ ...atUpperBound(spec), frames: MAX_FRAMES }, { sk, scene, lenient, auto: true });
26
+ const ends = [...(Array.isArray(full.keys) ? full.keys.map(k => k?.f) : []), ...used.map(u => u.end),
27
+ ...(Array.isArray(full.generators) ? full.generators.map(g => (g && Number.isFinite(g.f0) ? walkEnd(g) : null)) : [])];
28
+ return { ...spec, frames: Math.max(0, ...ends.filter(Number.isFinite)) + 1 };
29
+ }