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,211 @@
1
+ // Self-collision auto-fix (spec.fix.self, on unless false): the arms keep out of the rest of the body,
2
+ // on the fitted skin the BODY check measures (body.js, qa/body-checks.js). Wrist targets inside the
3
+ // body already went out onto the skin when they were solved (compile.js keepOut); what is left here:
4
+ // - elbow: an upper arm or forearm inside the body turns the elbow about the shoulder-wrist line
5
+ // (ctx.swivel; the hand keeps its place and turn). The turn is planned over the whole take at once,
6
+ // absolute: each frame scores every turn in TURNS by the depth left, the size of the turn and how
7
+ // far the elbow rises past LOW under the shoulder (no chicken wing), plus the change from the frame before, and the cheapest
8
+ // path wins (Viterbi), so the elbow eases in and out instead of snapping;
9
+ // - hand: a hand or forearm still a little inside moves its wrist IK target out along the skin's
10
+ // normal (ctx.skinPush), at most STEP a pass and REACH in all, eased over neighbouring frames. Deeper hits, and the
11
+ // head and neck (a hand at the mouth is on purpose), stay for QA.
12
+ // Frames whose arm is FK (armw 0) only get the elbow turn. The report says what moved.
13
+ import { bodyFor, bodyNormal, limbDepth, segmentDepth } from "../body.js";
14
+ import { add, sub, scale, len, apply, axisRot } from "../compiler/math.js";
15
+ import { CAPSULES, segPoints, sdBox } from "../qa/geometry.js";
16
+ import { round4 } from "./ease.js";
17
+ import { turnWeight } from "../compiler/pose.js";
18
+ import { allowance } from "../qa/checks.js";
19
+ import { regionOf } from "../qa/body-checks.js";
20
+
21
+ const TURNS = Array.from({ length: 19 }, (_, i) => -90 + i * 10);
22
+ const PASSES = 3, EASE = 5, MARGIN = 0.003, ALLOW = 0.003, STEP = 0.03, REACH = 0.08;
23
+ /** The most of a declared allowance the push leaves alone (m): a touch (a hand in a pocket), not a skill's leeway for a sweep it cannot avoid. */
24
+ const TOUCH = 0.03;
25
+ /** Plan weights: per cm^2 of depth, per (10 deg)^2 of turn, per cm^2 of elbow over the shoulder, per (10 deg)^2 of change a frame. */
26
+ const DEEP = 40, TURN = 0.05, RISE = 4, CHANGE = 2;
27
+ /** How far (m) under the shoulder the elbow may come before RISE counts. Where the spec declares a chicken-wing check
28
+ * on this elbow (checks.below, joint <Side>ForeArm under <Side>Arm: the smoke skill's), rising past its min costs
29
+ * WING_PRICE: a smoking elbow the turn raised past it broke that check once the wrist stopped at its limit (wrist-clamp.js). */
30
+ const LOW = 0.09, WING_PRICE = 400;
31
+
32
+ /** The spec's chicken-wing min (m) for the elbow of `side` on frame i (a below check on it at least half on), else null. */
33
+ function wingMin(out, i, side) {
34
+ let min = null;
35
+ for (const b of out.ctx.spec.checks?.below ?? []) {
36
+ const joint = String(b.joint).replace(/^joint:/, ""), ref = String(b.ref).replace(/^joint:/, ""), f = out.f0 + i;
37
+ const on = Array.isArray(b.when) ? f >= b.when[0] && f <= b.when[1] : b.when ? (out.frames[i].c[b.when] ?? 0) > 0.5 : true;
38
+ if (on && joint === `${side}ForeArm` && ref === `${side}Arm` && Number.isFinite(b.min)) min = Math.max(min ?? 0, b.min);
39
+ }
40
+ return min;
41
+ }
42
+ /** The most the turn changes in one frame (deg): a fast arm turned faster than this pops. */
43
+ const STEP_TURN = 10;
44
+ const SIDES = { l: "Left", r: "Right" };
45
+
46
+ /** How much hand k is on a measured target at channels c: a fit of its own (the mouth), or a fit of the
47
+ * other hand anchored to a prop k holds (the flame under k's cigarette). Such a hand is not moved. */
48
+ export function fitWeight(spec, c, k) {
49
+ let w = 0;
50
+ for (const fit of spec.fits ?? []) {
51
+ const a = spec.anchors?.[fit.anchor], on = c[`w_${fit.anchor}_${fit.side}`] ?? 0;
52
+ if (fit.side === k || (a?.kind === "prop" && spec.props?.[a.prop]?.hand === k)) w = Math.max(w, on);
53
+ }
54
+ return Math.min(1, w);
55
+ }
56
+
57
+ const CAP = Object.fromEntries(CAPSULES.map(c => [c[0], c[3]]));
58
+ /** How deep (m) segment A-B of radius r goes into the scene's boxes (QA's COLLIDE on the same capsules). */
59
+ function sceneDepth(boxes, A, B, r) {
60
+ let worst = 0;
61
+ for (const box of boxes) worst = Math.max(worst, r - Math.min(...segPoints(A, B, 5).map(p => sdBox(p, box))));
62
+ return worst;
63
+ }
64
+
65
+ /** Deepest skin depth (m) of the upper arm and forearm with the elbow turned `deg` more about the shoulder-wrist line,
66
+ * and how deep they go into the scene's boxes (an elbow turned out of the body must not turn into a steering wheel). */
67
+ function elbowDepth(body, sk, s, side, deg, boxes = []) {
68
+ const I = sk.index, a = I[`${side}Arm`], fa = I[`${side}ForeArm`], S = s.P[a], W = s.P[I[`${side}Hand`]];
69
+ const E = deg ? add(S, apply(axisRot(sub(W, S), deg), sub(s.P[fa], S))) : s.P[fa];
70
+ const worst = Math.max(segmentDepth(body, sk, s, a, S, E), segmentDepth(body, sk, s, fa, E, W));
71
+ const k = side === "Left" ? "l" : "r", scene = boxes.length ? Math.max(sceneDepth(boxes, S, E, CAP[`uarm_${k}`]), sceneDepth(boxes, E, W, CAP[`farm_${k}`])) : 0;
72
+ return { depth: worst, scene, rise: Math.max(0, E[1] - (S[1] - LOW)), under: S[1] - E[1] };
73
+ }
74
+
75
+ /** Shoulder to wrist at full stretch (m), at bind. */
76
+ const armLength = (sk, side) => { const I = sk.index, P = sk.restWorldPos; return len(sub(P[I[`${side}ForeArm`]], P[I[`${side}Arm`]])) + len(sub(P[I[`${side}Hand`]], P[I[`${side}ForeArm`]])); };
77
+
78
+ /** Absolute elbow turns (deg) for side k over the take, or null when no frame needs or has one. */
79
+ function planTurns(out, body, k) {
80
+ const { sk } = out.ctx, side = SIDES[k], n = out.frames.length, now = out.ctx.swivel[k];
81
+ const cost = out.frames.map(({ s, c }, i) => {
82
+ const tw = turnWeight(c[`armw_${k}`] ?? 0), cur = (now.get(out.f0 + i) ?? 0) * tw;
83
+ if (!cur && elbowDepth(body, sk, s, side, 0).depth <= ALLOW) return null;
84
+ // only the boxes the arm can reach this frame (their bounding sphere within the arm's length of the shoulder)
85
+ const S = s.P[sk.index[`${side}Arm`]], reach = armLength(sk, side) + 0.15;
86
+ const boxes = Object.values(out.objectsAt(i)).filter(box => box.half && !box.ignore && len(sub(add(box.pos, apply(box.R, [0, box.half[1], 0])), S)) - len(box.half) < reach);
87
+ const base = elbowDepth(body, sk, s, side, -cur, boxes).scene, wmin = wingMin(out, i, side);
88
+ return TURNS.map(deg => {
89
+ const { depth, scene, rise, under } = elbowDepth(body, sk, s, side, deg * tw - cur, boxes), cm = Math.max(0, depth - ALLOW) * 100;
90
+ const wing = wmin === null ? 0 : Math.max(0, wmin - under);
91
+ // a box the arm was already in at no turn is QA's COLLIDE to report, not this plan's to avoid
92
+ const hit = Math.max(0, scene - Math.max(base, ALLOW)) * 100;
93
+ return DEEP * (cm * cm + hit * hit) + TURN * (deg / 10) ** 2 + RISE * (rise * 100) ** 2 + WING_PRICE * (wing * 100) ** 2;
94
+ });
95
+ });
96
+ if (cost.every(c => !c)) return null;
97
+ // a frame that needs no turn has none; the take may start already turned
98
+ const zero = TURNS.indexOf(0), still = TURNS.map((_, j) => (j === zero ? 0 : Infinity));
99
+ let acc = TURNS.map(() => 0);
100
+ const back = [];
101
+ for (let i = 0; i < n; i++) {
102
+ const here = cost[i] ?? still, prev = acc, from = [];
103
+ acc = here.map((h, j) => {
104
+ let best = Infinity, arg = zero;
105
+ for (let p = 0; p < TURNS.length; p++) {
106
+ if (Math.abs(TURNS[j] - TURNS[p]) > STEP_TURN) continue;
107
+ const v = prev[p] + CHANGE * ((TURNS[j] - TURNS[p]) / 10) ** 2;
108
+ if (v < best) { best = v; arg = p; }
109
+ }
110
+ from.push(arg);
111
+ return best + h;
112
+ });
113
+ back.push(from);
114
+ }
115
+ const path = new Array(n);
116
+ let j = acc.indexOf(Math.min(...acc));
117
+ for (let i = n - 1; i >= 0; i--) { path[i] = TURNS[j]; j = back[i][j]; }
118
+ // the 10-degree steps, smoothed
119
+ return path.map((_, i) => (path[Math.max(0, i - 1)] + 2 * path[i] + path[Math.min(n - 1, i + 1)]) / 4);
120
+ }
121
+
122
+ /** Per-frame wrist pushes for side k: a hand or forearm a little inside the skin (not the head or neck) goes out along its normal. */
123
+ function handPushes(out, body, k, stats) {
124
+ const { sk } = out.ctx, side = SIDES[k], I = sk.index, n = out.frames.length, v = new Array(n).fill(null);
125
+ const face = [I.Head, I.Neck, I.HeadTop_End].filter(b => b !== undefined);
126
+ for (let i = 0; i < n; i++) {
127
+ const { s, c } = out.frames[i], f = out.f0 + i;
128
+ // a hand on a measured target (a fit's anchor: the mouth, the flame) stays where its goal needs it; on its way
129
+ // there the push fades out with the target's weight
130
+ const free = 1 - fitWeight(out.ctx.spec, c, k);
131
+ if (free < 0.05) continue;
132
+ // past what the spec allows (a hand in a pocket touches the hip: checks.allow), as QA's BODY measures it
133
+ let hit = null;
134
+ for (const b of [I[`${side}Hand`], I[`${side}ForeArm`]]) {
135
+ const h = limbDepth(body, sk, s, b);
136
+ if (h.into < 0 || face.includes(h.into)) continue;
137
+ const allowed = Math.min(TOUCH, allowance(out.ctx.spec.checks?.allow, regionOf(sk, b), regionOf(sk, h.into)) ?? 0), past = h.depth - Math.max(allowed, ALLOW);
138
+ if (past > 0 && (!hit || past > hit.past)) hit = { ...h, b, past };
139
+ }
140
+ if (!hit) continue;
141
+ if (!(c[`armw_${k}`] > 1e-4)) { stats.skipped++; continue; }
142
+ const done = len(out.ctx.skinPush[k].get(f) ?? [0, 0, 0]);
143
+ if (done >= REACH) { stats.deep++; continue; }
144
+ const nrm = bodyNormal(body, s, hit.p, new Set([...body.skip.get(hit.b), ...face]));
145
+ if (nrm) v[i] = scale(nrm, free * Math.min(STEP, REACH - done, hit.past + MARGIN));
146
+ }
147
+ // eased dilation: neighbours get the strongest nearby push, scaled down
148
+ const eased = new Array(n).fill(null);
149
+ v.forEach((p, i) => {
150
+ if (!p) return;
151
+ for (let j = Math.max(0, i - EASE - 1); j <= Math.min(n - 1, i + EASE + 1); j++) {
152
+ const t = 1 - Math.abs(i - j) / (EASE + 1), w = scale(p, t * t * (3 - 2 * t));
153
+ if (!eased[j] || len(w) > len(eased[j])) eased[j] = w;
154
+ }
155
+ });
156
+ return eased;
157
+ }
158
+
159
+ export function selfClear(out, engine) {
160
+ const body = bodyFor(out.ctx.sk);
161
+ if (!body) return null;
162
+ const n = out.frames.length, stats = { skipped: 0, deep: 0 }, turned = new Set(), pushed = new Set();
163
+ let maxTurn = 0, maxPush = 0;
164
+ for (let pass = 0; pass < PASSES; pass++) {
165
+ let touched = new Set();
166
+ for (const k of ["r", "l"]) {
167
+ const plan = planTurns(out, body, k);
168
+ if (!plan) continue;
169
+ plan.forEach((deg, i) => {
170
+ const f = out.f0 + i, cur = out.ctx.swivel[k].get(f) ?? 0, next = Math.abs(deg) < 0.5 ? 0 : deg;
171
+ if (Math.abs(next - cur) < 0.5) return;
172
+ if (next) out.ctx.swivel[k].set(f, next); else out.ctx.swivel[k].delete(f);
173
+ maxTurn = Math.max(maxTurn, Math.abs(next)); if (next) turned.add(`${k}${i}`); touched.add(f);
174
+ });
175
+ }
176
+ if (touched.size) engine.solve([...touched]);
177
+ touched = new Set();
178
+ stats.skipped = 0; stats.deep = 0;
179
+ for (const k of ["r", "l"]) {
180
+ handPushes(out, body, k, stats).forEach((p, i) => {
181
+ if (!p) return;
182
+ const f = out.f0 + i, next = add(out.ctx.skinPush[k].get(f) ?? [0, 0, 0], p);
183
+ out.ctx.skinPush[k].set(f, next);
184
+ maxPush = Math.max(maxPush, len(next)); pushed.add(`${k}${i}`); touched.add(f);
185
+ });
186
+ }
187
+ if (!touched.size) break;
188
+ engine.solve([...touched]);
189
+ }
190
+ // the pushes add up pass by pass from frames that read their normals apart: smoothed over time, so the hand
191
+ // does not jerk (a 1-4-6-4-1 kernel, twice)
192
+ if (pushed.size) {
193
+ const touched = new Set();
194
+ for (const k of ["r", "l"]) {
195
+ const at = i => out.ctx.skinPush[k].get(out.f0 + i) ?? [0, 0, 0];
196
+ for (let pass = 0; pass < 2; pass++) {
197
+ const was = Array.from({ length: n }, (_, i) => at(i));
198
+ was.forEach((_, i) => {
199
+ const sm = [-2, -1, 0, 1, 2].reduce((acc, d, j) => add(acc, scale(was[Math.min(n - 1, Math.max(0, i + d))], [1, 4, 6, 4, 1][j] / 16)), [0, 0, 0]);
200
+ if (len(sm) < 1e-5 && !out.ctx.skinPush[k].has(out.f0 + i)) return;
201
+ out.ctx.skinPush[k].set(out.f0 + i, sm); touched.add(out.f0 + i);
202
+ });
203
+ }
204
+ }
205
+ if (touched.size) engine.solve([...touched]);
206
+ }
207
+ if (!turned.size && !pushed.size && !stats.skipped) return null;
208
+ return { elbowFrames: turned.size, maxTurn: Math.round(maxTurn), handFrames: pushed.size, maxPush: round4(maxPush), skipped: stats.skipped, tooDeep: stats.deep,
209
+ how: "elbow turned about the shoulder-wrist line; a hand a little inside pushed out along the skin" };
210
+ }
211
+
@@ -0,0 +1,70 @@
1
+ // Planted-foot lock: while a heel or ball is planted (QA's own definition, over the ground under
2
+ // it: the floor or an object's top, qa/ground.js) and drifts less than
3
+ // 1 cm, the ankle target is shifted so the planted point stays where it landed. The foot pose
4
+ // depends only on the ankle target, fyaw and fpitch, so the shift moves the sole rigidly.
5
+ // A pivot (the planted point is the only one down: the heel is up over a planted ball, or the
6
+ // ball over a planted heel) is held whatever the drift: fyaw and fpitch then turn the foot about
7
+ // that point, as a batter's back foot turns on its ball, instead of about the ankle. A pivot that
8
+ // held more than 1 cm keeps holding while its point stays under PIVOT_H (a hover, not a step) and
9
+ // lets go no faster than EASE_STEP a frame.
10
+ import { add, sub } from "../compiler/math.js";
11
+ import { QA_LIMITS } from "../qa/checks.js";
12
+ import { groundSole } from "../qa/ground.js";
13
+ import { round4 } from "./ease.js";
14
+
15
+ const MAX_DRIFT = 0.01, DECAY = 4, PIVOT_H = 0.03, EASE_STEP = 0.02;
16
+
17
+ export function lockFeet(out, engine) {
18
+ const n = out.frames.length, H = QA_LIMITS.plantH;
19
+ let plants = 0, pivots = 0, worst = 0;
20
+ const touched = new Set();
21
+ for (const [k, S] of [["l", "Left"], ["r", "Right"]]) {
22
+ const ch = out.baked[`foot_${k}`];
23
+ if (!ch) continue;
24
+ const sole = out.frames.map((_, i) => groundSole(out, i, S));
25
+ const planted = (i, key) => sole[i][key][1] < H && sole[i - 1][key][1] < H && Math.abs(sole[i][key][1] - sole[i - 1][key][1]) < 0.0005;
26
+ const delta = Array.from({ length: n }, () => null);
27
+ for (let i = 1; i < n;) {
28
+ if (!planted(i, "ball") && !planted(i, "heel")) { i++; continue; }
29
+ // one contact run: the ball wins over the heel; a switch re-anchors on the new point
30
+ let key = null, ref = null, j = i, max = 0, last = [0, 0, 0], pivot = false;
31
+ const run = [];
32
+ const hold = jj => {
33
+ const d = sub(ref, sole[jj][key]); d[1] = 0;
34
+ last = d;
35
+ run.push([jj, d]);
36
+ max = Math.max(max, Math.hypot(d[0], d[2]));
37
+ };
38
+ for (; j < n && (planted(j, "ball") || planted(j, "heel")); j++) {
39
+ const kj = planted(j, "ball") ? "ball" : "heel";
40
+ pivot ||= sole[j][kj === "ball" ? "heel" : "ball"][1] >= H;
41
+ // on a switch the new point is anchored where the previous frame's shift put it
42
+ if (kj !== key) { key = kj; ref = add(sole[j - 1][key], last); }
43
+ hold(j);
44
+ }
45
+ if (pivot && max >= MAX_DRIFT) for (; j < n && sole[j][key][1] < PIVOT_H; j++) hold(j);
46
+ if (max > 2e-4 && (max < MAX_DRIFT || pivot)) {
47
+ plants++; worst = Math.max(worst, max);
48
+ if (max >= MAX_DRIFT) pivots++;
49
+ for (const [jj, d] of run) delta[jj] = d;
50
+ const decay = Math.max(DECAY, Math.ceil(Math.hypot(last[0], last[2]) / EASE_STEP));
51
+ for (let m = 1; m <= decay && j - 1 + m < n; m++) {
52
+ if (delta[j - 1 + m]) break;
53
+ const t = m / (decay + 1), e = 1 - t * t * (3 - 2 * t);
54
+ delta[j - 1 + m] = [last[0] * e, 0, last[2] * e];
55
+ }
56
+ }
57
+ i = j;
58
+ }
59
+ for (let i = 0; i < n; i++) {
60
+ if (!delta[i]) continue;
61
+ const f = out.f0 + i;
62
+ ch[f] = add(ch[f], delta[i]);
63
+ touched.add(f);
64
+ }
65
+ }
66
+ if (!plants) return null;
67
+ engine.solve([...touched]);
68
+ return { plants, ...(pivots ? { pivots } : {}), frames: touched.size, max: round4(worst),
69
+ how: pivots ? "planted heel/ball held in place; turning feet pivot on it" : "planted heel/ball held in place" };
70
+ }
@@ -0,0 +1,51 @@
1
+ // A planted flat foot keeps its heading (lane Q1). While both its heel and its ball are planted
2
+ // (QA's definition: under plantH over the ground, qa/ground.js, and still), the foot cannot turn without one of them sliding: a
3
+ // flat foot under the body's weight grips the floor. A turn the channels ask for then (fyaw_<k>, or
4
+ // the body's yaw when fyaw_<k> is not keyed) waits: the foot keeps the heading it planted with
5
+ // until its heel or its toes come up or it lifts, and then eases onto the keyed heading at about
6
+ // TURN_RATE degrees a frame. With the heel up it turns on its ball (slide.js holds the ball and
7
+ // pivots the foot there), in the air it turns freely. Session 4db5d447: fyaw_r keyed 0 at f0 and
8
+ // 30 at f37 turned the flat back foot 9 deg over the load (f10-19), and its heel slid 2.2 cm.
9
+ // A foot that turns only while a heel or ball is up, and a flat foot that does not turn, are left
10
+ // as they were.
11
+ import { QA_LIMITS } from "../qa/checks.js";
12
+ import { groundSole } from "../qa/ground.js";
13
+ import { round4 } from "./ease.js";
14
+
15
+ const TURN_RATE = 6, DECAY = 4, TOL = 1;
16
+ const smooth = t => t * t * (3 - 2 * t);
17
+
18
+ /** Holds the heading of flat planted feet (fyaw_<k>), eases it onto the keys once a heel or ball is up; re-solves those frames. */
19
+ export function holdTurns(out, engine) {
20
+ const n = out.frames.length, H = QA_LIMITS.plantH, touched = new Set();
21
+ let worst = 0;
22
+ for (const [k, S] of [["l", "Left"], ["r", "Right"]]) {
23
+ const keyed = out.baked[`fyaw_${k}`] ?? out.baked.yaw;
24
+ if (!out.baked[`foot_${k}`] || !keyed) continue;
25
+ const sole = out.frames.map((_, i) => groundSole(out, i, S));
26
+ const down = (i, key) => sole[i][key][1] < H && sole[i - 1][key][1] < H && Math.abs(sole[i][key][1] - sole[i - 1][key][1]) < 0.0005;
27
+ const flat = i => i > 0 && i < n && down(i, "heel") && down(i, "ball");
28
+ const want = Array.from({ length: n }, (_, i) => keyed[out.f0 + i]), yaw = want.slice();
29
+ for (let i = 1; i < n; i++) {
30
+ if (!flat(i)) continue;
31
+ const held = yaw[i - 1];
32
+ let j = i, turn = 0;
33
+ for (; flat(j); j++) turn = Math.max(turn, Math.abs(yaw[j] - held));
34
+ // a turn within TOL degrees (the heel moves under 3 mm) is left as it is
35
+ if (turn <= TOL) { i = j; continue; }
36
+ worst = Math.max(worst, turn);
37
+ for (let m = i; m < j; m++) yaw[m] = held;
38
+ // then onto the keyed heading, as long as the foot does not plant flat again
39
+ const gap = j < n ? want[j] - held : 0, ease = Math.max(DECAY, Math.ceil(Math.abs(gap) / TURN_RATE));
40
+ for (let m = 0; gap && m < ease && j + m < n && !flat(j + m); m++) yaw[j + m] = want[j + m] - gap * (1 - smooth((m + 1) / (ease + 1)));
41
+ i = j;
42
+ }
43
+ const moved = yaw.map((v, i) => v !== want[i]);
44
+ if (!moved.some(Boolean)) continue;
45
+ const ch = (out.baked[`fyaw_${k}`] = keyed.slice());
46
+ moved.forEach((m, i) => { if (m) { ch[out.f0 + i] = yaw[i]; touched.add(out.f0 + i); } });
47
+ }
48
+ if (!touched.size) return null;
49
+ engine.solve([...touched]);
50
+ return { frames: touched.size, max: round4(worst) };
51
+ }
@@ -0,0 +1,39 @@
1
+ // C16. Follow entries: spec.objects[id] = { follow: <ref>, offset?, aim?, visible?, level? } puts a
2
+ // record (a body, a group root, or a record with no box: an effect, a light, an Empty) on a point
3
+ // of the body at every frame. The ref is any point ref (refs.js): landmark:mouth, joint:<Bone>,
4
+ // prop:<prop>.<point>, anchor:<name>, object:<id> (an object a follow entry does not move).
5
+ // aim where the record's +Y points (effects emit along it): "up" (default, world up), "face"
6
+ // (the face's forward direction, the Head bone's rest +Z, as checks.orient "face"), a
7
+ // world direction [x, y, z], or a ref (toward that point). The record turns the shortest
8
+ // way from upright onto it.
9
+ // offset metres along the record's own axes after aim (+Y along aim), from the ref point.
10
+ // The take bakes a world-space track at every frame (take.js: pos, quat and hold on every frame,
11
+ // so object-travel.js cuts the carry of the record's own parents), so the renderer and the effects
12
+ // (born at the emitter's pose at the birth frame) see the moving path. A follow entry is not an
13
+ // obstacle (compile.js marks its bodies carried) and QA checks it against nothing: it rides the body.
14
+ import { refPoint } from "./refs.js";
15
+ import { carried } from "./compiler/limbs.js";
16
+ import { add, sub, apply, norm, len, matFromQuat } from "./compiler/math.js";
17
+ import { arcQuat } from "./compiler/quat.js";
18
+
19
+ const UP = [0, 1, 0];
20
+
21
+ /** The unit direction aim gives at absolute frame f (inside the window), `from` the followed point. */
22
+ function aimDir(out, f, aim = "up", from) {
23
+ if (Array.isArray(aim)) return norm(aim);
24
+ if (aim === "face") { const { sk } = out.ctx; return norm(carried(sk, out.frames[f - (out.f0 ?? 0)].s, sk.index.Head, [0, 0, 1])); }
25
+ const to = aim === "up" ? null : refPoint(out, f, aim), d = to ? sub(to, from) : UP;
26
+ return len(d) > 1e-9 ? norm(d) : UP;
27
+ }
28
+
29
+ /**
30
+ * World pose { pos, R } of the record follow entry o puts at absolute frame f of compile result
31
+ * out (a frame outside the compiled window takes the nearest one in it), or null when the ref
32
+ * has no point there.
33
+ */
34
+ export function followPose(out, o, f) {
35
+ const lo = out.f0 ?? 0, ff = Math.max(lo, Math.min(lo + out.frames.length - 1, f)), p = refPoint(out, ff, o.follow);
36
+ if (!p) return null;
37
+ const R = matFromQuat(arcQuat(UP, aimDir(out, ff, o.aim, p)));
38
+ return { pos: add(p, apply(R, o.offset ?? [0, 0, 0])), R };
39
+ }
@@ -0,0 +1,204 @@
1
+ // C17. Objects nobody holds (a can inside a machine, a box on a shelf) move by keys and fall.
2
+ // spec.objects[id] with no prop, follow or hinge field, for a body or a group (its root):
3
+ // keys [{ f, pos, rot | aim (+ roll) }] the world pose of its root (a body's base pivot, a group's
4
+ // origin) at frame f, on the curve held keys use (object-keys.js keyedPose, C14). aim turns the
5
+ // scene pose's +Y onto aim, then roll degrees about it. Before the first key of a run it stays
6
+ // where it was (its scene pose, or where it landed), after the last it holds the last key.
7
+ // fall { at, onto?, bounce?, tumble? } or a list from where it is at frame `at` it falls under gravity
8
+ // (G m/s^2) onto the top of the highest still body under the centre of its parts (onto: that object's
9
+ // top; nothing below: the floor, y 0) and comes to rest on a stable face (settle.js): it lands on its
10
+ // lowest point, turning at the spin it was let go with, rolls flat and over an edge onto a lower face
11
+ // while the drop has the energy (a phone falls flat, a can lies down). tumble: false drops it straight
12
+ // down as it was, bouncing at most once (bounce: the restitution 0..0.6, default 0.25, never higher
13
+ // than BOUNCE_MAX m); one that lands on a stable face as it falls bounces the same way, and one that
14
+ // rolls spends its landing on the roll and does not bounce (bounce is not read for it).
15
+ // Keys and falls chain in frame order: a fall starts from the pose the keys before it leave (a key on the
16
+ // fall's own frame comes first), keys after a fall take over at their first frame.
17
+ // A carry (C3) takes fall too: before its grab (the hand finds it where it landed) or from its release on.
18
+ // A free object moves like a hinged one (compiler/compile.js objectTracks): an obstacle at its pose that
19
+ // frame, read through object-pose.js by refs, goals, probes, observations.near and QA. Pure over the spec,
20
+ // the scene and the time map (a retime moves its keys and falls with the body's keys): the body never
21
+ // moves it, and nothing it lands on moves (bodies a spec.objects entry moves are no support).
22
+ import { isCarried, compose, relative, movesRecord } from "./carried.js";
23
+ import { groupsOf } from "./scene.js";
24
+ import { curveOf, keyedPose } from "./object-keys.js";
25
+ import { add, apply, sub, T, quatFromMat, I3 } from "./compiler/math.js";
26
+ import { shapeOf, airPath, settlePath, settledAt, spinOf, spinEnergy, TIP } from "./settle.js";
27
+
28
+ /** Gravity, m/s^2. */
29
+ export const G = 9.8;
30
+ /** The highest a bounce goes, metres. */
31
+ export const BOUNCE_MAX = 0.03;
32
+ const BOUNCE = 0.25, TOUCH = 0.005;
33
+ const HELD_OR_HINGED = ["prop", "follow", "pivot", "axis", "channel"];
34
+
35
+ /** A free entry (C17): no prop, follow or hinge field, and keys or fall. */
36
+ export const isFree = o => !!o && typeof o === "object" && !Array.isArray(o) && HELD_OR_HINGED.every(k => o[k] === undefined) && (o.keys !== undefined || o.fall !== undefined);
37
+ /** The falls of an entry (fall is one or a list), as given. */
38
+ const fallsOf = o => (o?.fall === undefined ? [] : Array.isArray(o.fall) ? o.fall : [o.fall]);
39
+
40
+ const restOf = (scene, id) => scene?.objects?.[id] ?? groupsOf(scene)[id] ?? null;
41
+ const partsIn = (scene, id) => Object.keys(scene?.objects ?? {}).filter(k => k === id || scene.objects[k].ancestors?.includes(id));
42
+
43
+ /** The 8 corners of a base-pivot box { pos, R, half }. */
44
+ function corners(box) {
45
+ const out = [];
46
+ for (const x of [-1, 1]) for (const y of [0, 2]) for (const z of [-1, 1]) out.push(add(box.pos, apply(box.R, [x * box.half[0], y * box.half[1], z * box.half[2]])));
47
+ return out;
48
+ }
49
+ const topOf = box => Math.max(...corners(box).map(p => p[1]));
50
+ /** True when the vertical through x/z point p crosses the footprint of box. */
51
+ const under = (box, p) => { const q = apply(T(box.R), sub([p[0], box.pos[1] + box.half[1], p[2]], box.pos)); return Math.abs(q[0]) <= box.half[0] && Math.abs(q[2]) <= box.half[2]; };
52
+
53
+ /** Bodies a spec.objects entry moves (itself or a group above it): they hold nothing up. */
54
+ function moversIn(spec, scene) {
55
+ const out = new Set();
56
+ for (const [id, o] of Object.entries(scene?.objects ?? {})) if ([id, ...(o.ancestors ?? [])].some(k => movesRecord(spec.objects?.[k]))) out.add(id);
57
+ return out;
58
+ }
59
+
60
+ /** The part boxes of `id` with its root at world pose `root`. */
61
+ function boxesAtRoot(scene, id, root) {
62
+ const rest = restOf(scene, id);
63
+ return partsIn(scene, id).map(p => ({ id: p, ...compose(root, relative(rest, scene.objects[p])), half: scene.objects[p].half }));
64
+ }
65
+
66
+ /** What `id` lands on from pose `start`: { support (a body id, or null for the floor), top, h (the drop, m) }. */
67
+ export function landingOf(spec, scene, id, start, onto = null) {
68
+ const boxes = boxesAtRoot(scene, id, start), own = new Set(boxes.map(b => b.id)), movers = moversIn(spec, scene);
69
+ if (!boxes.length) return { support: null, top: 0, h: 0 };
70
+ const low = Math.min(...boxes.flatMap(b => corners(b).map(p => p[1])));
71
+ const c = boxes.flatMap(corners).reduce((s, p) => add(s, p), [0, 0, 0]).map(v => v / (boxes.length * 8));
72
+ const pool = (onto ? partsIn(scene, onto) : Object.keys(scene.objects)).filter(k => !own.has(k) && !movers.has(k));
73
+ let best = null;
74
+ for (const k of pool) {
75
+ const box = scene.objects[k], top = topOf(box);
76
+ if (under(box, c) && top <= low + TOUCH && (!best || top > best.top)) best = { support: k, top };
77
+ }
78
+ // onto an object that is not right below: its highest top, the agent placed it
79
+ if (!best && onto) for (const k of pool) { const top = topOf(scene.objects[k]); if (top <= low + TOUCH && (!best || top > best.top)) best = { support: k, top }; }
80
+ best ??= { support: null, top: 0 };
81
+ return { ...best, h: Math.max(0, low - best.top) };
82
+ }
83
+
84
+ const shapes = new WeakMap();
85
+ /** The shape of `id` in its root frame (settle.js), once per scene. */
86
+ function shapeIn(scene, id) {
87
+ if (!shapes.has(scene)) shapes.set(scene, new Map());
88
+ const cache = shapes.get(scene);
89
+ if (!cache.has(id)) cache.set(id, shapeOf(boxesAtRoot(scene, id, { pos: [0, 0, 0], R: I3() })));
90
+ return cache.get(id);
91
+ }
92
+
93
+ /**
94
+ * A fall from root pose `start` at (mapped) frame t0: its timing, its pose at a frame, where it rests. `before` is its pose
95
+ * a frame earlier (a release from the hand), for the spin it is let go with.
96
+ */
97
+ function fallPlan(spec, scene, id, start, t0, fall, fps, before = null) {
98
+ const land = landingOf(spec, scene, id, start, fall.onto ?? null), shape = fall.tumble === false ? null : shapeIn(scene, id);
99
+ if (shape) {
100
+ const spin = before ? spinOf(before.R, start.R, fps) : [0, 0, 0], air = airPath(shape, start, spin, land.top, fps);
101
+ // a spin tips it the way its top moves (spin x up); a spin about the vertical tips nothing
102
+ const path = settlePath(shape, air.at(air.tl), land.top, TIP * land.h + spinEnergy(shape, spin), [-spin[2], 0, spin[0]]);
103
+ if (path.steps.length || spin.some(v => v)) {
104
+ const at = f => { const t = (f - t0) / fps; return t <= 0 ? start : t <= air.tl ? air.at(t) : settledAt(path, t - air.tl); };
105
+ return { t0, land: t0 + air.tl * fps, end: t0 + (air.tl + path.time) * fps, h: land.h, support: land.support, top: land.top, at, rest: path.rest };
106
+ }
107
+ }
108
+ const h = land.h, tl = Math.sqrt(2 * h / G);
109
+ const e = Math.min(0.6, Math.max(0, fall.bounce ?? BOUNCE)), vb = Math.min(e * G * tl, Math.sqrt(2 * G * BOUNCE_MAX)), tb = 2 * vb / G;
110
+ const rest = { pos: [start.pos[0], start.pos[1] - h, start.pos[2]], R: start.R };
111
+ const at = f => {
112
+ const t = (f - t0) / fps;
113
+ if (t <= 0) return start;
114
+ const dy = t <= tl ? -0.5 * G * t * t : t <= tl + tb ? -h + vb * (t - tl) - 0.5 * G * (t - tl) ** 2 : -h;
115
+ return { pos: [start.pos[0], start.pos[1] + Math.min(0, dy), start.pos[2]], R: start.R };
116
+ };
117
+ return { t0, land: t0 + tl * fps, end: t0 + (tl + tb) * fps, h, support: land.support, top: land.top, at, rest };
118
+ }
119
+
120
+ /** The events of an entry's falls and key runs in frame order (keys split into runs by the falls between them). */
121
+ function eventsOf(o, map, from = -Infinity, to = Infinity) {
122
+ // a carry's keys pose it in hand (C14); only its falls are free
123
+ const keys = isCarried(o) ? [] : [...(o.keys ?? [])].sort((a, b) => a.f - b.f);
124
+ const falls = fallsOf(o).map(fl => ({ ...fl, t: map(fl.at) })).filter(fl => fl.t >= from && fl.t < to).sort((a, b) => a.t - b.t);
125
+ const events = [];
126
+ let run = [], i = 0;
127
+ for (const k of keys) {
128
+ while (i < falls.length && falls[i].t < map(k.f)) { if (run.length) events.push({ run }); run = []; events.push({ fall: falls[i++] }); }
129
+ run.push(k);
130
+ }
131
+ if (run.length) events.push({ run });
132
+ while (i < falls.length) events.push({ fall: falls[i++] });
133
+ return events;
134
+ }
135
+
136
+ /** The pose events give at mapped frame f, starting from pose `start`; `keep` caches each fall's plan on its event. */
137
+ function walk(events, f, start, plan, keep = true) {
138
+ let pose = start;
139
+ for (const e of events) {
140
+ if (e.run) {
141
+ const { t } = e.curve;
142
+ if (f < t[0]) return pose;
143
+ if (f <= t[t.length - 1]) return keyedPose(e.curve, f);
144
+ pose = keyedPose(e.curve, t[t.length - 1]);
145
+ } else {
146
+ if (f < e.fall.t) return pose;
147
+ const p = keep ? (e.plan ??= plan(pose, e.fall)) : plan(pose, e.fall);
148
+ if (f < p.end) return p.at(f);
149
+ pose = p.rest;
150
+ }
151
+ }
152
+ return pose;
153
+ }
154
+
155
+ const timelines = new WeakMap();
156
+ /** The events of entry o under time map `map`, cached per scene, entry and span (key curves built once). */
157
+ function timelineOf(ctx, id, o, map, part) {
158
+ if (!timelines.has(ctx.scene)) timelines.set(ctx.scene, new Map());
159
+ const cache = timelines.get(ctx.scene), key = `${id}|${part}|${ctx.grab}|${ctx.release}`, had = cache.get(key);
160
+ if (had && had.map === map && had.o === o && had.spec === ctx.spec) return had.events;
161
+ const rest = restOf(ctx.scene, id), m = map ?? (f => f), [from, to] = part === "before" ? [-Infinity, ctx.grab] : part === "after" ? [ctx.release, Infinity] : [-Infinity, Infinity];
162
+ const events = eventsOf(o, m, from, to);
163
+ for (const e of events) if (e.run) e.curve = curveOf({ keys: e.run }, m, () => quatFromMat(rest.R), null);
164
+ cache.set(key, { map, o, spec: ctx.spec, events });
165
+ return events;
166
+ }
167
+
168
+ const planner = (ctx, id) => (start, fall) => fallPlan(ctx.spec, ctx.scene, id, start, fall.t, fall, ctx.spec.fps ?? 30);
169
+
170
+ /**
171
+ * World pose { pos, R } of the root of free entry `id` at absolute frame f (ctx = { spec, scene,
172
+ * timeMap }): keys and falls (C17) from its scene pose. null for an id the scene does not know.
173
+ */
174
+ export function freePoseAt(ctx, id, f) {
175
+ const rest = restOf(ctx.scene, id), o = ctx.spec.objects?.[id];
176
+ if (!rest || !o) return null;
177
+ return walk(timelineOf(ctx, id, o, ctx.timeMap, "all"), f, { pos: rest.pos, R: rest.R }, planner(ctx, id));
178
+ }
179
+
180
+ /** Where carry `id` (entry o) is before its grab at absolute f: its scene pose, or where a fall before grab left it. */
181
+ export function beforeGrab(ctx, id, o, f, grab) {
182
+ const rest = restOf(ctx.scene, id);
183
+ if (!rest || !fallsOf(o).length) return rest && { pos: rest.pos, R: rest.R };
184
+ return walk(timelineOf({ ...ctx, grab }, id, o, ctx.timeMap, "before"), f, { pos: rest.pos, R: rest.R }, planner(ctx, id));
185
+ }
186
+
187
+ /**
188
+ * Where carry `id` (entry o) is at absolute f after its release, from its release pose; null without a fall from the release on.
189
+ * `beforePose` (a frame before the release) gives the spin it is let go with to a fall that starts right at the release.
190
+ */
191
+ export function afterRelease(ctx, id, o, f, release, releasePose, beforePose = null) {
192
+ const events = fallsOf(o).length ? timelineOf({ ...ctx, release }, id, o, ctx.timeMap, "after") : [];
193
+ // the release pose comes from the solved body, which a rebake changes: plan these falls afresh
194
+ const plan = (start, fall) => fallPlan(ctx.spec, ctx.scene, id, start, fall.t, fall, ctx.spec.fps ?? 30, fall.t <= release + 1 ? beforePose : null);
195
+ return events.length ? walk(events, f, releasePose, plan, false) : null;
196
+ }
197
+
198
+ /** The falls of entry `id` as planned under the current time map, for readers (timeline, tests): [{ t0, land, end, h, support, top }]. */
199
+ export function fallPlans(ctx, id) {
200
+ const o = ctx.spec.objects?.[id];
201
+ if (!isFree(o)) return [];
202
+ freePoseAt(ctx, id, Infinity);
203
+ return timelineOf(ctx, id, o, ctx.timeMap, "all").filter(e => e.plan).map(({ plan: { t0, land, end, h, support, top } }) => ({ t0, land, end, h, support, top }));
204
+ }