cozyclay 2.1.1 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (585) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/README.md +12 -30
  3. package/bin/agent/agent-routes.mjs +43 -116
  4. package/bin/agent/agent-runner.mjs +14 -15
  5. package/bin/agent/cliproxy-provider.mjs +135 -0
  6. package/bin/agent/credential-store.mjs +30 -7
  7. package/bin/agent/pi-tools.mjs +4 -1
  8. package/bin/agent/provider-keys.mjs +32 -0
  9. package/bin/agent/provider-login.mjs +110 -0
  10. package/bin/agent/provider-routes.mjs +99 -0
  11. package/bin/agent/providers.mjs +19 -97
  12. package/bin/agent/script-tool.mjs +17 -4
  13. package/bin/agent/session-store.mjs +2 -2
  14. package/bin/agent/studio-context-budget.mjs +93 -0
  15. package/bin/agent/studio-motion-reference.mjs +23 -0
  16. package/bin/agent/studio-prompt.mjs +1 -1
  17. package/bin/agent/studio-rebase-text.mjs +32 -0
  18. package/bin/agent/studio-rebase.mjs +26 -39
  19. package/bin/agent/studio-tools.mjs +9 -28
  20. package/bin/agent/tool-arguments.mjs +49 -0
  21. package/bin/agent/turn-frames.mjs +66 -0
  22. package/bin/codex-auth.mjs +9 -2
  23. package/bin/cozyclay.mjs +10 -116
  24. package/bin/live/cli.mjs +3 -3
  25. package/bin/mcp-runtime.mjs +2 -12
  26. package/bin/package-signature.mjs +0 -4
  27. package/dist/app/index.html +7 -6
  28. package/dist/assets/EXRLoader-CvVIvGVE.js +1 -0
  29. package/dist/assets/analytics-BWCqdgWA.js +1 -0
  30. package/dist/assets/app-DfTCxaKk.js +5087 -0
  31. package/dist/assets/app-f-fkV5uP.css +1 -0
  32. package/dist/assets/compile-worker-BxKXh07s.js +5 -0
  33. package/dist/assets/fflate.module-KbGj5-H6.js +5 -0
  34. package/dist/assets/{first-shot-handoff-msLI71g1.js → first-shot-handoff-DNO8kv_N.js} +1 -1
  35. package/dist/assets/{landing-ChUvB_zL.js → landing-yfZaJkWc.js} +1 -1
  36. package/dist/assets/scene-asset-sync-U3kS2rvK.js +20 -0
  37. package/dist/assets/scene-asset-sync-_S8NQl5B.css +1 -0
  38. package/dist/assets/workflow-D78eiKTa.js +179 -0
  39. package/dist/cozyclay-package.json +1 -1
  40. package/dist/index.html +3 -3
  41. package/dist/sw.js +3 -6
  42. package/dist/workflow/index.html +5 -4
  43. package/mcp/LIVE-PROTOCOL.md +7 -47
  44. package/mcp/README.md +16 -26
  45. package/mcp/live-hub.mjs +6 -10
  46. package/mcp/package.json +1 -3
  47. package/mcp/tool-handlers.mjs +4 -168
  48. package/package.json +3 -22
  49. package/src/App.jsx +151 -2625
  50. package/src/agent-progress.js +80 -0
  51. package/src/agent-turn-mark.js +30 -0
  52. package/src/analytics.js +1 -19
  53. package/src/app-context.js +12 -2
  54. package/src/app-stage.jsx +2 -34
  55. package/src/asset-pane.jsx +6 -5
  56. package/src/camera-block.js +21 -0
  57. package/src/camera-direction.js +120 -0
  58. package/src/character-kind.js +3 -9
  59. package/src/command-bus.js +10 -8
  60. package/src/commands/cast.js +5 -34
  61. package/src/commands/elements/character.js +1 -1
  62. package/src/commands/elements/motion.js +1 -6
  63. package/src/commands/elements/object.js +6 -2
  64. package/src/commands/elements/shot.js +4 -1
  65. package/src/commands/elements.js +38 -14
  66. package/src/commands/index.js +3 -1
  67. package/src/commands/kits.js +37 -0
  68. package/src/commands/motion-timing.js +57 -0
  69. package/src/commands/motion.js +9 -152
  70. package/src/commands/motion3-compile.js +65 -0
  71. package/src/commands/motion3.js +214 -0
  72. package/src/commands/objects.js +6 -4
  73. package/src/commands/shot.js +55 -13
  74. package/src/commands/stage.js +14 -1
  75. package/src/commands/view.js +4 -1
  76. package/src/document-mirrors.js +42 -0
  77. package/src/document-store.js +4 -0
  78. package/src/domains/cast.js +19 -194
  79. package/src/domains/motion/motion-range-pin-rules.js +1 -1
  80. package/src/domains/motion/take-source.js +53 -0
  81. package/src/domains/motion.js +60 -1838
  82. package/src/domains/scenes.js +12 -55
  83. package/src/domains/shots.js +2 -2
  84. package/src/domains/stage.js +4 -2
  85. package/src/draft-ghost.js +47 -0
  86. package/src/draft-ghost.jsx +129 -0
  87. package/src/dualview.jsx +4 -3
  88. package/src/effects/EffectsLayer.jsx +112 -0
  89. package/src/effects/effect-meshes.js +232 -0
  90. package/src/effects/effect-record.js +103 -0
  91. package/src/effects/emitter.js +59 -0
  92. package/src/effects/simulate.js +261 -0
  93. package/src/effects/state.js +59 -0
  94. package/src/execution-telemetry.js +2 -3
  95. package/src/first-success-guide.jsx +5 -5
  96. package/src/ghost-set.js +257 -0
  97. package/src/held-object-edit.jsx +113 -0
  98. package/src/held-object-pose.js +175 -0
  99. package/src/hierarchy-model.js +1 -1
  100. package/src/hierarchy-panel.jsx +1 -1
  101. package/src/imported-mesh.jsx +103 -0
  102. package/src/kits/held.js +84 -0
  103. package/src/kits/index.js +70 -0
  104. package/src/kits/smoking.js +57 -0
  105. package/src/light-blockers.js +93 -0
  106. package/src/motion/beat-board-model.js +84 -0
  107. package/src/motion/beat-board.css +124 -0
  108. package/src/motion/beat-board.jsx +60 -0
  109. package/src/motion/center-of-mass.js +66 -0
  110. package/src/motion/fix-collisions.js +850 -0
  111. package/src/motion/physics-review.js +118 -0
  112. package/src/{ardy → motion}/physics-support.js +1 -76
  113. package/src/motion/rig-snapshot.js +22 -0
  114. package/src/motion/take-segments.jsx +140 -0
  115. package/src/{ardy → motion}/timeline-coordinates.js +0 -21
  116. package/src/{ardy → motion}/timeline.css +1 -2
  117. package/src/{ardy → motion}/timeline.jsx +42 -413
  118. package/src/motion/waypoints.js +76 -0
  119. package/src/motion3/beats.js +83 -0
  120. package/src/motion3/body.js +158 -0
  121. package/src/motion3/bones.js +47 -0
  122. package/src/motion3/carried.js +47 -0
  123. package/src/motion3/compile-job.js +79 -0
  124. package/src/motion3/compile-worker.js +10 -0
  125. package/src/motion3/compiler/anchors.js +113 -0
  126. package/src/motion3/compiler/arm-orient.js +128 -0
  127. package/src/motion3/compiler/channels.js +125 -0
  128. package/src/motion3/compiler/compile.js +205 -0
  129. package/src/motion3/compiler/gait-rhythm.js +97 -0
  130. package/src/motion3/compiler/grip.js +171 -0
  131. package/src/motion3/compiler/head-steady.js +48 -0
  132. package/src/motion3/compiler/hold.js +134 -0
  133. package/src/motion3/compiler/idle.js +113 -0
  134. package/src/motion3/compiler/limbs.js +197 -0
  135. package/src/motion3/compiler/math.js +72 -0
  136. package/src/motion3/compiler/pose.js +177 -0
  137. package/src/motion3/compiler/quat.js +49 -0
  138. package/src/motion3/compiler/skeleton.js +74 -0
  139. package/src/motion3/compiler/validate-common.js +32 -0
  140. package/src/motion3/compiler/validate-goals.js +118 -0
  141. package/src/motion3/compiler/validate-objects.js +144 -0
  142. package/src/motion3/compiler/validate-parts.js +118 -0
  143. package/src/motion3/compiler/validate.js +122 -0
  144. package/src/motion3/compiler/vocab.js +74 -0
  145. package/src/motion3/compiler/walk-pelvis.js +275 -0
  146. package/src/motion3/compiler/walk.js +207 -0
  147. package/src/motion3/compiler/wrist-clamp.js +32 -0
  148. package/src/motion3/delta.js +47 -0
  149. package/src/motion3/describe.js +177 -0
  150. package/src/motion3/docs.js +27 -0
  151. package/src/motion3/edit-carry.js +21 -0
  152. package/src/motion3/fix/ease.js +16 -0
  153. package/src/motion3/fix/ground.js +37 -0
  154. package/src/motion3/fix/index.js +52 -0
  155. package/src/motion3/fix/push.js +71 -0
  156. package/src/motion3/fix/reach.js +46 -0
  157. package/src/motion3/fix/retime.js +79 -0
  158. package/src/motion3/fix/self.js +211 -0
  159. package/src/motion3/fix/slide.js +70 -0
  160. package/src/motion3/fix/turn.js +51 -0
  161. package/src/motion3/follow.js +39 -0
  162. package/src/motion3/free-motion.js +204 -0
  163. package/src/motion3/grip-choice.js +130 -0
  164. package/src/motion3/hold-plan.js +157 -0
  165. package/src/motion3/json-patch.js +77 -0
  166. package/src/motion3/key-aim.js +105 -0
  167. package/src/motion3/key-edit.js +82 -0
  168. package/src/motion3/lenient.js +127 -0
  169. package/src/motion3/object-keys.js +236 -0
  170. package/src/motion3/object-pose.js +182 -0
  171. package/src/motion3/object-tracks.js +12 -0
  172. package/src/motion3/people.js +82 -0
  173. package/src/motion3/pipeline.js +102 -0
  174. package/src/motion3/playback.js +51 -0
  175. package/src/motion3/probes.js +91 -0
  176. package/src/motion3/qa/body-checks.js +34 -0
  177. package/src/motion3/qa/check-names.js +14 -0
  178. package/src/motion3/qa/checks.js +125 -0
  179. package/src/motion3/qa/contact-checks.js +51 -0
  180. package/src/motion3/qa/explain.js +80 -0
  181. package/src/motion3/qa/fmt.js +5 -0
  182. package/src/motion3/qa/foot-checks.js +72 -0
  183. package/src/motion3/qa/geometry.js +77 -0
  184. package/src/motion3/qa/goals.js +191 -0
  185. package/src/motion3/qa/ground.js +60 -0
  186. package/src/motion3/qa/hints-contact.js +195 -0
  187. package/src/motion3/qa/hints-goal.js +160 -0
  188. package/src/motion3/qa/hints-knee.js +24 -0
  189. package/src/motion3/qa/hints-motion.js +209 -0
  190. package/src/motion3/qa/hints-object.js +91 -0
  191. package/src/motion3/qa/index.js +44 -0
  192. package/src/motion3/qa/key-checks.js +82 -0
  193. package/src/motion3/qa/near.js +96 -0
  194. package/src/motion3/qa/object-checks.js +135 -0
  195. package/src/motion3/qa/patch.js +161 -0
  196. package/src/motion3/qa/people-checks.js +37 -0
  197. package/src/motion3/qa/severity.js +38 -0
  198. package/src/motion3/qa/spec-checks.js +65 -0
  199. package/src/motion3/qa/style.js +228 -0
  200. package/src/motion3/qa/wrist-checks.js +69 -0
  201. package/src/motion3/qa/wrist-obs.js +60 -0
  202. package/src/motion3/refs.js +129 -0
  203. package/src/motion3/repair-tune.js +121 -0
  204. package/src/motion3/repair.js +83 -0
  205. package/src/motion3/report.js +67 -0
  206. package/src/motion3/rigs/index.js +14 -0
  207. package/src/motion3/rigs/x-bot-tpose-body.json +1 -0
  208. package/src/motion3/rigs/x-bot-tpose.json +1 -0
  209. package/src/motion3/rigs/y-bot-tpose-body.json +1 -0
  210. package/src/motion3/rigs/y-bot-tpose.json +1 -0
  211. package/src/motion3/scene.js +60 -0
  212. package/src/motion3/schema/brief.js +89 -0
  213. package/src/motion3/schema/fields.js +44 -0
  214. package/src/motion3/schema/spec-examples.js +46 -0
  215. package/src/motion3/schema/topics.js +113 -0
  216. package/src/motion3/schema/types.js +149 -0
  217. package/src/motion3/search.js +117 -0
  218. package/src/motion3/second-hand.js +112 -0
  219. package/src/motion3/settle.js +136 -0
  220. package/src/motion3/skills/basic.js +83 -0
  221. package/src/motion3/skills/book.js +76 -0
  222. package/src/motion3/skills/car.js +145 -0
  223. package/src/motion3/skills/flip.js +200 -0
  224. package/src/motion3/skills/frames-auto.js +29 -0
  225. package/src/motion3/skills/impact.js +109 -0
  226. package/src/motion3/skills/index.js +85 -0
  227. package/src/motion3/skills/info.js +80 -0
  228. package/src/motion3/skills/smoke-kit.js +109 -0
  229. package/src/motion3/skills/smoke.js +160 -0
  230. package/src/motion3/spec-store.js +59 -0
  231. package/src/motion3/take-edit.js +155 -0
  232. package/src/motion3/take.js +63 -0
  233. package/src/motion3/timeline.js +127 -0
  234. package/src/motion3/views.js +121 -0
  235. package/src/motion3/walk-plan.js +119 -0
  236. package/src/object-catalog.jsx +7 -1
  237. package/src/object-colors.js +98 -0
  238. package/src/object-gizmo-parts.js +115 -0
  239. package/src/object-gizmo.jsx +37 -122
  240. package/src/object-glow.js +139 -0
  241. package/src/object-hidden.js +33 -0
  242. package/src/object-look.js +196 -0
  243. package/src/object-size.js +10 -0
  244. package/src/object-travel.js +71 -6
  245. package/src/panels/CameraPanel.jsx +7 -7
  246. package/src/panels/HdrEnvironmentControls.jsx +59 -0
  247. package/src/panels/LightPanel.jsx +25 -4
  248. package/src/panels/ObjectLookPanel.jsx +88 -0
  249. package/src/panels/PosePanel.jsx +2 -17
  250. package/src/panels/RigControlPanel.jsx +5 -53
  251. package/src/panels/SubjectBox.jsx +4 -1
  252. package/src/panels/details.css +14 -0
  253. package/src/posestudio.jsx +4 -27
  254. package/src/project-browser.jsx +3 -4
  255. package/src/project-resources.js +18 -31
  256. package/src/project.js +13 -104
  257. package/src/props.jsx +25 -280
  258. package/src/qa-label.js +25 -0
  259. package/src/rail-dot-drag.js +23 -0
  260. package/src/render-passes.js +10 -0
  261. package/src/resource-status.jsx +1 -0
  262. package/src/result-modal.jsx +4 -19
  263. package/src/room.jsx +20 -12
  264. package/src/scene-assets.js +8 -6
  265. package/src/scene-light-place.js +72 -0
  266. package/src/scene-lights.jsx +148 -0
  267. package/src/scene-objects.js +31 -139
  268. package/src/scenes.js +15 -105
  269. package/src/semantic-edit.js +2 -5
  270. package/src/set-pieces.jsx +183 -0
  271. package/src/settings-menu.jsx +3 -11
  272. package/src/shell/BottomDock.jsx +9 -51
  273. package/src/shell/DetailsSlot.jsx +16 -200
  274. package/src/shell/GhostsMenuGroup.jsx +74 -0
  275. package/src/shell/LibrarySlot.jsx +1 -3
  276. package/src/shell/MenuBar.jsx +3 -3
  277. package/src/shell/PreferencesDialog.jsx +15 -264
  278. package/src/shell/PreferencesSlot.jsx +2 -8
  279. package/src/shell/TopBar.jsx +5 -84
  280. package/src/shell/ViewportToolbar.jsx +10 -70
  281. package/src/shell/agent-glass.css +19 -6
  282. package/src/shell/details-selection.js +42 -0
  283. package/src/shell/ghosts-menu.css +22 -0
  284. package/src/shell/glass-regions.css +29 -3
  285. package/src/shell/preferences.css +6 -1
  286. package/src/shot-authoring.js +4 -7
  287. package/src/soft-shadows.js +37 -0
  288. package/src/stage-attach-frames.js +12 -12
  289. package/src/stage-canvas-rigs.jsx +3 -3
  290. package/src/stage-capture.jsx +42 -8
  291. package/src/stage-characters.jsx +22 -216
  292. package/src/stage-editor-gizmos.jsx +6 -6
  293. package/src/stage-environment.js +93 -0
  294. package/src/stage-hdr-builtins.js +107 -0
  295. package/src/stage-hdr-image.js +116 -0
  296. package/src/stage-hdr-library.js +70 -0
  297. package/src/stage-hdr-map.js +94 -0
  298. package/src/stage-hdr.js +130 -0
  299. package/src/stage-lighting.js +149 -0
  300. package/src/stage-occlusion.js +210 -0
  301. package/src/stage-sky.jsx +159 -0
  302. package/src/stage-startup-defaults.js +5 -9
  303. package/src/studio-actions.js +16 -14
  304. package/src/studio-agent-commands.js +19 -16
  305. package/src/studio-agent-motion.js +9 -9
  306. package/src/studio-agent-protocol.js +47 -77
  307. package/src/studio-app-binding.js +27 -47
  308. package/src/studio-asset-list.js +17 -0
  309. package/src/studio-contact-sheet.js +37 -5
  310. package/src/studio-elements.js +27 -7
  311. package/src/studio-entity-bounds.js +18 -0
  312. package/src/studio-error-codes.js +15 -0
  313. package/src/studio-export-framing.js +152 -0
  314. package/src/studio-lighting-check.js +103 -0
  315. package/src/studio-motion-visual.js +150 -0
  316. package/src/studio-schema-errors.js +80 -0
  317. package/src/studio-support-gap.js +74 -0
  318. package/src/styles.css +1 -19
  319. package/src/timeline-defaults.js +5 -0
  320. package/src/timeline-extent.js +8 -20
  321. package/src/tutorial-walk.js +17 -0
  322. package/src/ui-scrub.js +1 -1
  323. package/src/use-shot-camera-tracks.js +12 -0
  324. package/src/view-ghosts.js +97 -0
  325. package/src/view-ghosts.jsx +49 -0
  326. package/src/workflow/AgentPanel.jsx +47 -166
  327. package/src/workflow/AgentProgressCard.jsx +109 -0
  328. package/src/workflow/AgentReplyText.jsx +37 -0
  329. package/src/workflow/AgentTurnSteps.jsx +84 -0
  330. package/src/workflow/LoginPanel.jsx +140 -0
  331. package/src/workflow/WorkflowBuilder.jsx +7 -23
  332. package/src/workflow/agent-client.js +54 -51
  333. package/src/workflow/agent-panel.css +71 -1
  334. package/src/workflow/agent-progress-card.css +167 -0
  335. package/src/workflow/agent-progress-view.js +47 -0
  336. package/src/workflow/agent-turn-steps.css +113 -0
  337. package/src/workflow/agent-turn.js +191 -0
  338. package/src/workflow/login-client.js +34 -0
  339. package/src/workflow/motion-input.js +18 -31
  340. package/src/workflow/node-schema.js +1 -1
  341. package/src/workflow/reply-markdown.js +69 -0
  342. package/src/workflow/scene-asset-sync.js +8 -8
  343. package/tools/agent-session-stats.mjs +61 -0
  344. package/tools/dev-full.mjs +5 -56
  345. package/tools/eslint-baseline.json +12 -13
  346. package/tools/knip-baseline.json +0 -179
  347. package/tools/loc-baseline.json +31 -81
  348. package/tools/motion3/extract-body.mjs +103 -0
  349. package/tools/motion3/extract-rig.mjs +85 -0
  350. package/tools/process-supervisor.mjs +0 -88
  351. package/tools/run-tests.mjs +9 -125
  352. package/bin/agent/motion-runtime.mjs +0 -76
  353. package/dist/ardy/cskel27-rest.json +0 -557
  354. package/dist/assets/analytics-C9pMpW7s.js +0 -1
  355. package/dist/assets/app-B_2XB9rw.css +0 -1
  356. package/dist/assets/app-DAuvKPP8.js +0 -4920
  357. package/dist/assets/config-Bx7pYsD9.js +0 -1
  358. package/dist/assets/config-DeoFTYzb.css +0 -1
  359. package/dist/assets/demo-CvKuBgy5.js +0 -1
  360. package/dist/assets/scene-asset-sync-DKkpjKBV.js +0 -19
  361. package/dist/assets/scene-asset-sync-DjJ_cJ2R.css +0 -1
  362. package/dist/assets/ticket-BPk2Ni95.js +0 -1
  363. package/dist/assets/workflow-jJRBR94Z.js +0 -179
  364. package/dist/demo/walk-then-stop.npz +0 -0
  365. package/mcp/ardy-prompts.mjs +0 -218
  366. package/src/ardy/auto-fix-panel.css +0 -108
  367. package/src/ardy/auto-physics.js +0 -1194
  368. package/src/ardy/client.js +0 -153
  369. package/src/ardy/convert.js +0 -313
  370. package/src/ardy/cskel27-neutral.js +0 -39
  371. package/src/ardy/cskel27.js +0 -77
  372. package/src/ardy/export.js +0 -165
  373. package/src/ardy/fix-collisions.js +0 -2257
  374. package/src/ardy/foot-lock.js +0 -230
  375. package/src/ardy/motion-calibration.js +0 -118
  376. package/src/ardy/motion-edit.js +0 -213
  377. package/src/ardy/motion-url.js +0 -55
  378. package/src/ardy/mp4-duration.js +0 -102
  379. package/src/ardy/npz.js +0 -595
  380. package/src/ardy/physics-panel.css +0 -21
  381. package/src/ardy/physics-panel.jsx +0 -89
  382. package/src/ardy/physics-refine.js +0 -111
  383. package/src/ardy/physics-review.js +0 -567
  384. package/src/ardy/physics-temporal.js +0 -54
  385. package/src/ardy/platform-fit-panel.css +0 -64
  386. package/src/ardy/platform-fit-panel.jsx +0 -41
  387. package/src/ardy/platform-fit.js +0 -326
  388. package/src/ardy/playback.js +0 -711
  389. package/src/ardy/pose-pin.js +0 -97
  390. package/src/ardy/prompt-clips.js +0 -30
  391. package/src/ardy/retime.js +0 -211
  392. package/src/ardy/root-drop.js +0 -369
  393. package/src/ardy/timeline-resize.js +0 -13
  394. package/src/ardy/to-cskel27.js +0 -225
  395. package/src/ardy/trim.js +0 -33
  396. package/src/ardy/waypoints.js +0 -369
  397. package/src/ardy-bridge-contract.js +0 -281
  398. package/src/domains/motion/motion-video-capture.js +0 -664
  399. package/src/i2v-motion-client.js +0 -77
  400. package/src/i2v-motion-studio.jsx +0 -180
  401. package/src/line-edit.js +0 -1348
  402. package/src/motion/generation.js +0 -105
  403. package/src/motion-readiness-ui.jsx +0 -67
  404. package/src/motion-readiness.js +0 -40
  405. package/src/motion-resources.js +0 -177
  406. package/src/motion-store.js +0 -65
  407. package/src/motion-trail.js +0 -601
  408. package/src/multimodel-ingest.js +0 -394
  409. package/src/panels/PromptBlocksPanel.jsx +0 -444
  410. package/src/panels/VideoCapturePanel.jsx +0 -181
  411. package/src/pose-extract/browser.js +0 -42
  412. package/src/pose-extract/detector.js +0 -100
  413. package/src/pose-extract/fit.js +0 -227
  414. package/src/pose-extract/image-frame.js +0 -51
  415. package/src/pose-extract/index.js +0 -58
  416. package/src/pose-extract/keyframes.js +0 -74
  417. package/src/pose-extract/landmarks.js +0 -162
  418. package/src/pose-extract/mirror.js +0 -153
  419. package/src/pose-extract/take.js +0 -131
  420. package/src/pose-extract/video-frames.js +0 -91
  421. package/src/take-recipe.js +0 -165
  422. package/src/trail-key-conflicts.js +0 -39
  423. package/src/trail-pick.js +0 -59
  424. package/src/use-i2v-capture-modal.js +0 -15
  425. package/tools/ardy/BRIDGE.md +0 -37
  426. package/tools/ardy/GVHMR-NPZ.md +0 -68
  427. package/tools/ardy/README.md +0 -124
  428. package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
  429. package/tools/ardy/artifacts.mjs +0 -34
  430. package/tools/ardy/bridge.mjs +0 -1732
  431. package/tools/ardy/bvh-cskel27.mjs +0 -1265
  432. package/tools/ardy/cclay_constrained_generate.py +0 -561
  433. package/tools/ardy/cclay_gvhmr_worker.py +0 -264
  434. package/tools/ardy/cclay_motion_edit.py +0 -510
  435. package/tools/ardy/cclay_sequence_generate.py +0 -705
  436. package/tools/ardy/cclay_worker.py +0 -304
  437. package/tools/ardy/dump-npz.py +0 -205
  438. package/tools/ardy/extract-rest.mjs +0 -299
  439. package/tools/ardy/extract.mjs +0 -507
  440. package/tools/ardy/footage.mjs +0 -467
  441. package/tools/ardy/gvhmr-floor.mjs +0 -102
  442. package/tools/ardy/gvhmr-to-cskel27.mjs +0 -21
  443. package/tools/ardy/gvhmr_fastpath.py +0 -168
  444. package/tools/ardy/gvhmr_trajectory.py +0 -240
  445. package/tools/ardy/merge-text-encoder.py +0 -160
  446. package/tools/ardy/mocap-metrics.mjs +0 -200
  447. package/tools/ardy/mocap-quality-gate.mjs +0 -110
  448. package/tools/ardy/motion-stabilize.mjs +0 -348
  449. package/tools/ardy/npz.mjs +0 -414
  450. package/tools/ardy/pose-to-npz.mjs +0 -106
  451. package/tools/ardy/prompt-limits.mjs +0 -5
  452. package/tools/ardy/run-edit-on-box.sh +0 -73
  453. package/tools/ardy/run-local.mjs +0 -233
  454. package/tools/ardy/run-on-box.sh +0 -593
  455. package/tools/ardy/run-sequence-on-box.sh +0 -177
  456. package/tools/ardy/runners/gvhmr-worker.mjs +0 -206
  457. package/tools/ardy/runners/index.mjs +0 -18
  458. package/tools/ardy/runners/local.mjs +0 -455
  459. package/tools/ardy/runners/proc.mjs +0 -125
  460. package/tools/ardy/runners/remote.mjs +0 -252
  461. package/tools/ardy/setup-local.mjs +0 -189
  462. package/tools/ardy/setup-text-encoder-on-box.sh +0 -93
  463. package/tools/ardy/setup-text-encoder.py +0 -306
  464. package/tools/ardy/smpl-cskel27.mjs +0 -178
  465. package/tools/ardy/visual-qa.mjs +0 -196
  466. package/tools/ardy/vq-car.mjs +0 -52
  467. package/tools/bench/EXP3.md +0 -61
  468. package/tools/bench/cclay_bench_extract_incam.py +0 -125
  469. package/tools/bench/cclay_bench_extract_obs.py +0 -204
  470. package/tools/bench/cclay_bench_runner.py +0 -42
  471. package/tools/bench/cube-contact.mjs +0 -162
  472. package/tools/bench/exp3.mjs +0 -152
  473. package/tools/bench/extract-bench-lib.mjs +0 -216
  474. package/tools/bench/extract-bench.mjs +0 -302
  475. package/tools/bench/fal-generate.mjs +0 -88
  476. package/tools/bench/fit/README.md +0 -189
  477. package/tools/bench/fit/camera.mjs +0 -59
  478. package/tools/bench/fit/contact.mjs +0 -419
  479. package/tools/bench/fit/footlock.mjs +0 -234
  480. package/tools/bench/fit/motion.mjs +0 -85
  481. package/tools/bench/fit/pin.mjs +0 -27
  482. package/tools/bench/fit/remote.mjs +0 -105
  483. package/tools/bench/fit-bench.mjs +0 -126
  484. package/tools/bench/fit-sanity.mjs +0 -45
  485. package/tools/bench/metrics.mjs +0 -313
  486. package/tools/bench/obs/depth.mjs +0 -258
  487. package/tools/bench/obs/extrinsics.mjs +0 -175
  488. package/tools/bench/obs/fit_mannequin_betas.py +0 -340
  489. package/tools/bench/obs/ground.mjs +0 -294
  490. package/tools/bench/obs/heading.mjs +0 -151
  491. package/tools/bench/obs/ladder.mjs +0 -555
  492. package/tools/bench/obs/mannequin-betas.json +0 -124
  493. package/tools/bench/obs/remote.mjs +0 -83
  494. package/tools/bench/obs/rest_joints.py +0 -52
  495. package/tools/bench/obs/ybot-targets.mjs +0 -49
  496. package/tools/bench/obs-bench.mjs +0 -520
  497. package/tools/bench/score.mjs +0 -343
  498. package/tools/bench/summarize.mjs +0 -86
  499. package/tools/demo-worker/README.md +0 -137
  500. package/tools/demo-worker/api-client.mjs +0 -233
  501. package/tools/demo-worker/generate.mjs +0 -301
  502. package/tools/demo-worker/index.mjs +0 -466
  503. package/tools/demo-worker/sign-core.mjs +0 -112
  504. package/tools/demo-worker/sign.mjs +0 -161
  505. package/tools/gt-render/browser.mjs +0 -182
  506. package/tools/gt-render/camera-math.mjs +0 -276
  507. package/tools/gt-render/page.mjs +0 -267
  508. package/tools/gt-render/render.mjs +0 -514
  509. package/tools/gt-render/scene-box.mjs +0 -31
  510. package/tools/gt-render/take-transform.mjs +0 -87
  511. package/tools/kimodo/compare-seam.mjs +0 -108
  512. package/tools/kimodo/constraints.mjs +0 -244
  513. package/tools/kimodo/edit.mjs +0 -259
  514. package/tools/kimodo/effector-constraints.mjs +0 -200
  515. package/tools/kimodo/generate.mjs +0 -646
  516. package/tools/kimodo/local-output.mjs +0 -265
  517. package/tools/kimodo/measure-pose.mjs +0 -65
  518. package/tools/kimodo/measure-preserve.mjs +0 -899
  519. package/tools/kimodo/measure-waypoints.mjs +0 -55
  520. package/tools/kimodo/mlx-generate.py +0 -111
  521. package/tools/kimodo/pose-constraints.mjs +0 -248
  522. package/tools/kimodo/preserve-mask.mjs +0 -543
  523. package/tools/kimodo/read-npz.mjs +0 -189
  524. package/tools/kimodo/run-edit-on-box.mjs +0 -155
  525. package/tools/kimodo/run-sequence-on-box.mjs +0 -178
  526. package/tools/kimodo/runner.mjs +0 -284
  527. package/tools/kimodo/seam-batch.mjs +0 -77
  528. package/tools/kimodo/setup-local.mjs +0 -30
  529. package/tools/kimodo/setup-on-box.sh +0 -339
  530. package/tools/kimodo/soma77-to-cskel27.mjs +0 -305
  531. package/tools/morphgs/assets/gen_truth.py +0 -39
  532. package/tools/morphgs/assets/mesh_ori_rig.txt +0 -27
  533. package/tools/morphgs/assets/playback-check.mjs +0 -30
  534. package/tools/morphgs/demo-gate.sh +0 -26
  535. package/tools/morphgs/fbx2morphgs.mjs +0 -68
  536. package/tools/morphgs/morphgs-to-cskel27.mjs +0 -105
  537. package/tools/morphgs/patches/preprocess_src-none-mode.patch +0 -76
  538. package/tools/morphgs/setup-on-cluster.sh +0 -126
  539. package/tools/projflow/driver.py +0 -1069
  540. package/tools/projflow/generate.mjs +0 -805
  541. package/tools/projflow/hml22-to-cskel27.mjs +0 -735
  542. package/tools/projflow/line-edit-job.mjs +0 -547
  543. package/tools/projflow/replay.mjs +0 -452
  544. package/tools/projflow/runner.mjs +0 -269
  545. package/tools/projflow/service.mjs +0 -742
  546. package/tools/track/DESIGN.md +0 -513
  547. package/tools/track/backfill-provenance.mjs +0 -116
  548. package/tools/track/budget.mjs +0 -8
  549. package/tools/track/check-rig.mjs +0 -209
  550. package/tools/track/diagnostics.schema.json +0 -60
  551. package/tools/track/export-rig.mjs +0 -256
  552. package/tools/track/fallback.mjs +0 -7
  553. package/tools/track/fk-parity-fixture.mjs +0 -161
  554. package/tools/track/gate.mjs +0 -349
  555. package/tools/track/masks.mjs +0 -318
  556. package/tools/track/metrics.mjs +0 -201
  557. package/tools/track/publish-obs.mjs +0 -108
  558. package/tools/track/py/check_env.py +0 -61
  559. package/tools/track/py/eval_lr.py +0 -229
  560. package/tools/track/py/lr_viterbi.py +0 -294
  561. package/tools/track/py/masks.py +0 -405
  562. package/tools/track/py/objective.py +0 -382
  563. package/tools/track/py/rig.py +0 -508
  564. package/tools/track/py/scene.py +0 -326
  565. package/tools/track/py/test_joint_indices.py +0 -123
  566. package/tools/track/py/test_lr_viterbi.py +0 -195
  567. package/tools/track/py/test_masks.py +0 -229
  568. package/tools/track/py/test_rig.py +0 -288
  569. package/tools/track/py/test_scene.py +0 -354
  570. package/tools/track/py/test_track.py +0 -328
  571. package/tools/track/py/track.py +0 -411
  572. package/tools/track/remote.mjs +0 -140
  573. package/tools/track/rig-dump.mjs +0 -227
  574. package/tools/track/run-box-tests.mjs +0 -14
  575. package/tools/track/run-box.mjs +0 -168
  576. package/tools/track/setup-box.sh +0 -59
  577. package/tools/track/study-2d.mjs +0 -728
  578. /package/src/{ardy → motion}/collision-blockers.js +0 -0
  579. /package/src/{ardy → motion}/ground.js +0 -0
  580. /package/src/{ardy → motion}/ik-drag.js +0 -0
  581. /package/src/{ardy → motion}/ik-key-json.js +0 -0
  582. /package/src/{ardy → motion}/ik.js +0 -0
  583. /package/src/{ardy → motion}/physics-surface.js +0 -0
  584. /package/src/{ardy → motion}/playback-clock.js +0 -0
  585. /package/src/{ardy → motion}/range-pin.js +0 -0
@@ -0,0 +1,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,136 @@
1
+ // How a falling object (C17, free-motion.js) comes to rest. It falls with its centre on a parabola, turning at the spin it
2
+ // was let go with, and lands on its lowest point. Then it rolls about what touches the support. Landing on an edge or a
3
+ // corner it rocks back onto the face nearest down, or falls over onto the face it leans to when it leans past that edge.
4
+ // Then it goes over an edge onto a lower face while the landing has the energy (a share TIP of the drop; only one that hit
5
+ // tilted or spinning, since a flat landing has nothing to tip it), or at once from a precarious face (one it can tilt less
6
+ // than PRECARIOUS before its centre passes the edge). So a phone on its edge falls flat, a tilted box comes down on a face,
7
+ // a can let go at a slant lies down, and a can or cup dropped upright stays upright. It only rolls onto a face that lowers
8
+ // its centre by at least LOWER of its height, so a near cube stays the way it lands. The shape is the box around the
9
+ // object's parts in its own frame, and its centre is the parts' volume-weighted centre. Every step is a rotation about a
10
+ // pivot on the support, paced by gravity: closed form and deterministic.
11
+ import { add, sub, scale, dot, cross, len, norm, mul, apply, axisRot, clamp } from "./compiler/math.js";
12
+ import { G } from "./free-motion.js";
13
+
14
+ /** The share of the drop height a landing spends tipping the object over an edge. */
15
+ export const TIP = 0.3;
16
+ const LOWER = 0.2, FLAT = 0.5, PRECARIOUS = 15, MIN_SPIN = 1, MAX_SPIN = 6 * Math.PI, MAX_STEPS = 3;
17
+ const DOWN = [0, -1, 0];
18
+ const AXES = [[1, 0, 0], [-1, 0, 0], [0, 1, 0], [0, -1, 0], [0, 0, 1], [0, 0, -1]];
19
+ const deg = r => (r * 180) / Math.PI;
20
+
21
+ /** The shape of part boxes { pos, R, half } given in the root frame: { com, lo, hi } (box around the corners); null without volume. */
22
+ export function shapeOf(boxes) {
23
+ const solid = boxes.filter(b => b.half[0] * b.half[1] * b.half[2] > 1e-12);
24
+ if (!solid.length) return null;
25
+ let vol = 0, c = [0, 0, 0];
26
+ const corners = [];
27
+ for (const b of solid) {
28
+ const v = b.half[0] * b.half[1] * b.half[2];
29
+ vol += v;
30
+ c = add(c, scale(add(b.pos, apply(b.R, [0, b.half[1], 0])), v));
31
+ for (const x of [-1, 1]) for (const y of [0, 2]) for (const z of [-1, 1]) corners.push(add(b.pos, apply(b.R, [x * b.half[0], y * b.half[1], z * b.half[2]])));
32
+ }
33
+ return { com: scale(c, 1 / vol), lo: [0, 1, 2].map(i => Math.min(...corners.map(p => p[i]))), hi: [0, 1, 2].map(i => Math.max(...corners.map(p => p[i]))) };
34
+ }
35
+
36
+ const boxCorners = s => [0, 1, 2, 3, 4, 5, 6, 7].map(i => [i & 1 ? s.hi[0] : s.lo[0], i & 2 ? s.hi[1] : s.lo[1], i & 4 ? s.hi[2] : s.lo[2]]);
37
+ const world = (pose, p) => add(pose.pos, apply(pose.R, p));
38
+ const lowest = (s, pose) => Math.min(...boxCorners(s).map(p => world(pose, p)[1]));
39
+ /** The pose moved up or down so its lowest corner sits on the support top. */
40
+ const snap = (s, pose, top) => ({ pos: [pose.pos[0], pose.pos[1] + top - lowest(s, pose), pose.pos[2]], R: pose.R });
41
+ const about = (pose, pivot, Q) => ({ pos: add(pivot, apply(Q, sub(pose.pos, pivot))), R: mul(Q, pose.R) });
42
+ /** The face whose outward normal points most nearly down. */
43
+ const downFace = R => AXES.reduce((best, n, k) => (dot(apply(R, n), DOWN) > dot(apply(R, AXES[best]), DOWN) ? k : best), 0);
44
+ /** Where the plane of face k lies along its axis, and the centre's distance to it (its height when it rests on k). */
45
+ const facePlane = (s, k) => { const a = k >> 1; return AXES[k][a] > 0 ? s.hi[a] : s.lo[a]; };
46
+ const heightOn = (s, k) => Math.abs(facePlane(s, k) - s.com[k >> 1]);
47
+
48
+ /** The spin (rad/s, world axis times rate) that takes rotation Ra to Rb in one frame at fps; capped at MAX_SPIN, none under MIN_SPIN (a hand's jitter). */
49
+ export function spinOf(Ra, Rb, fps) {
50
+ const D = mul(Rb, [[Ra[0][0], Ra[1][0], Ra[2][0]], [Ra[0][1], Ra[1][1], Ra[2][1]], [Ra[0][2], Ra[1][2], Ra[2][2]]]);
51
+ const a = Math.acos(clamp((D[0][0] + D[1][1] + D[2][2] - 1) / 2, -1, 1));
52
+ if (a * fps < MIN_SPIN) return [0, 0, 0];
53
+ const axis = norm([D[2][1] - D[1][2], D[0][2] - D[2][0], D[1][0] - D[0][1]]), rate = Math.min(MAX_SPIN, a * fps);
54
+ return len(axis) > 0 ? scale(axis, rate) : [0, 0, 0];
55
+ }
56
+
57
+ /** The fall through the air from `start` onto the support top: { tl (s), at(t) } with the centre on a parabola, turning at `spin`. */
58
+ export function airPath(s, start, spin, top, fps) {
59
+ const rate = len(spin), com0 = world(start, s.com);
60
+ const at = t => {
61
+ const R = rate > 0 ? mul(axisRot(spin, deg(rate * t)), start.R) : start.R;
62
+ return { pos: sub(add(com0, [0, -0.5 * G * t * t, 0]), apply(R, s.com)), R };
63
+ };
64
+ const h = Math.max(0, lowest(s, start) - top);
65
+ if (rate === 0) return { tl: Math.sqrt((2 * h) / G), at };
66
+ // spinning: the first sub-frame step whose lowest corner reaches the top, then halved down to the touch
67
+ const dt = 1 / (fps * 8), cap = Math.sqrt((2 * (h + len(sub(s.hi, s.lo)))) / G) + dt;
68
+ let a = 0, b = dt;
69
+ while (b < cap && lowest(s, at(b)) > top) { a = b; b += dt; }
70
+ for (let i = 0; i < 24 && b - a > 1e-7; i++) { const m = (a + b) / 2; if (lowest(s, at(m)) > top) a = m; else b = m; }
71
+ return { tl: b, at };
72
+ }
73
+
74
+ /** A step's duration (s): gravity-paced by the centre's height and the angle it rolls through. */
75
+ const stepTime = (height, angle) => 0.12 + 0.9 * Math.sqrt(Math.max(height, 1e-4) / G) * Math.sqrt(angle / 90);
76
+
77
+ /**
78
+ * From the pose it lands in, how it rolls to rest on the support top with `energy` (m of drop) to tip over edges:
79
+ * { steps: [{ from, pivot, axis, angle, time }], rest, time }. `lean` (horizontal) is the way a spin tips it: it gives a flat
80
+ * landing the energy and breaks a tie between edges.
81
+ */
82
+ export function settlePath(s, impact, top, energy, lean = [0, 0, 0]) {
83
+ const steps = [];
84
+ let pose = snap(s, impact, top), time = 0;
85
+ const push = (pivot, axis, angle) => {
86
+ const height = world(pose, s.com)[1] - top, t = stepTime(height, angle);
87
+ steps.push({ from: pose, pivot, axis, angle, time: t });
88
+ pose = snap(s, about(pose, pivot, axisRot(axis, angle)), top);
89
+ time += t;
90
+ };
91
+ // landed on an edge or a corner: back onto the face nearest down, or over onto the face it leans to when it leans past
92
+ // that edge (more than the angle its centre needs to pass it), rolling about the corners on the support
93
+ let k = downFace(pose.R);
94
+ const tiltOf = j => deg(Math.acos(clamp(dot(apply(pose.R, AXES[j]), DOWN), -1, 1))), tilt = tiltOf(k);
95
+ if (tilt > FLAT) {
96
+ const side = [0, 1, 2, 3, 4, 5].filter(j => j >> 1 !== k >> 1).reduce((a, j) => (tiltOf(j) < tiltOf(a) ? j : a), k === 0 || k === 1 ? 2 : 0);
97
+ if (tilt > deg(Math.atan2(heightOn(s, side), heightOn(s, k)))) k = side;
98
+ const touching = boxCorners(s).map(p => world(pose, p)).filter(p => p[1] <= top + 1e-4);
99
+ const n = apply(pose.R, AXES[k]);
100
+ push(scale(touching.reduce(add, [0, 0, 0]), 1 / touching.length), norm(cross(n, DOWN)), tiltOf(k));
101
+ }
102
+ // over an edge onto a lower face while the landing has the energy, at once from a precarious face (or a centre past the
103
+ // edge); a flat landing with no spin has none
104
+ let left = tilt > FLAT || len(lean) > 0 ? energy : 0;
105
+ for (let i = 0; i < MAX_STEPS; i++) {
106
+ const com = world(pose, s.com), cK = com[1] - top;
107
+ let best = null;
108
+ for (let j = 0; j < 6; j++) {
109
+ if (j >> 1 === k >> 1) continue;
110
+ const out = apply(pose.R, AXES[j]), edge = world(pose, AXES[j].map((v, a) => (v ? facePlane(s, j) : a === k >> 1 ? facePlane(s, k) : s.com[a])));
111
+ const w = -dot(sub(com, edge), out), cJ = heightOn(s, j);
112
+ if (!(w < 0 || cJ < cK * (1 - LOWER))) continue;
113
+ const cost = w < 0 || deg(Math.atan2(w, cK)) < PRECARIOUS ? 0 : Math.hypot(cK, w) - cK, side = dot(out, lean);
114
+ if (!best || cost < best.cost - 1e-9 || (Math.abs(cost - best.cost) <= 1e-9 && side > best.side)) best = { j, out, edge, cost, side };
115
+ }
116
+ if (!best || best.cost > left) break;
117
+ left -= best.cost;
118
+ const touching = boxCorners(s).map(p => world(pose, p)).filter(p => p[1] <= top + 1e-4 && dot(sub(p, best.edge), best.out) >= -1e-4);
119
+ push(touching.length ? scale(touching.reduce(add, [0, 0, 0]), 1 / touching.length) : [best.edge[0], top, best.edge[2]], norm(cross(best.out, DOWN)), 90);
120
+ k = best.j;
121
+ }
122
+ return { steps, rest: pose, time };
123
+ }
124
+
125
+ /** The pose `u` seconds after it landed, along the settle path: each step rolls about its pivot, accelerating like a fall. */
126
+ export function settledAt(path, u) {
127
+ let t = u;
128
+ for (const st of path.steps) {
129
+ if (t < st.time) { const s = t / st.time; return about(st.from, st.pivot, axisRot(st.axis, st.angle * s * s)); }
130
+ t -= st.time;
131
+ }
132
+ return path.rest;
133
+ }
134
+
135
+ /** The energy (m of drop) a spin adds: its kinetic energy over m g, for the box's radius of gyration about its centre. */
136
+ export const spinEnergy = (s, spin) => (len(spin) ** 2 * ((s.hi[0] - s.lo[0]) ** 2 + (s.hi[1] - s.lo[1]) ** 2 + (s.hi[2] - s.lo[2]) ** 2)) / (24 * G);
@@ -0,0 +1,83 @@
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
+ // the hand may touch what it grips: an object, or the other person (people.js) whose joint the anchor is on
75
+ const a = args.define ?? ctx.anchors?.[name], obj = a?.kind === "object" ? a.object : a?.kind === "person" ? `person:${a.person}` : null;
76
+ return {
77
+ keys: b.keys, end: cut ? last : ctx.f0 + reach + hold + release, ...(cut ? { cut: `release cut at f${last} (hold to the end)` } : {}), anchors: def,
78
+ props: { [prop]: { hand: k, origin: [`${S}HandIndex2`, `${S}HandMiddle2`], points: { fingers: [0, 0, 0] } } },
79
+ fits: [{ side: k, anchor: name, effector: { prop, point: "fingers" }, ...(args.orient === false ? { orient: false } : {}) }],
80
+ checks: { contacts: [{ name: `fingers on ${name}`, point: `prop:${prop}.fingers`, target: `anchor:${name}`, tol: 0.04, when: w }],
81
+ allow: obj ? [{ part: "hand", object: obj, depth: 0.04 }] : [] },
82
+ };
83
+ }
@@ -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;