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,77 @@
1
+ // Collision proxies for motion QA: body capsules on Mixamo joints, scene objects as oriented
2
+ // boxes (base pivot = bottom centre, CozyClay convention), the head as the measured ellipsoid.
3
+ import { add, sub, scale, dot, len, apply, T, clamp } from "../compiler/math.js";
4
+ import { bonePoint } from "../compiler/skeleton.js";
5
+
6
+ // [part, from joint, to joint, radius m]. Radii are fitted to the X/Y Bot skin.
7
+ export const CAPSULES = [
8
+ ["torso_lo", "Spine", "Spine2", 0.12], ["torso_hi", "Spine2", "Neck", 0.105], ["hips", "LeftUpLeg", "RightUpLeg", 0.095],
9
+ ["neck", "Neck", "Head", 0.055],
10
+ ...["Left", "Right"].flatMap(S => {
11
+ const k = S === "Left" ? "l" : "r";
12
+ return [[`thigh_${k}`, `${S}UpLeg`, `${S}Leg`, 0.07], [`shin_${k}`, `${S}Leg`, `${S}Foot`, 0.05],
13
+ [`uarm_${k}`, `${S}Arm`, `${S}ForeArm`, 0.042], [`farm_${k}`, `${S}ForeArm`, `${S}Hand`, 0.033],
14
+ [`hand_${k}`, `${S}Hand`, `${S}HandMiddle3`, 0.022]];
15
+ }),
16
+ ];
17
+
18
+ export const segPoints = (a, b, n = 9) => Array.from({ length: n }, (_, i) => add(a, scale(sub(b, a), i / (n - 1))));
19
+
20
+ /** Signed distance from p to a box (negative inside). */
21
+ export function sdBox(p, box) {
22
+ const q = apply(T(box.R), sub(p, box.pos));
23
+ const c = [0, box.half[1], 0];
24
+ const d = [Math.abs(q[0] - c[0]) - box.half[0], Math.abs(q[1] - c[1]) - box.half[1], Math.abs(q[2] - c[2]) - box.half[2]];
25
+ const outside = len([Math.max(d[0], 0), Math.max(d[1], 0), Math.max(d[2], 0)]);
26
+ return outside + Math.min(Math.max(d[0], d[1], d[2]), 0);
27
+ }
28
+
29
+ export function segDist(p1, q1, p2, q2) {
30
+ let best = Infinity;
31
+ const d2 = sub(q2, p2), dd = dot(d2, d2);
32
+ for (const a of segPoints(p1, q1)) {
33
+ const u = dd < 1e-12 ? 0 : clamp(dot(sub(a, p2), d2) / dd, 0, 1);
34
+ best = Math.min(best, len(sub(a, add(p2, scale(d2, u)))));
35
+ }
36
+ return best;
37
+ }
38
+
39
+ /** Depth (m) of point p inside the head ellipsoid, negative when outside. */
40
+ export function headDepth(sk, s, p) {
41
+ const c = bonePoint(s, sk.index.Head, sk.landmarks.headCentre), R = s.R[sk.index.Head];
42
+ const q = apply(T(R), sub(p, c)), r = sk.landmarks.headRadii;
43
+ const v = len([q[0] / r[0], q[1] / r[1], q[2] / r[2]]);
44
+ return (1 - v) * Math.min(...r);
45
+ }
46
+
47
+ /** The rig's standing ankle height (m): where foot_* stands a flat foot on the floor (X Bot 0.087, Y Bot 0.105). */
48
+ export const ankleY = sk => Math.round(sk.restWorldPos[sk.index.LeftFoot][1] * 1000) / 1000;
49
+
50
+ /** A point `drop` m straight below bone b's joint at bind, carried by b (the joint itself when 0). */
51
+ const below = (sk, s, b, drop) => (drop ? bonePoint(s, b, apply(T(sk.restWorldRot[b]), [0, -drop, 0])) : s.P[b]);
52
+
53
+ /**
54
+ * Sole points carried by a foot, on the skin's sole (the surface verify_result's floor measure
55
+ * reads): heel 4.5 cm behind and 0.5 cm above the bottom of the ankle at bind; ball under the
56
+ * ToeBase joint at the toe pad; tip at the front edge. The rig's sole landmark says how far the
57
+ * skin lies below the joints (X Bot: on the ball joint; Y Bot: 3.3 cm under it).
58
+ */
59
+ export function solePoints(sk, s, side) {
60
+ const I = sk.index, P = sk.restWorldPos, drop = sk.landmarks.sole ?? {};
61
+ const foot = I[`${side}Foot`], toe = I[`${side}ToeBase`], toeEnd = I[`${side}Toe_End`] ?? toe;
62
+ const heelRest = [P[foot][0], 0.005, P[foot][2] - 0.045];
63
+ const heelLocal = apply(T(sk.restWorldRot[foot]), sub(heelRest, P[foot]));
64
+ return { heel: bonePoint(s, foot, heelLocal), ball: below(sk, s, toe, drop.ball), tip: below(sk, s, toeEnd, drop.tip) };
65
+ }
66
+
67
+ /** Outward unit normal of a box at p (the nearest face from inside, the closest direction from outside). */
68
+ export function boxNormal(p, box) {
69
+ const q = apply(T(box.R), sub(p, box.pos));
70
+ q[1] -= box.half[1];
71
+ const d = q.map((v, i) => Math.abs(v) - box.half[i]);
72
+ let n;
73
+ if (Math.max(...d) < 0) { const i = d.indexOf(Math.max(...d)); n = [0, 0, 0]; n[i] = Math.sign(q[i]) || 1; }
74
+ else n = q.map((v, i) => (d[i] > 0 ? Math.sign(v) * d[i] : 0));
75
+ const l = len(n);
76
+ return apply(box.R, l > 1e-12 ? scale(n, 1 / l) : [0, 1, 0]);
77
+ }
@@ -0,0 +1,191 @@
1
+ // Goals (C8): what the motion must achieve, measured on the compiled frames. A goal names one
2
+ // predicate and the frames it must hold on (at: an absolute frame or [f0, f1]):
3
+ // near: [a, b] refDistance(a, b) <= tol (default 0.05 m)
4
+ // above: [a, b] a.y - b.y >= by (default 0 m)
5
+ // moved: ref an object's point (default its base) is at least by (default 0.05 m) from
6
+ // where the scene put it; any other point ref (prop:, joint:, landmark:, anchor:)
7
+ // gets at least by from where it was at at[0]: one value, its farthest frame
8
+ // still: ref the point stays within tol (default 0.01 m) of the same home moved measures
9
+ // from (an object: where the scene put it; any other point: where it is at at[0])
10
+ // speed: ref the point's speed (central difference, m/s) is at least by (no default)
11
+ // mode "all" (every frame of at; the default, except speed) or "any" (one frame; speed's default).
12
+ // after / before: "<goal name>" cut at to the frames after (before) the first frame the named goal
13
+ // holds, so an event the agent did not place on a frame can still order two goals ("the can moves
14
+ // after the kick"). A named goal that never holds, or holds too late, leaves no frames: the goal
15
+ // fails with value and at null and one GOAL issue (value 0.01) at at[0]; the named goal's own GOAL
16
+ // group carries the measure. Each goal answers one result { name, kind, ok, value, at, need }: for
17
+ // "all" the frame closest to failing (or the worst failure), for "any" the best frame. A failed goal
18
+ // adds GOAL issues (value = the shortfall, limit 0): "all" one per failing frame, so the group's
19
+ // count and first..last show how much of at fails and the search (search.js) sees progress; "any"
20
+ // one, at the best frame. Issue frames index out.frames (out.f0 + i); goals[].at and every frame in
21
+ // a message are absolute.
22
+ import { refPoint, refDistance } from "../refs.js";
23
+ import { partsOf } from "../object-pose.js";
24
+ import { add, apply, len, sub } from "../compiler/math.js";
25
+ import { m3 } from "./fmt.js";
26
+
27
+ /** The goal predicates, one per goal. */
28
+ export const GOAL_KINDS = ["near", "above", "moved", "still", "speed"];
29
+ const DEFAULT_NEED = { near: 0.05, above: 0, moved: 0.05, still: 0.01 };
30
+ /** Predicates met at value <= need (tol); the others are met at value >= need (by). */
31
+ const AT_MOST = new Set(["near", "still"]);
32
+ /** Predicates on one point ref (the others take [ref, ref]). */
33
+ export const ONE_REF = new Set(["moved", "still", "speed"]);
34
+
35
+ /** The predicates goal g names (validation wants exactly one). */
36
+ export const goalKinds = g => GOAL_KINDS.filter(k => g?.[k] !== undefined);
37
+ /** A goal's name: its own, else its place in spec.goals ("goals[1]", which groupKey keeps apart). */
38
+ export const goalName = (g, i) => g.name ?? `goals[${i}]`;
39
+ /** The bound: tol for near and still, by for above, moved and speed. */
40
+ export const goalNeed = (g, kind) => (AT_MOST.has(kind) ? g.tol : g.by) ?? DEFAULT_NEED[kind];
41
+ /** "all" or "any": the goal's own, else any for speed (a speed is reached) and all for the rest. */
42
+ const goalMode = (g, kind) => g.mode ?? (kind === "speed" ? "any" : "all");
43
+
44
+ /** moved and still measure the named point of an object ref (its base when none is named), any other ref as it is. */
45
+ export const movedRef = ref => (!ref.startsWith("object:") || /\.(center|top|base)$/.test(ref) ? ref : `${ref}.base`);
46
+
47
+ /** Where the scene put the point of a moved object ref: the same AABB point refs.js answers, from the scene boxes. */
48
+ function scenePoint(scene, ref) {
49
+ const [, id, which] = /^object:(.+)\.(center|top|base)$/s.exec(movedRef(ref)) ?? [];
50
+ const boxes = partsOf(scene, id).map(p => scene.objects[p]);
51
+ if (!boxes.length) return null;
52
+ const lo = [Infinity, Infinity, Infinity], hi = [-Infinity, -Infinity, -Infinity];
53
+ for (const b of boxes) for (const x of [-1, 1]) for (const y of [0, 2]) for (const z of [-1, 1]) {
54
+ const p = add(b.pos, apply(b.R, [x * b.half[0], y * b.half[1], z * b.half[2]]));
55
+ for (let i = 0; i < 3; i++) { lo[i] = Math.min(lo[i], p[i]); hi[i] = Math.max(hi[i], p[i]); }
56
+ }
57
+ const c = [(lo[0] + hi[0]) / 2, (lo[1] + hi[1]) / 2, (lo[2] + hi[2]) / 2];
58
+ return which === "top" ? [c[0], hi[1], c[2]] : which === "base" ? [c[0], lo[1], c[2]] : c;
59
+ }
60
+
61
+ /** The point ref of a moved or still goal. */
62
+ const homeRef = g => g.moved ?? g.still;
63
+
64
+ /** Where a moved or still goal measures from: an object's point where the scene put it, any other point where it is at `from` (default at[0]). */
65
+ export const movedHome = (out, g, from = Array.isArray(g.at) ? g.at[0] : g.at) => (homeRef(g).startsWith("object:") ? scenePoint(out.ctx.scene, homeRef(g)) : refPoint(out, from, homeRef(g)));
66
+
67
+ /** The speed of point ref at absolute frame f (m/s): central difference, one-sided at the window's edges; null when unknown. */
68
+ function speedAt(out, ref, f) {
69
+ const p = refPoint(out, f, ref), fps = out.fps ?? 24;
70
+ if (!p) return null;
71
+ const a = refPoint(out, f - 1, ref), b = refPoint(out, f + 1, ref);
72
+ if (a && b) return (len(sub(b, a)) * fps) / 2;
73
+ const q = a ?? b;
74
+ return q ? len(sub(p, q)) * fps : null;
75
+ }
76
+
77
+ /** The measured value of goal g at absolute frame f (metres, m/s for speed), or null when a ref is unknown there. */
78
+ function measure(out, g, kind, f, home) {
79
+ if (kind === "near") return refDistance(out, f, g.near[0], g.near[1]);
80
+ if (kind === "above") {
81
+ const a = refPoint(out, f, g.above[0]), b = refPoint(out, f, g.above[1]);
82
+ return a && b ? a[1] - b[1] : null;
83
+ }
84
+ if (kind === "speed") return speedAt(out, g.speed, f);
85
+ const p = home && refPoint(out, f, movedRef(homeRef(g)));
86
+ return p ? len(sub(p, home)) : null;
87
+ }
88
+
89
+ /** How far value v is from meeting the bound (> 0 fails). */
90
+ const shortOf = (kind, v, need) => (AT_MOST.has(kind) ? v - need : need - v);
91
+ const round4 = x => Math.max(1e-4, Math.round(x * 1e4) / 1e4);
92
+
93
+ /** The measured frames of goal g over [a, b]: [{ f, v, short }], and the frame its point is measured from (a point's moved or still). */
94
+ function rowsOf(X, g, kind, a, b) {
95
+ const need = goalNeed(g, kind), point = (kind === "moved" || kind === "still") && !homeRef(g).startsWith("object:");
96
+ // a point is measured from where it is at the window's first frame (outside a windowed trial compile: unknown, so nothing is measured)
97
+ const home = kind === "moved" || kind === "still" ? movedHome(X.out, g, a) : null, rows = [];
98
+ for (let f = Math.max(a, X.f0); f <= Math.min(b, X.last); f++) {
99
+ const v = measure(X.out, g, kind, f, home);
100
+ if (v !== null) rows.push({ f, v, short: shortOf(kind, v, need) });
101
+ }
102
+ return { rows, from: point ? a : undefined };
103
+ }
104
+
105
+ /**
106
+ * The first frame goal j holds (its own window, cut by its own after/before): a frame, null when
107
+ * it never holds there (or sits in an after/before loop), undefined when no frame of it is measured
108
+ * (a windowed trial compile that does not reach it).
109
+ */
110
+ function eventOf(X, j) {
111
+ if (X.events.has(j)) return X.events.get(j);
112
+ if (X.visiting.has(j)) return null;
113
+ X.visiting.add(j);
114
+ const g = X.goals[j], kind = goalKinds(g)[0], w = kind && windowOf(X, j);
115
+ let e;
116
+ if (w && !w.wait) {
117
+ const { rows } = rowsOf(X, g, kind, w.a, w.b);
118
+ e = rows.length ? (rows.find(r => r.short <= 0)?.f ?? null) : undefined;
119
+ } else e = w?.wait ? null : undefined;
120
+ X.visiting.delete(j);
121
+ X.events.set(j, e);
122
+ return e;
123
+ }
124
+
125
+ /** The frames goal i measures: at cut by after and before ({ a, b }), or { wait } when that leaves none, or { skip } when unknown here. */
126
+ function windowOf(X, i) {
127
+ const g = X.goals[i];
128
+ let [a, b] = Array.isArray(g.at) ? g.at : [g.at, g.at];
129
+ for (const side of ["after", "before"]) {
130
+ if (g[side] === undefined) continue;
131
+ const j = X.names.get(g[side]);
132
+ if (j === undefined || j === i) return { wait: `${side} "${g[side]}" names no other goal` };
133
+ const e = eventOf(X, j);
134
+ if (e === undefined) return { skip: true };
135
+ if (e === null) return { wait: `waits for "${g[side]}" (${side}), which never holds` };
136
+ if (side === "after") a = Math.max(a, e + 1); else b = Math.min(b, e - 1);
137
+ if (a > b) return { wait: `no frames left ${side} "${g[side]}" (it holds first at f${e}; at is f${Array.isArray(g.at) ? `${g.at[0]}-f${g.at[1]}` : g.at})` };
138
+ }
139
+ return { a, b };
140
+ }
141
+
142
+ function message(name, kind, row, need, any, from) {
143
+ const where = any ? `f${row.f}, the best of f${any[0]}-f${any[1]}` : `f${row.f}`;
144
+ if (kind === "near") return `${name}: ${m3(row.v)} m at ${where} (needs <= ${need})`;
145
+ if (kind === "above") return `${name}: ${m3(row.v)} m above at ${where} (needs >= ${need})`;
146
+ if (kind === "speed") return `${name}: ${m3(row.v)} m/s at ${where} (needs >= ${need})`;
147
+ if (kind === "still") return `${name}: ${m3(row.v)} m from ${from === undefined ? "its scene pose" : `f${from}`} at ${where} (needs <= ${need})`;
148
+ return `${name}: moved ${m3(row.v)} m${from === undefined ? "" : ` from f${from}`} at ${where} (needs >= ${need})`;
149
+ }
150
+
151
+ /** The frames goal i measures in compile result out (after/before applied): { a, b } | { wait } | { skip } (hints-goal.js). */
152
+ export function goalWindow(out, goals, i) {
153
+ const f0 = out.f0 ?? 0, names = new Map(goals.map((g, j) => [goalName(g, j), j]));
154
+ return windowOf({ out, goals, names, f0, last: f0 + (out.frames?.length ?? 0) - 1, events: new Map(), visiting: new Set() }, i);
155
+ }
156
+
157
+ /**
158
+ * goalChecks(out, goals) -> { issues, results }
159
+ * @param {object} out compileMotion result (refs.js reads it)
160
+ * @param {object[]} goals spec.goals
161
+ * @returns {{ issues: object[], results: { name: string, kind: string, ok: boolean, value: number|null, at: number|null, need: number }[] }}
162
+ */
163
+ export function goalChecks(out, goals) {
164
+ const issues = [], results = [], f0 = out.f0 ?? 0;
165
+ const X = { out, goals, names: new Map(goals.map((g, j) => [goalName(g, j), j])), f0, last: f0 + (out.frames?.length ?? 0) - 1, events: new Map(), visiting: new Set() };
166
+ goals.forEach((g, i) => {
167
+ const kind = goalKinds(g)[0];
168
+ if (!kind) return;
169
+ const name = goalName(g, i), need = goalNeed(g, kind), w = windowOf(X, i), at0 = Array.isArray(g.at) ? g.at[0] : g.at;
170
+ if (w.skip) return;
171
+ if (w.wait) {
172
+ results.push({ name, kind, ok: false, value: null, at: null, need });
173
+ if (at0 >= f0 && at0 <= X.last) issues.push({ kind: "GOAL", msg: `${name}: ${w.wait}`, frame: at0 - f0, value: 0.01, limit: 0 });
174
+ return;
175
+ }
176
+ let { rows, from } = rowsOf(X, g, kind, w.a, w.b);
177
+ // a windowed trial compile (search.js) may not reach at; those frames cannot change there
178
+ if (!rows.length) return;
179
+ // a point's moved is one value: the farthest it gets from where it was at at[0]
180
+ if (kind === "moved" && from !== undefined) rows = [rows.reduce((p, r) => (r.v > p.v ? r : p))];
181
+ const any = goalMode(g, kind) === "any", cut = Array.isArray(g.at) ? w.a !== w.b : false;
182
+ const pick = rows.reduce((p, r) => ((any ? r.short < p.short : r.short > p.short) ? r : p));
183
+ const ok = pick.short <= 0;
184
+ results.push({ name, kind, ok, value: m3(pick.v), at: pick.f, need });
185
+ if (ok) return;
186
+ for (const r of any ? [pick] : rows.filter(r => r.short > 0)) {
187
+ issues.push({ kind: "GOAL", msg: message(name, kind, r, need, any && cut ? [w.a, w.b] : null, from), frame: r.f - f0, value: round4(r.short), limit: 0 });
188
+ }
189
+ });
190
+ return { issues, results };
191
+ }
@@ -0,0 +1,60 @@
1
+ // The ground under a foot: the floor (y 0) or the top of a scene obstacle the sole stands on.
2
+ // Every ground measurement reads sole heights from here: QA's air runs, FLOAT and balance
3
+ // (style.js), SLIDE (checks.js), the timeline's planted runs, and the fixes that keep to QA's
4
+ // planted definition (fix/slide.js, fix/turn.js). A foot on a box top is then on the ground as one
5
+ // on the floor is. Session 68d305dc: a foot on a 0.7 m cube read as "no foot on the ground"
6
+ // (FLOAT n57), and naming the cube in checks.supports did not clear it (a support rests the hips).
7
+ // The obstacles are out.objectsAt(i): carried and follow objects are not ground, a hinge is at its
8
+ // turned pose. GROUND (a sole under the floor) and fix.ground stay on the floor.
9
+ import { T, apply, sub, len } from "../compiler/math.js";
10
+ import { solePoints } from "./geometry.js";
11
+
12
+ /** A top up to this far (m) above a sole point is still the ground under it: a sole sunk into a top stands on it. */
13
+ const SINK = 0.05;
14
+
15
+ const prepared = new WeakMap();
16
+
17
+ /** The obstacle boxes of one frame's map, with what the vertical-line test needs. */
18
+ function boxesOf(objs) {
19
+ let list = prepared.get(objs);
20
+ if (!list) {
21
+ list = Object.values(objs).filter(box => box.half && !box.ignore).map(box => {
22
+ const RT = T(box.R), c = [box.pos[0] + box.R[0][1] * box.half[1], box.pos[1] + box.R[1][1] * box.half[1], box.pos[2] + box.R[2][1] * box.half[1]];
23
+ return { RT, c, half: box.half, up: [box.R[1][0], box.R[1][1], box.R[1][2]], r: len(box.half) };
24
+ });
25
+ prepared.set(objs, list);
26
+ }
27
+ return list;
28
+ }
29
+
30
+ /** Height of the box's top on the vertical line through p (any rotation), or null when the line misses it. */
31
+ function topOver(b, p) {
32
+ if (Math.hypot(p[0] - b.c[0], p[2] - b.c[2]) > b.r) return null;
33
+ const q = apply(b.RT, sub(p, b.c));
34
+ let lo = -Infinity, hi = Infinity;
35
+ for (let k = 0; k < 3; k++) {
36
+ const d = b.up[k], h = b.half[k];
37
+ if (Math.abs(d) < 1e-9) { if (Math.abs(q[k]) > h) return null; continue; }
38
+ const t1 = (-h - q[k]) / d, t2 = (h - q[k]) / d;
39
+ lo = Math.max(lo, Math.min(t1, t2));
40
+ hi = Math.min(hi, Math.max(t1, t2));
41
+ }
42
+ return lo <= hi ? p[1] + hi : null;
43
+ }
44
+
45
+ /** World height of the ground under point p at frame index i of out (out.frames[i]). */
46
+ export function groundY(out, i, p) {
47
+ let g = 0;
48
+ for (const b of boxesOf(out.objectsAt(i))) {
49
+ const top = topOver(b, p);
50
+ if (top !== null && top <= p[1] + SINK && top > g) g = top;
51
+ }
52
+ return g;
53
+ }
54
+
55
+ /** solePoints of frame index i with y the height above the ground under each point (x, z are world). */
56
+ export function groundSole(out, i, side) {
57
+ const sp = solePoints(out.ctx.sk, out.frames[i].s, side);
58
+ for (const key of ["heel", "ball", "tip"]) sp[key] = [sp[key][0], sp[key][1] - groundY(out, i, sp[key]), sp[key][2]];
59
+ return sp;
60
+ }
@@ -0,0 +1,195 @@
1
+ // Hints for contact-type issues: a body part inside a box, the floor, the head, the torso or the
2
+ // other limb. Each answers a world move (direction x amount) for whatever drives that part.
3
+ import { add, sub, scale, norm, dot, len, clamp, apply, ry, T } from "../compiler/math.js";
4
+ import { bonePoint } from "../compiler/skeleton.js";
5
+ import { CAPSULES, segPoints, sdBox, boxNormal, headDepth, segDist, solePoints, ankleY } from "./geometry.js";
6
+ import { vec, m3 } from "./fmt.js";
7
+ import { kneeFoldHint, kneeWhy } from "./hints-knee.js";
8
+ import { objectFrame } from "../compiler/anchors.js";
9
+ import { bodyFor, bodyNormal, limbDepth } from "../body.js";
10
+ import { footProbes, footDepths } from "./foot-checks.js";
11
+ import { personCapsules } from "./people-checks.js";
12
+
13
+ const MARGIN = 0.003;
14
+ /** The most a foot hint lifts a foot over a body it went into through a side (a stair riser); higher, it backs out. */
15
+ const LIFT_MAX = 0.25;
16
+ const sideOf = name => (/(^|_)l$|^Left/.test(name) ? "l" : /(^|_)r$|^Right/.test(name) ? "r" : null);
17
+
18
+ /** fix (qa/patch.js) moving anchor `name` of hand k by a world delta at f: its own point, in the frame that point is written in. */
19
+ function anchorFix(X, name, f, delta, k) {
20
+ const a = X.spec.anchors?.[name], path = field => `/anchors/${name}/${field}`;
21
+ if (a?.kind === "world") return { path: path("p"), delta };
22
+ if (a?.kind === "body") return { path: path("p"), delta: apply(ry(-(X.fr(f).c.yaw ?? 0)), delta) };
23
+ if (["bone", "segment", "prop", "person"].includes(a?.kind)) return { path: path("offset"), delta };
24
+ if (a?.kind !== "object") return undefined;
25
+ // local is in the object's axes where that hand's target read it (anchors.js objectFrame)
26
+ const fr = X.fr(f), o = objectFrame(X.out.ctx, a, name, { ...fr, diag: { ...fr.diag, frame: f } }, k);
27
+ return { path: path("local"), delta: apply(T(o.R), delta) };
28
+ }
29
+
30
+ /** cause + hint (+ fix, qa/patch.js) for moving a body part (capsule name, prop_<name>, hand_<k>) by `delta` at frame f. */
31
+ export function moveHint(X, part, f, delta, why = "") {
32
+ const prop = /^prop_(.+)$/.exec(part);
33
+ const k = prop ? X.spec.props?.[prop[1]]?.hand : sideOf(part);
34
+ let label, cause, fix, shown = delta;
35
+ if (/^(hand|farm|uarm)_/.test(part) || prop) {
36
+ const d = X.hand(k, f);
37
+ label = d.label;
38
+ fix = d.anchor ? anchorFix(X, d.anchor, f, delta, k) : d.ch === `hand_${k}` ? { channel: d.ch, f, delta } : undefined;
39
+ // an object anchor moves its point on the object (local, in the object's axes), never the object
40
+ const on = d.anchor && X.spec.anchors[d.anchor]?.kind === "object" ? X.spec.anchors[d.anchor].object : null;
41
+ if (on && fix) { label = `anchors.${d.anchor}.local (a point on ${on}, in ${on}'s axes; ${on} stays)`; shown = fix.delta; }
42
+ cause = d.anchor ? `anchor ${d.anchor}${d.fit !== null ? ` (fits[${d.fit}])` : ""} via ${X.cause(d.ch, f)}` : X.cause(d.ch, f);
43
+ } else if (/^(shin|foot|sole)_/.test(part)) { label = `foot_${k}`; cause = X.cause(label, f); fix = { channel: label, f, delta }; }
44
+ // a body held since its key (it breathes and sways there, compiler/idle.js): move what placed it (patch.js hold)
45
+ else { label = "root"; cause = X.cause("root", f); fix = { channel: "root", f, delta, ...(/^root held since /.test(cause) ? { hold: true } : {}) }; }
46
+ // a key the walk generator wrote: the agent changes the walk, not the key
47
+ const walk = / (walk|walkTo) /.test(` ${cause}`) && /step|start|end/.test(cause);
48
+ if (walk && !/^anchor/.test(label)) return { cause, hint: `walk path: ${label} needs ${vec(delta)} m at f${f}${why}; change that walk's heading/turnDistance/step (or give one a { range: [lo, hi] })` };
49
+ return { cause, hint: `${label}: move ${vec(shown)} m at f${f}${why}`, fix };
50
+ }
51
+
52
+ const capsule = name => CAPSULES.find(c => c[0] === name);
53
+
54
+ /** Deepest point of a capsule in a box at frame f and the box's outward normal there. */
55
+ function deepest(X, f, part, box) {
56
+ const [, a, b] = capsule(part), { s } = X.fr(f);
57
+ const pts = segPoints(s.P[X.I[a]], s.P[X.I[b]]), ds = pts.map(p => sdBox(p, box)), m = Math.min(...ds);
58
+ return { p: pts[ds.indexOf(m)], n: boxNormal(pts[ds.indexOf(m)], box), d: m };
59
+ }
60
+
61
+ function boxHint(X, g, part, id, need) {
62
+ const f = g.worstFrame, box = X.out.objectsAt(X.rel(f))[id];
63
+ if (!box || !capsule(part)) return null;
64
+ const { n } = deepest(X, f, part, box);
65
+ return moveHint(X, part, f, scale(n, need), ` (out of ${id})`);
66
+ }
67
+
68
+ /**
69
+ * A foot in a box (foot-checks.js): through a side face (a toe swung into a stair's riser) the foot rises over the
70
+ * box's top by what its deepest probe lacks, with the way straight out of that side as the other choice; through
71
+ * the top face (a sole sunk in a step) it comes out along that face.
72
+ */
73
+ function footHint(X, g, part, id) {
74
+ const f = g.worstFrame, box = X.out.objectsAt(X.rel(f))[id];
75
+ if (!box) return null;
76
+ const probes = footProbes(X.sk, X.fr(f).s, part.endsWith("_l") ? "Left" : "Right"), d = footDepths(probes, box), need = g.worst - g.limit + MARGIN;
77
+ if (!(d.side > 0)) return d.topAt ? moveHint(X, part, f, scale(boxNormal(d.topAt, box), need), ` (out of ${id}'s top)`) : null;
78
+ const under = p => 2 * box.half[1] - apply(T(box.R), sub(p, box.pos))[1];
79
+ const rise = Math.max(...probes.filter(p => sdBox(p, box) < 0).map(under)) + MARGIN, out = scale(boxNormal(d.sideAt, box), need);
80
+ // a step's riser is cleared by lifting the swing; a panel the foot kicks or a wall is left the way the foot came
81
+ if (rise > LIFT_MAX) return moveHint(X, part, f, out, ` (out of ${id}'s side)`);
82
+ const r = moveHint(X, part, f, apply(box.R, [0, rise, 0]), ` (over ${id}'s top; its ${g.worst.toFixed(3)} m is through a side)`);
83
+ r.hint += /^walk path/.test(r.hint) ? "; a higher lift raises the swing" : `; or move ${vec(out)} m out of that side`;
84
+ return r;
85
+ }
86
+
87
+ function towardFrom(X, g, part, from, need, what) {
88
+ return moveHint(X, part, g.worstFrame, scale(norm(from), need), ` (away from the ${what})`);
89
+ }
90
+
91
+ /** Probe points of a hand or prop at frame f (as the FACE/BODY checks probe them). */
92
+ function probes(X, f, nm) {
93
+ const { s, diag } = X.fr(f), I = X.I;
94
+ if (nm.startsWith("hand_")) { const S = nm.endsWith("l") ? "Left" : "Right"; return segPoints(s.P[I[`${S}Hand`]], s.P[I[`${S}HandMiddle3`]], 5); }
95
+ return Object.entries(diag.props?.[nm.slice(5)] ?? {}).filter(([k, p]) => Array.isArray(p) && k !== "origin").map(([, p]) => p);
96
+ }
97
+
98
+ export const contactHints = {
99
+ COLLIDE(X, g) {
100
+ const [, part, id] = /^(\S+) x (.+)$/.exec(g.subject) ?? [];
101
+ if (/^foot_[lr]$/.test(part ?? "")) return footHint(X, g, part, id);
102
+ const r = boxHint(X, g, part, id, g.worst - g.limit + MARGIN);
103
+ if (r && /^(hand|farm)_/.test(part) && !X.spec.fix?.penetration && g.worst - g.limit <= 0.05) {
104
+ r.hint += "; or fix.penetration: 0.02 pushes shallow hand hits out";
105
+ if (X.spec.fix !== false) r.fix = { path: "/fix/penetration", value: 0.02 };
106
+ }
107
+ return r;
108
+ },
109
+ /** PERSON "<part> x person:<id>": the part moves out of the other person's deepest capsule, along the line between their nearest points. */
110
+ PERSON(X, g) {
111
+ const [, part, id] = /^(\S+) x person:(.+)$/.exec(g.subject) ?? [], mine = capsule(part), p0 = X.out?.ctx?.scene?.people?.[id];
112
+ if (!mine || !p0) return null;
113
+ const f = g.worstFrame, { s } = X.fr(f), pts = segPoints(s.P[X.I[mine[1]]], s.P[X.I[mine[2]]]);
114
+ const near = ([other, C, D, q]) => {
115
+ const cd = sub(D, C), dd = Math.max(dot(cd, cd), 1e-12);
116
+ return pts.map(p => { const at = add(C, scale(cd, clamp(dot(sub(p, C), cd) / dd, 0, 1))); return { other, p, q: at, depth: mine[3] + q - len(sub(p, at)) }; })
117
+ .reduce((m, x) => (x.depth > m.depth ? x : m));
118
+ };
119
+ const hit = personCapsules(p0, f).map(near).reduce((m, x) => (x.depth > m.depth ? x : m));
120
+ const away = len(sub(hit.p, hit.q)) > 1e-6 ? norm(sub(hit.p, hit.q)) : norm([hit.p[0] - hit.q[0] || 1, 0, hit.p[2] - hit.q[2]]);
121
+ const r = moveHint(X, part, f, scale(away, g.worst - g.limit + MARGIN), ` (out of ${p0.name}'s ${hit.other})`);
122
+ return { ...r, hint: `${r.hint}; or, for a touch you mean, checks.allow [{ part: "${part}", object: "person:${id}", depth: ${m3(g.worst + MARGIN)} }]` };
123
+ },
124
+ CLEARANCE(X, g) {
125
+ const k = (X.spec.checks?.clearance ?? []).find(c => g.subject.startsWith(`${c.name}:`));
126
+ return k && boxHint(X, g, k.part, k.object, k.min - g.worst + MARGIN);
127
+ },
128
+ GROUND(X, g) {
129
+ const k = g.subject.startsWith("Left") ? "l" : "r", f = g.worstFrame, c = X.fr(f).c, fold = X.fr(f).diag[`leg_fold_${k}`];
130
+ // the knee on its stop left the ankle under its target (compiler/limbs.js): raising the foot folds it further, the root comes up
131
+ if (fold) return kneeFoldHint(X, k, f, kneeWhy("the sole is in the floor because the knee is on its stop: ", fold), c[`foot_${k}`]);
132
+ const pitch = c[`fpitch_${k}`] ? `, or less fpitch_${k} (now ${m3(c[`fpitch_${k}`])} deg; + lifts the heel)` : "";
133
+ return { cause: X.cause(`foot_${k}`, f), hint: `foot_${k}: raise ${m3(g.worst + 0.001)} m at f${f} (foot_* is the ankle; it stands ${ankleY(X.sk)} m above the floor)${pitch}`,
134
+ fix: { channel: `foot_${k}`, f, delta: [0, g.worst + 0.001, 0] } };
135
+ },
136
+ FACE(X, g) {
137
+ const nm = g.subject.replace(/ into head$/, ""), f = g.worstFrame, { s } = X.fr(f);
138
+ const c = bonePoint(s, X.I.Head, X.sk.landmarks.headCentre);
139
+ const p = probes(X, f, nm).reduce((best, q) => (headDepth(X.sk, s, q) > headDepth(X.sk, s, best) ? q : best));
140
+ return towardFrom(X, g, nm, sub(p, c), g.worst - g.limit + MARGIN, "head");
141
+ },
142
+ BODY(X, g) {
143
+ const [, nm, part] = /^(\S+) into (\S+)$/.exec(g.subject) ?? [];
144
+ const cap = capsule(part), f = g.worstFrame, { s } = X.fr(f);
145
+ // an arm on the fitted skin (body-checks.js): the hand target moves out along the skin where it goes deepest;
146
+ // an upper arm or forearm says so, since turning its elbow (elbow_<k>) away from the body may do instead
147
+ const limb = /^(hand|farm|uarm)_([lr])$/.exec(nm ?? ""), body = limb && bodyFor(X.sk);
148
+ if (body) {
149
+ const bone = X.I[`${limb[2] === "l" ? "Left" : "Right"}${{ hand: "Hand", farm: "ForeArm", uarm: "Arm" }[limb[1]]}`], hit = limbDepth(body, X.sk, s, bone);
150
+ const out = hit.p && bodyNormal(body, s, hit.p, body.skip.get(bone));
151
+ if (out) {
152
+ const r = moveHint(X, nm, f, scale(out, g.worst - g.limit + MARGIN), ` (out of the ${part})`);
153
+ if (limb[1] !== "hand") r.hint += `; or turn elbow_${limb[2]} away from the ${part}`;
154
+ return r;
155
+ }
156
+ }
157
+ if (!cap) return null;
158
+ const A = s.P[X.I[cap[1]]], B = s.P[X.I[cap[2]]], ab = sub(B, A);
159
+ const axis = p => add(A, scale(ab, clamp(dot(sub(p, A), ab) / dot(ab, ab), 0, 1)));
160
+ const p = probes(X, f, nm).reduce((best, q) => (len(sub(q, axis(q))) < len(sub(best, axis(best))) ? q : best));
161
+ return towardFrom(X, g, nm, sub(p, axis(p)), g.worst - g.limit + MARGIN, "torso");
162
+ },
163
+ SELF(X, g) {
164
+ const f = g.worstFrame, { s } = X.fr(f), P = s.P, I = X.I;
165
+ if (g.subject === "hands overlap") {
166
+ const d = norm(sub(P[I.LeftHand], P[I.RightHand])), need = (g.worst - g.limit + MARGIN) / 2;
167
+ const r = moveHint(X, "hand_l", f, scale(d, need));
168
+ return { cause: r.cause, hint: `${r.hint}; and ${moveHint(X, "hand_r", f, scale(d, -need)).hint}`, fix: moveHint(X, "hand_l", f, scale(d, 2 * need)).fix };
169
+ }
170
+ const shins = g.subject === "shins touch", min = shins ? 0.1 : 0.135;
171
+ const [a, b] = shins ? ["Leg", "Foot"] : ["UpLeg", "Leg"];
172
+ const gap = segDist(P[I[`Left${a}`]], P[I[`Left${b}`]], P[I[`Right${a}`]], P[I[`Right${b}`]]);
173
+ const d = sub(P[I[`Left${b}`]], P[I[`Right${b}`]]); d[1] = 0;
174
+ const need = (min - gap + MARGIN) / 2, dir = norm(d);
175
+ return { cause: X.cause("foot_l", f), hint: `foot_l: move ${vec(scale(dir, need))} m and foot_r ${vec(scale(dir, -need))} m at f${f} (${shins ? "shins" : "thighs"} ${m3(gap)} m apart, need ${min}); or spread knee_l/knee_r`,
176
+ fix: { channel: "foot_l", f, delta: scale(dir, 2 * need) } };
177
+ },
178
+ OBJECT(X, g) {
179
+ const [, id, oid] = /^(\S+) into (.+)$/.exec(g.subject) ?? [];
180
+ const ch = X.spec.objects?.[id]?.channel;
181
+ if (!ch) return null;
182
+ const at = f => X.out.baked[ch]?.[f] ?? 0, clean = g.first > 0 ? at(g.first - 1) : at(0);
183
+ return { cause: X.cause(ch, g.worstFrame), hint: `${ch}: keep it within ${m3(clean)} deg (it reaches ${m3(at(g.worstFrame))} at f${g.worstFrame}, ${m3(g.worst)} m into ${oid}), or move ${oid}`,
184
+ fix: { channel: ch, f: g.worstFrame, value: clean } };
185
+ },
186
+ BALANCE(X, g) {
187
+ const f = g.worstFrame, { s } = X.fr(f), P = s.P, I = X.I;
188
+ if (g.subject === "no foot on the ground") return { cause: X.cause("root", f), hint: `at f${f} lower foot_l or foot_r onto the ground, the floor or the top of an object under it (the ankle stands ${ankleY(X.sk)} m above it), or declare checks.supports for the seat the hips rest on` };
189
+ const com = scale(["Hips", "Spine1", "Spine2", "Head"].reduce((acc, j) => add(acc, P[I[j]]), [0, 0, 0]), 0.25);
190
+ const feet = ["Left", "Right"].flatMap(S => Object.values(solePoints(X.sk, s, S)));
191
+ const xs = feet.map(p => p[0]), zs = feet.map(p => p[2]), mg = 0.08 - 0.01;
192
+ const to = [clamp(com[0], Math.min(...xs) - mg, Math.max(...xs) + mg), com[1], clamp(com[2], Math.min(...zs) - mg, Math.max(...zs) + mg)];
193
+ return { cause: X.cause("root", f), hint: `root: move ${vec(sub(to, com))} m at f${f} (centre of mass over the feet), or step a foot under it`, fix: { channel: "root", f, delta: sub(to, com) } };
194
+ },
195
+ };