cozyclay 2.1.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (577) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +12 -30
  3. package/bin/agent/agent-routes.mjs +40 -113
  4. package/bin/agent/agent-runner.mjs +9 -10
  5. package/bin/agent/cliproxy-provider.mjs +135 -0
  6. package/bin/agent/credential-store.mjs +30 -7
  7. package/bin/agent/pi-tools.mjs +4 -1
  8. package/bin/agent/provider-keys.mjs +32 -0
  9. package/bin/agent/provider-login.mjs +110 -0
  10. package/bin/agent/provider-routes.mjs +99 -0
  11. package/bin/agent/providers.mjs +19 -97
  12. package/bin/agent/script-tool.mjs +17 -4
  13. package/bin/agent/studio-motion-reference.mjs +23 -0
  14. package/bin/agent/studio-prompt.mjs +1 -1
  15. package/bin/agent/studio-rebase-text.mjs +32 -0
  16. package/bin/agent/studio-rebase.mjs +26 -39
  17. package/bin/agent/studio-tools.mjs +9 -28
  18. package/bin/agent/tool-arguments.mjs +49 -0
  19. package/bin/agent/turn-frames.mjs +66 -0
  20. package/bin/codex-auth.mjs +9 -2
  21. package/bin/cozyclay.mjs +10 -116
  22. package/bin/live/cli.mjs +3 -3
  23. package/bin/mcp-runtime.mjs +2 -12
  24. package/bin/package-signature.mjs +0 -4
  25. package/dist/app/index.html +7 -6
  26. package/dist/assets/EXRLoader-CvVIvGVE.js +1 -0
  27. package/dist/assets/analytics-C9sUnndd.js +1 -0
  28. package/dist/assets/app-CCh--1Hc.js +5087 -0
  29. package/dist/assets/app-f-fkV5uP.css +1 -0
  30. package/dist/assets/compile-worker-CkxiD6lH.js +5 -0
  31. package/dist/assets/fflate.module-KbGj5-H6.js +5 -0
  32. package/dist/assets/{first-shot-handoff-msLI71g1.js → first-shot-handoff-CC4xe4HM.js} +1 -1
  33. package/dist/assets/{landing-ChUvB_zL.js → landing-DjHD8u7k.js} +1 -1
  34. package/dist/assets/scene-asset-sync-Hx_FzdOH.js +20 -0
  35. package/dist/assets/scene-asset-sync-_S8NQl5B.css +1 -0
  36. package/dist/assets/workflow-C4yKH1p4.js +179 -0
  37. package/dist/cozyclay-package.json +1 -1
  38. package/dist/index.html +3 -3
  39. package/dist/sw.js +3 -6
  40. package/dist/workflow/index.html +5 -4
  41. package/mcp/LIVE-PROTOCOL.md +7 -47
  42. package/mcp/README.md +16 -26
  43. package/mcp/live-hub.mjs +6 -10
  44. package/mcp/package.json +1 -3
  45. package/mcp/tool-handlers.mjs +4 -168
  46. package/package.json +3 -22
  47. package/src/App.jsx +150 -2624
  48. package/src/agent-progress.js +79 -0
  49. package/src/agent-turn-mark.js +30 -0
  50. package/src/analytics.js +1 -19
  51. package/src/app-context.js +5 -0
  52. package/src/app-stage.jsx +2 -34
  53. package/src/asset-pane.jsx +6 -5
  54. package/src/camera-block.js +21 -0
  55. package/src/camera-direction.js +120 -0
  56. package/src/character-kind.js +3 -9
  57. package/src/command-bus.js +8 -6
  58. package/src/commands/cast.js +5 -34
  59. package/src/commands/elements/character.js +1 -1
  60. package/src/commands/elements/motion.js +1 -6
  61. package/src/commands/elements/object.js +6 -2
  62. package/src/commands/elements/shot.js +4 -1
  63. package/src/commands/elements.js +38 -14
  64. package/src/commands/index.js +3 -1
  65. package/src/commands/kits.js +37 -0
  66. package/src/commands/motion-timing.js +57 -0
  67. package/src/commands/motion.js +9 -152
  68. package/src/commands/motion3-compile.js +65 -0
  69. package/src/commands/motion3.js +207 -0
  70. package/src/commands/objects.js +6 -4
  71. package/src/commands/shot.js +49 -11
  72. package/src/commands/stage.js +14 -1
  73. package/src/commands/view.js +4 -1
  74. package/src/document-mirrors.js +42 -0
  75. package/src/document-store.js +4 -0
  76. package/src/domains/cast.js +19 -194
  77. package/src/domains/motion/motion-range-pin-rules.js +1 -1
  78. package/src/domains/motion/take-source.js +51 -0
  79. package/src/domains/motion.js +60 -1838
  80. package/src/domains/scenes.js +12 -55
  81. package/src/domains/shots.js +2 -2
  82. package/src/domains/stage.js +4 -2
  83. package/src/draft-ghost.js +46 -0
  84. package/src/draft-ghost.jsx +129 -0
  85. package/src/dualview.jsx +4 -3
  86. package/src/effects/EffectsLayer.jsx +112 -0
  87. package/src/effects/effect-meshes.js +232 -0
  88. package/src/effects/effect-record.js +103 -0
  89. package/src/effects/emitter.js +59 -0
  90. package/src/effects/simulate.js +261 -0
  91. package/src/effects/state.js +59 -0
  92. package/src/execution-telemetry.js +2 -3
  93. package/src/first-success-guide.jsx +5 -5
  94. package/src/ghost-set.js +257 -0
  95. package/src/held-object-edit.jsx +113 -0
  96. package/src/held-object-pose.js +175 -0
  97. package/src/hierarchy-model.js +1 -1
  98. package/src/hierarchy-panel.jsx +1 -1
  99. package/src/imported-mesh.jsx +103 -0
  100. package/src/kits/held.js +84 -0
  101. package/src/kits/index.js +70 -0
  102. package/src/kits/smoking.js +57 -0
  103. package/src/light-blockers.js +93 -0
  104. package/src/motion/beat-board-model.js +84 -0
  105. package/src/motion/beat-board.css +124 -0
  106. package/src/motion/beat-board.jsx +59 -0
  107. package/src/motion/center-of-mass.js +66 -0
  108. package/src/motion/fix-collisions.js +850 -0
  109. package/src/motion/physics-review.js +118 -0
  110. package/src/{ardy → motion}/physics-support.js +1 -76
  111. package/src/motion/rig-snapshot.js +22 -0
  112. package/src/motion/take-segments.jsx +140 -0
  113. package/src/{ardy → motion}/timeline-coordinates.js +0 -21
  114. package/src/{ardy → motion}/timeline.css +1 -2
  115. package/src/{ardy → motion}/timeline.jsx +42 -413
  116. package/src/motion/waypoints.js +76 -0
  117. package/src/motion3/beats.js +83 -0
  118. package/src/motion3/body.js +158 -0
  119. package/src/motion3/bones.js +47 -0
  120. package/src/motion3/carried.js +47 -0
  121. package/src/motion3/compile-job.js +75 -0
  122. package/src/motion3/compile-worker.js +10 -0
  123. package/src/motion3/compiler/anchors.js +105 -0
  124. package/src/motion3/compiler/arm-orient.js +128 -0
  125. package/src/motion3/compiler/channels.js +125 -0
  126. package/src/motion3/compiler/compile.js +205 -0
  127. package/src/motion3/compiler/gait-rhythm.js +97 -0
  128. package/src/motion3/compiler/grip.js +171 -0
  129. package/src/motion3/compiler/head-steady.js +48 -0
  130. package/src/motion3/compiler/hold.js +134 -0
  131. package/src/motion3/compiler/idle.js +113 -0
  132. package/src/motion3/compiler/limbs.js +170 -0
  133. package/src/motion3/compiler/math.js +72 -0
  134. package/src/motion3/compiler/pose.js +162 -0
  135. package/src/motion3/compiler/quat.js +49 -0
  136. package/src/motion3/compiler/skeleton.js +74 -0
  137. package/src/motion3/compiler/validate-common.js +32 -0
  138. package/src/motion3/compiler/validate-goals.js +118 -0
  139. package/src/motion3/compiler/validate-objects.js +143 -0
  140. package/src/motion3/compiler/validate-parts.js +116 -0
  141. package/src/motion3/compiler/validate.js +122 -0
  142. package/src/motion3/compiler/vocab.js +73 -0
  143. package/src/motion3/compiler/walk-pelvis.js +275 -0
  144. package/src/motion3/compiler/walk.js +207 -0
  145. package/src/motion3/compiler/wrist-clamp.js +32 -0
  146. package/src/motion3/delta.js +47 -0
  147. package/src/motion3/describe.js +174 -0
  148. package/src/motion3/docs.js +27 -0
  149. package/src/motion3/edit-carry.js +21 -0
  150. package/src/motion3/fix/ease.js +16 -0
  151. package/src/motion3/fix/ground.js +37 -0
  152. package/src/motion3/fix/index.js +52 -0
  153. package/src/motion3/fix/push.js +71 -0
  154. package/src/motion3/fix/reach.js +46 -0
  155. package/src/motion3/fix/retime.js +79 -0
  156. package/src/motion3/fix/self.js +211 -0
  157. package/src/motion3/fix/slide.js +70 -0
  158. package/src/motion3/fix/turn.js +51 -0
  159. package/src/motion3/follow.js +39 -0
  160. package/src/motion3/free-motion.js +173 -0
  161. package/src/motion3/grip-choice.js +130 -0
  162. package/src/motion3/hold-plan.js +157 -0
  163. package/src/motion3/json-patch.js +77 -0
  164. package/src/motion3/key-aim.js +105 -0
  165. package/src/motion3/key-edit.js +82 -0
  166. package/src/motion3/lenient.js +127 -0
  167. package/src/motion3/object-keys.js +236 -0
  168. package/src/motion3/object-pose.js +182 -0
  169. package/src/motion3/object-tracks.js +12 -0
  170. package/src/motion3/pipeline.js +102 -0
  171. package/src/motion3/playback.js +51 -0
  172. package/src/motion3/probes.js +91 -0
  173. package/src/motion3/qa/body-checks.js +34 -0
  174. package/src/motion3/qa/check-names.js +14 -0
  175. package/src/motion3/qa/checks.js +121 -0
  176. package/src/motion3/qa/contact-checks.js +51 -0
  177. package/src/motion3/qa/explain.js +80 -0
  178. package/src/motion3/qa/fmt.js +5 -0
  179. package/src/motion3/qa/foot-checks.js +72 -0
  180. package/src/motion3/qa/geometry.js +77 -0
  181. package/src/motion3/qa/goals.js +191 -0
  182. package/src/motion3/qa/ground.js +60 -0
  183. package/src/motion3/qa/hints-contact.js +179 -0
  184. package/src/motion3/qa/hints-goal.js +160 -0
  185. package/src/motion3/qa/hints-knee.js +24 -0
  186. package/src/motion3/qa/hints-motion.js +209 -0
  187. package/src/motion3/qa/hints-object.js +91 -0
  188. package/src/motion3/qa/index.js +42 -0
  189. package/src/motion3/qa/key-checks.js +82 -0
  190. package/src/motion3/qa/near.js +96 -0
  191. package/src/motion3/qa/object-checks.js +135 -0
  192. package/src/motion3/qa/patch.js +161 -0
  193. package/src/motion3/qa/severity.js +38 -0
  194. package/src/motion3/qa/spec-checks.js +65 -0
  195. package/src/motion3/qa/style.js +218 -0
  196. package/src/motion3/qa/wrist-checks.js +69 -0
  197. package/src/motion3/qa/wrist-obs.js +60 -0
  198. package/src/motion3/refs.js +120 -0
  199. package/src/motion3/repair-tune.js +121 -0
  200. package/src/motion3/repair.js +83 -0
  201. package/src/motion3/report.js +67 -0
  202. package/src/motion3/rigs/index.js +14 -0
  203. package/src/motion3/rigs/x-bot-tpose-body.json +1 -0
  204. package/src/motion3/rigs/x-bot-tpose.json +1 -0
  205. package/src/motion3/rigs/y-bot-tpose-body.json +1 -0
  206. package/src/motion3/rigs/y-bot-tpose.json +1 -0
  207. package/src/motion3/scene.js +60 -0
  208. package/src/motion3/schema/brief.js +89 -0
  209. package/src/motion3/schema/fields.js +44 -0
  210. package/src/motion3/schema/spec-examples.js +46 -0
  211. package/src/motion3/schema/topics.js +111 -0
  212. package/src/motion3/schema/types.js +147 -0
  213. package/src/motion3/search.js +117 -0
  214. package/src/motion3/second-hand.js +112 -0
  215. package/src/motion3/skills/basic.js +82 -0
  216. package/src/motion3/skills/book.js +76 -0
  217. package/src/motion3/skills/car.js +145 -0
  218. package/src/motion3/skills/frames-auto.js +29 -0
  219. package/src/motion3/skills/index.js +83 -0
  220. package/src/motion3/skills/info.js +65 -0
  221. package/src/motion3/skills/smoke-kit.js +109 -0
  222. package/src/motion3/skills/smoke.js +160 -0
  223. package/src/motion3/spec-store.js +59 -0
  224. package/src/motion3/take-edit.js +155 -0
  225. package/src/motion3/take.js +63 -0
  226. package/src/motion3/timeline.js +127 -0
  227. package/src/motion3/views.js +121 -0
  228. package/src/motion3/walk-plan.js +119 -0
  229. package/src/object-catalog.jsx +7 -1
  230. package/src/object-colors.js +98 -0
  231. package/src/object-gizmo-parts.js +115 -0
  232. package/src/object-gizmo.jsx +37 -122
  233. package/src/object-glow.js +139 -0
  234. package/src/object-hidden.js +33 -0
  235. package/src/object-look.js +196 -0
  236. package/src/object-size.js +10 -0
  237. package/src/object-travel.js +71 -6
  238. package/src/panels/CameraPanel.jsx +7 -7
  239. package/src/panels/HdrEnvironmentControls.jsx +59 -0
  240. package/src/panels/LightPanel.jsx +25 -4
  241. package/src/panels/ObjectLookPanel.jsx +88 -0
  242. package/src/panels/PosePanel.jsx +2 -17
  243. package/src/panels/RigControlPanel.jsx +5 -53
  244. package/src/panels/SubjectBox.jsx +4 -1
  245. package/src/panels/details.css +14 -0
  246. package/src/posestudio.jsx +4 -27
  247. package/src/project-browser.jsx +3 -4
  248. package/src/project-resources.js +18 -31
  249. package/src/project.js +13 -104
  250. package/src/props.jsx +25 -280
  251. package/src/rail-dot-drag.js +23 -0
  252. package/src/render-passes.js +10 -0
  253. package/src/resource-status.jsx +1 -0
  254. package/src/result-modal.jsx +4 -19
  255. package/src/room.jsx +20 -12
  256. package/src/scene-assets.js +8 -6
  257. package/src/scene-light-place.js +72 -0
  258. package/src/scene-lights.jsx +148 -0
  259. package/src/scene-objects.js +31 -139
  260. package/src/scenes.js +15 -105
  261. package/src/semantic-edit.js +2 -5
  262. package/src/set-pieces.jsx +183 -0
  263. package/src/settings-menu.jsx +3 -11
  264. package/src/shell/BottomDock.jsx +9 -51
  265. package/src/shell/DetailsSlot.jsx +16 -200
  266. package/src/shell/GhostsMenuGroup.jsx +74 -0
  267. package/src/shell/LibrarySlot.jsx +1 -3
  268. package/src/shell/MenuBar.jsx +3 -3
  269. package/src/shell/PreferencesDialog.jsx +15 -264
  270. package/src/shell/PreferencesSlot.jsx +2 -8
  271. package/src/shell/TopBar.jsx +5 -84
  272. package/src/shell/ViewportToolbar.jsx +10 -70
  273. package/src/shell/agent-glass.css +19 -6
  274. package/src/shell/details-selection.js +42 -0
  275. package/src/shell/ghosts-menu.css +22 -0
  276. package/src/shell/glass-regions.css +29 -3
  277. package/src/shell/preferences.css +6 -1
  278. package/src/shot-authoring.js +4 -7
  279. package/src/soft-shadows.js +37 -0
  280. package/src/stage-attach-frames.js +12 -12
  281. package/src/stage-canvas-rigs.jsx +3 -3
  282. package/src/stage-capture.jsx +42 -8
  283. package/src/stage-characters.jsx +22 -216
  284. package/src/stage-editor-gizmos.jsx +6 -6
  285. package/src/stage-environment.js +93 -0
  286. package/src/stage-hdr-builtins.js +107 -0
  287. package/src/stage-hdr-image.js +116 -0
  288. package/src/stage-hdr-library.js +70 -0
  289. package/src/stage-hdr-map.js +94 -0
  290. package/src/stage-hdr.js +130 -0
  291. package/src/stage-lighting.js +149 -0
  292. package/src/stage-occlusion.js +210 -0
  293. package/src/stage-sky.jsx +159 -0
  294. package/src/stage-startup-defaults.js +5 -9
  295. package/src/studio-actions.js +12 -14
  296. package/src/studio-agent-commands.js +19 -16
  297. package/src/studio-agent-motion.js +9 -9
  298. package/src/studio-agent-protocol.js +41 -71
  299. package/src/studio-app-binding.js +25 -45
  300. package/src/studio-asset-list.js +17 -0
  301. package/src/studio-contact-sheet.js +37 -5
  302. package/src/studio-elements.js +27 -7
  303. package/src/studio-entity-bounds.js +18 -0
  304. package/src/studio-error-codes.js +8 -0
  305. package/src/studio-export-framing.js +152 -0
  306. package/src/studio-lighting-check.js +103 -0
  307. package/src/studio-motion-visual.js +150 -0
  308. package/src/studio-schema-errors.js +80 -0
  309. package/src/studio-support-gap.js +74 -0
  310. package/src/styles.css +1 -19
  311. package/src/timeline-defaults.js +5 -0
  312. package/src/timeline-extent.js +8 -20
  313. package/src/tutorial-walk.js +17 -0
  314. package/src/ui-scrub.js +1 -1
  315. package/src/use-shot-camera-tracks.js +12 -0
  316. package/src/view-ghosts.js +97 -0
  317. package/src/view-ghosts.jsx +49 -0
  318. package/src/workflow/AgentPanel.jsx +47 -166
  319. package/src/workflow/AgentProgressCard.jsx +107 -0
  320. package/src/workflow/AgentReplyText.jsx +37 -0
  321. package/src/workflow/AgentTurnSteps.jsx +84 -0
  322. package/src/workflow/LoginPanel.jsx +140 -0
  323. package/src/workflow/WorkflowBuilder.jsx +7 -23
  324. package/src/workflow/agent-client.js +54 -51
  325. package/src/workflow/agent-panel.css +71 -1
  326. package/src/workflow/agent-progress-card.css +167 -0
  327. package/src/workflow/agent-progress-view.js +47 -0
  328. package/src/workflow/agent-turn-steps.css +113 -0
  329. package/src/workflow/agent-turn.js +191 -0
  330. package/src/workflow/login-client.js +34 -0
  331. package/src/workflow/motion-input.js +18 -31
  332. package/src/workflow/node-schema.js +1 -1
  333. package/src/workflow/reply-markdown.js +69 -0
  334. package/src/workflow/scene-asset-sync.js +8 -8
  335. package/tools/agent-session-stats.mjs +61 -0
  336. package/tools/dev-full.mjs +5 -56
  337. package/tools/eslint-baseline.json +12 -13
  338. package/tools/knip-baseline.json +0 -179
  339. package/tools/loc-baseline.json +31 -81
  340. package/tools/motion3/extract-body.mjs +103 -0
  341. package/tools/motion3/extract-rig.mjs +85 -0
  342. package/tools/process-supervisor.mjs +0 -88
  343. package/tools/run-tests.mjs +8 -124
  344. package/bin/agent/motion-runtime.mjs +0 -76
  345. package/dist/ardy/cskel27-rest.json +0 -557
  346. package/dist/assets/analytics-C9pMpW7s.js +0 -1
  347. package/dist/assets/app-B_2XB9rw.css +0 -1
  348. package/dist/assets/app-DAuvKPP8.js +0 -4920
  349. package/dist/assets/config-Bx7pYsD9.js +0 -1
  350. package/dist/assets/config-DeoFTYzb.css +0 -1
  351. package/dist/assets/demo-CvKuBgy5.js +0 -1
  352. package/dist/assets/scene-asset-sync-DKkpjKBV.js +0 -19
  353. package/dist/assets/scene-asset-sync-DjJ_cJ2R.css +0 -1
  354. package/dist/assets/ticket-BPk2Ni95.js +0 -1
  355. package/dist/assets/workflow-jJRBR94Z.js +0 -179
  356. package/dist/demo/walk-then-stop.npz +0 -0
  357. package/mcp/ardy-prompts.mjs +0 -218
  358. package/src/ardy/auto-fix-panel.css +0 -108
  359. package/src/ardy/auto-physics.js +0 -1194
  360. package/src/ardy/client.js +0 -153
  361. package/src/ardy/convert.js +0 -313
  362. package/src/ardy/cskel27-neutral.js +0 -39
  363. package/src/ardy/cskel27.js +0 -77
  364. package/src/ardy/export.js +0 -165
  365. package/src/ardy/fix-collisions.js +0 -2257
  366. package/src/ardy/foot-lock.js +0 -230
  367. package/src/ardy/motion-calibration.js +0 -118
  368. package/src/ardy/motion-edit.js +0 -213
  369. package/src/ardy/motion-url.js +0 -55
  370. package/src/ardy/mp4-duration.js +0 -102
  371. package/src/ardy/npz.js +0 -595
  372. package/src/ardy/physics-panel.css +0 -21
  373. package/src/ardy/physics-panel.jsx +0 -89
  374. package/src/ardy/physics-refine.js +0 -111
  375. package/src/ardy/physics-review.js +0 -567
  376. package/src/ardy/physics-temporal.js +0 -54
  377. package/src/ardy/platform-fit-panel.css +0 -64
  378. package/src/ardy/platform-fit-panel.jsx +0 -41
  379. package/src/ardy/platform-fit.js +0 -326
  380. package/src/ardy/playback.js +0 -711
  381. package/src/ardy/pose-pin.js +0 -97
  382. package/src/ardy/prompt-clips.js +0 -30
  383. package/src/ardy/retime.js +0 -211
  384. package/src/ardy/root-drop.js +0 -369
  385. package/src/ardy/timeline-resize.js +0 -13
  386. package/src/ardy/to-cskel27.js +0 -225
  387. package/src/ardy/trim.js +0 -33
  388. package/src/ardy/waypoints.js +0 -369
  389. package/src/ardy-bridge-contract.js +0 -281
  390. package/src/domains/motion/motion-video-capture.js +0 -664
  391. package/src/i2v-motion-client.js +0 -77
  392. package/src/i2v-motion-studio.jsx +0 -180
  393. package/src/line-edit.js +0 -1348
  394. package/src/motion/generation.js +0 -105
  395. package/src/motion-readiness-ui.jsx +0 -67
  396. package/src/motion-readiness.js +0 -40
  397. package/src/motion-resources.js +0 -177
  398. package/src/motion-store.js +0 -65
  399. package/src/motion-trail.js +0 -601
  400. package/src/multimodel-ingest.js +0 -394
  401. package/src/panels/PromptBlocksPanel.jsx +0 -444
  402. package/src/panels/VideoCapturePanel.jsx +0 -181
  403. package/src/pose-extract/browser.js +0 -42
  404. package/src/pose-extract/detector.js +0 -100
  405. package/src/pose-extract/fit.js +0 -227
  406. package/src/pose-extract/image-frame.js +0 -51
  407. package/src/pose-extract/index.js +0 -58
  408. package/src/pose-extract/keyframes.js +0 -74
  409. package/src/pose-extract/landmarks.js +0 -162
  410. package/src/pose-extract/mirror.js +0 -153
  411. package/src/pose-extract/take.js +0 -131
  412. package/src/pose-extract/video-frames.js +0 -91
  413. package/src/take-recipe.js +0 -165
  414. package/src/trail-key-conflicts.js +0 -39
  415. package/src/trail-pick.js +0 -59
  416. package/src/use-i2v-capture-modal.js +0 -15
  417. package/tools/ardy/BRIDGE.md +0 -37
  418. package/tools/ardy/GVHMR-NPZ.md +0 -68
  419. package/tools/ardy/README.md +0 -124
  420. package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
  421. package/tools/ardy/artifacts.mjs +0 -34
  422. package/tools/ardy/bridge.mjs +0 -1732
  423. package/tools/ardy/bvh-cskel27.mjs +0 -1265
  424. package/tools/ardy/cclay_constrained_generate.py +0 -561
  425. package/tools/ardy/cclay_gvhmr_worker.py +0 -264
  426. package/tools/ardy/cclay_motion_edit.py +0 -510
  427. package/tools/ardy/cclay_sequence_generate.py +0 -705
  428. package/tools/ardy/cclay_worker.py +0 -304
  429. package/tools/ardy/dump-npz.py +0 -205
  430. package/tools/ardy/extract-rest.mjs +0 -299
  431. package/tools/ardy/extract.mjs +0 -507
  432. package/tools/ardy/footage.mjs +0 -467
  433. package/tools/ardy/gvhmr-floor.mjs +0 -102
  434. package/tools/ardy/gvhmr-to-cskel27.mjs +0 -21
  435. package/tools/ardy/gvhmr_fastpath.py +0 -168
  436. package/tools/ardy/gvhmr_trajectory.py +0 -240
  437. package/tools/ardy/merge-text-encoder.py +0 -160
  438. package/tools/ardy/mocap-metrics.mjs +0 -200
  439. package/tools/ardy/mocap-quality-gate.mjs +0 -110
  440. package/tools/ardy/motion-stabilize.mjs +0 -348
  441. package/tools/ardy/npz.mjs +0 -414
  442. package/tools/ardy/pose-to-npz.mjs +0 -106
  443. package/tools/ardy/prompt-limits.mjs +0 -5
  444. package/tools/ardy/run-edit-on-box.sh +0 -73
  445. package/tools/ardy/run-local.mjs +0 -233
  446. package/tools/ardy/run-on-box.sh +0 -593
  447. package/tools/ardy/run-sequence-on-box.sh +0 -177
  448. package/tools/ardy/runners/gvhmr-worker.mjs +0 -206
  449. package/tools/ardy/runners/index.mjs +0 -18
  450. package/tools/ardy/runners/local.mjs +0 -455
  451. package/tools/ardy/runners/proc.mjs +0 -125
  452. package/tools/ardy/runners/remote.mjs +0 -252
  453. package/tools/ardy/setup-local.mjs +0 -189
  454. package/tools/ardy/setup-text-encoder-on-box.sh +0 -93
  455. package/tools/ardy/setup-text-encoder.py +0 -306
  456. package/tools/ardy/smpl-cskel27.mjs +0 -178
  457. package/tools/ardy/visual-qa.mjs +0 -196
  458. package/tools/ardy/vq-car.mjs +0 -52
  459. package/tools/bench/EXP3.md +0 -61
  460. package/tools/bench/cclay_bench_extract_incam.py +0 -125
  461. package/tools/bench/cclay_bench_extract_obs.py +0 -204
  462. package/tools/bench/cclay_bench_runner.py +0 -42
  463. package/tools/bench/cube-contact.mjs +0 -162
  464. package/tools/bench/exp3.mjs +0 -152
  465. package/tools/bench/extract-bench-lib.mjs +0 -216
  466. package/tools/bench/extract-bench.mjs +0 -302
  467. package/tools/bench/fal-generate.mjs +0 -88
  468. package/tools/bench/fit/README.md +0 -189
  469. package/tools/bench/fit/camera.mjs +0 -59
  470. package/tools/bench/fit/contact.mjs +0 -419
  471. package/tools/bench/fit/footlock.mjs +0 -234
  472. package/tools/bench/fit/motion.mjs +0 -85
  473. package/tools/bench/fit/pin.mjs +0 -27
  474. package/tools/bench/fit/remote.mjs +0 -105
  475. package/tools/bench/fit-bench.mjs +0 -126
  476. package/tools/bench/fit-sanity.mjs +0 -45
  477. package/tools/bench/metrics.mjs +0 -313
  478. package/tools/bench/obs/depth.mjs +0 -258
  479. package/tools/bench/obs/extrinsics.mjs +0 -175
  480. package/tools/bench/obs/fit_mannequin_betas.py +0 -340
  481. package/tools/bench/obs/ground.mjs +0 -294
  482. package/tools/bench/obs/heading.mjs +0 -151
  483. package/tools/bench/obs/ladder.mjs +0 -555
  484. package/tools/bench/obs/mannequin-betas.json +0 -124
  485. package/tools/bench/obs/remote.mjs +0 -83
  486. package/tools/bench/obs/rest_joints.py +0 -52
  487. package/tools/bench/obs/ybot-targets.mjs +0 -49
  488. package/tools/bench/obs-bench.mjs +0 -520
  489. package/tools/bench/score.mjs +0 -343
  490. package/tools/bench/summarize.mjs +0 -86
  491. package/tools/demo-worker/README.md +0 -137
  492. package/tools/demo-worker/api-client.mjs +0 -233
  493. package/tools/demo-worker/generate.mjs +0 -301
  494. package/tools/demo-worker/index.mjs +0 -466
  495. package/tools/demo-worker/sign-core.mjs +0 -112
  496. package/tools/demo-worker/sign.mjs +0 -161
  497. package/tools/gt-render/browser.mjs +0 -182
  498. package/tools/gt-render/camera-math.mjs +0 -276
  499. package/tools/gt-render/page.mjs +0 -267
  500. package/tools/gt-render/render.mjs +0 -514
  501. package/tools/gt-render/scene-box.mjs +0 -31
  502. package/tools/gt-render/take-transform.mjs +0 -87
  503. package/tools/kimodo/compare-seam.mjs +0 -108
  504. package/tools/kimodo/constraints.mjs +0 -244
  505. package/tools/kimodo/edit.mjs +0 -259
  506. package/tools/kimodo/effector-constraints.mjs +0 -200
  507. package/tools/kimodo/generate.mjs +0 -646
  508. package/tools/kimodo/local-output.mjs +0 -265
  509. package/tools/kimodo/measure-pose.mjs +0 -65
  510. package/tools/kimodo/measure-preserve.mjs +0 -899
  511. package/tools/kimodo/measure-waypoints.mjs +0 -55
  512. package/tools/kimodo/mlx-generate.py +0 -111
  513. package/tools/kimodo/pose-constraints.mjs +0 -248
  514. package/tools/kimodo/preserve-mask.mjs +0 -543
  515. package/tools/kimodo/read-npz.mjs +0 -189
  516. package/tools/kimodo/run-edit-on-box.mjs +0 -155
  517. package/tools/kimodo/run-sequence-on-box.mjs +0 -178
  518. package/tools/kimodo/runner.mjs +0 -284
  519. package/tools/kimodo/seam-batch.mjs +0 -77
  520. package/tools/kimodo/setup-local.mjs +0 -30
  521. package/tools/kimodo/setup-on-box.sh +0 -339
  522. package/tools/kimodo/soma77-to-cskel27.mjs +0 -305
  523. package/tools/morphgs/assets/gen_truth.py +0 -39
  524. package/tools/morphgs/assets/mesh_ori_rig.txt +0 -27
  525. package/tools/morphgs/assets/playback-check.mjs +0 -30
  526. package/tools/morphgs/demo-gate.sh +0 -26
  527. package/tools/morphgs/fbx2morphgs.mjs +0 -68
  528. package/tools/morphgs/morphgs-to-cskel27.mjs +0 -105
  529. package/tools/morphgs/patches/preprocess_src-none-mode.patch +0 -76
  530. package/tools/morphgs/setup-on-cluster.sh +0 -126
  531. package/tools/projflow/driver.py +0 -1069
  532. package/tools/projflow/generate.mjs +0 -805
  533. package/tools/projflow/hml22-to-cskel27.mjs +0 -735
  534. package/tools/projflow/line-edit-job.mjs +0 -547
  535. package/tools/projflow/replay.mjs +0 -452
  536. package/tools/projflow/runner.mjs +0 -269
  537. package/tools/projflow/service.mjs +0 -742
  538. package/tools/track/DESIGN.md +0 -513
  539. package/tools/track/backfill-provenance.mjs +0 -116
  540. package/tools/track/budget.mjs +0 -8
  541. package/tools/track/check-rig.mjs +0 -209
  542. package/tools/track/diagnostics.schema.json +0 -60
  543. package/tools/track/export-rig.mjs +0 -256
  544. package/tools/track/fallback.mjs +0 -7
  545. package/tools/track/fk-parity-fixture.mjs +0 -161
  546. package/tools/track/gate.mjs +0 -349
  547. package/tools/track/masks.mjs +0 -318
  548. package/tools/track/metrics.mjs +0 -201
  549. package/tools/track/publish-obs.mjs +0 -108
  550. package/tools/track/py/check_env.py +0 -61
  551. package/tools/track/py/eval_lr.py +0 -229
  552. package/tools/track/py/lr_viterbi.py +0 -294
  553. package/tools/track/py/masks.py +0 -405
  554. package/tools/track/py/objective.py +0 -382
  555. package/tools/track/py/rig.py +0 -508
  556. package/tools/track/py/scene.py +0 -326
  557. package/tools/track/py/test_joint_indices.py +0 -123
  558. package/tools/track/py/test_lr_viterbi.py +0 -195
  559. package/tools/track/py/test_masks.py +0 -229
  560. package/tools/track/py/test_rig.py +0 -288
  561. package/tools/track/py/test_scene.py +0 -354
  562. package/tools/track/py/test_track.py +0 -328
  563. package/tools/track/py/track.py +0 -411
  564. package/tools/track/remote.mjs +0 -140
  565. package/tools/track/rig-dump.mjs +0 -227
  566. package/tools/track/run-box-tests.mjs +0 -14
  567. package/tools/track/run-box.mjs +0 -168
  568. package/tools/track/setup-box.sh +0 -59
  569. package/tools/track/study-2d.mjs +0 -728
  570. /package/src/{ardy → motion}/collision-blockers.js +0 -0
  571. /package/src/{ardy → motion}/ground.js +0 -0
  572. /package/src/{ardy → motion}/ik-drag.js +0 -0
  573. /package/src/{ardy → motion}/ik-key-json.js +0 -0
  574. /package/src/{ardy → motion}/ik.js +0 -0
  575. /package/src/{ardy → motion}/physics-surface.js +0 -0
  576. /package/src/{ardy → motion}/playback-clock.js +0 -0
  577. /package/src/{ardy → motion}/range-pin.js +0 -0
@@ -0,0 +1,160 @@
1
+ // Hints for GOAL groups (C8, C6). The hint moves whatever drives the goal's moving side at the
2
+ // worst frame by the shortfall (+3 mm): a hand (a hand joint, a prop point, an object in hand),
3
+ // else a foot or the root, through moveHint (hints-contact.js, read only). The fix is
4
+ // { channel: "hand_l", f, delta } when the hand target drives that hand (qa/patch.js turns it into
5
+ // a patch), { path: "/anchors/<a>/p", delta } when a world anchor does, and none for an FK arm.
6
+ // still moves the point back toward where it should stay; speed and a goal that waits on another
7
+ // (after/before) get a hint without a fix. Two causes come first, because moving the hand target
8
+ // cannot cure them:
9
+ // - another anchor weight of that hand is above 0 at the frame (it ramps between two keys
10
+ // across the goal), so the hand goes part of the way to that anchor: key it 0 there;
11
+ // - the arm is at full reach (the wrist target is clamped or missed): move the body (bodyMove).
12
+ import { resolveBone } from "../bones.js";
13
+ import { inHandAt } from "../object-pose.js";
14
+ import { refPoint } from "../refs.js";
15
+ import { scale, norm, sub, len } from "../compiler/math.js";
16
+ import { moveHint } from "./hints-contact.js";
17
+ import { goalKinds, goalName, goalNeed, goalWindow, movedHome, movedRef } from "./goals.js";
18
+ import { m3, vec } from "./fmt.js";
19
+
20
+ const MARGIN = 0.003, LEAK = 0.02, BODY_MAX = 0.2;
21
+
22
+ /**
23
+ * The body move that brings shoulder k `need` m nearer its hand at f (the arm points at its
24
+ * target when at full reach): { delta, hint, fix } with fix { channel: "root", f, delta, hold:
25
+ * true } (qa/patch.js moves what places the body: a standIdle at, a walkTo's steps or the agent's
26
+ * root keys). null when the arm points mostly up or down, where walking closer does not help.
27
+ */
28
+ export function bodyMove(X, k, f, need, why = "") {
29
+ const S = k === "l" ? "Left" : "Right", { s } = X.fr(f), v = sub(s.P[X.I[`${S}Hand`]], s.P[X.I[`${S}Arm`]]);
30
+ const flat = Math.hypot(v[0], v[2]) / Math.max(len(v), 1e-9);
31
+ if (flat < 0.5) return null;
32
+ const delta = scale(norm([v[0], 0, v[2]]), Math.min(BODY_MAX, need / flat));
33
+ return { delta, hint: `body: move ${vec(delta)} m toward the target at f${f}, ${X.cause("root", f)} (arm ${k} is at full reach)${why}`, fix: { channel: "root", f, delta, hold: true } };
34
+ }
35
+
36
+ /** Arm k cannot reach its wrist target at f (soft reach clamped it, or the IK missed; not a target inside the elbow's fold, arm_fold). */
37
+ const atReach = (X, k, f) => (X.fr(f).diag[`reach_over_${k}`] ?? 0) > 1e-3 || ((X.fr(f).diag[`arm_miss_${k}`] ?? 0) > 2e-3 && !X.fr(f).diag[`arm_fold_${k}`]);
38
+
39
+ /** The largest other anchor weight of hand k above LEAK at f while `drive` leads it, or null. */
40
+ function leakOf(X, k, f, drive) {
41
+ let best = null;
42
+ for (const [ch, w] of Object.entries(X.fr(f).c)) {
43
+ if (ch === drive || !new RegExp(`^w_.+_${k}$`).test(ch) || !(w > LEAK) || (best && w <= best.w)) continue;
44
+ best = { ch, w };
45
+ }
46
+ return best;
47
+ }
48
+
49
+ /** Leak hint: key the stray weight 0 at the goal's last frame when it rises from 0 there, else at f. */
50
+ function leakHint(X, goal, part, f, why) {
51
+ const k = part.slice(-1), d = X.hand(k, f), leak = d.anchor ? leakOf(X, k, f, d.ch) : null;
52
+ if (!leak) return null;
53
+ const end = Array.isArray(goal.at) ? goal.at[1] : goal.at, prev = X.bracket(leak.ch, f).prev;
54
+ const at = prev && Math.abs(X.out.baked[leak.ch]?.[Math.round(prev.f)] ?? 1) <= LEAK && end >= f && !(X.bracket(leak.ch, f).next?.f <= end) ? end : f;
55
+ const share = leak.w / Math.max(1, (X.fr(f).c[d.ch] ?? 1) + leak.w), other = leak.ch.replace(/^w_/, "").replace(/_[lr]$/, "");
56
+ return { cause: X.cause(leak.ch, f), hint: `${leak.ch} is ${m3(leak.w)} at f${f}, so anchor ${other} takes ${Math.round(share * 100)}% of the hand target from ${d.label}: key ${leak.ch} 0 at f${at}${why}`,
57
+ fix: { channel: leak.ch, f: at, value: 0 } };
58
+ }
59
+
60
+ /** The goal a GOAL group reports: the longest name its subject starts with. */
61
+ function goalOf(X, g) {
62
+ let best = null;
63
+ (X.spec?.goals ?? []).forEach((goal, i) => {
64
+ const name = goalName(goal, i);
65
+ if (g.subject.startsWith(`${name}:`) && (!best || name.length > best.name.length)) best = { goal, name, i };
66
+ });
67
+ return best;
68
+ }
69
+
70
+ /** The record that carries object `id` at frame f (the id itself or a group above it), or null. */
71
+ function carrierOf(X, id, f) {
72
+ const ids = [id, ...(X.out.ctx.scene?.objects?.[id]?.ancestors ?? [])];
73
+ const key = ids.find(k => inHandAt(X.spec.objects?.[k], f));
74
+ return key ? X.spec.objects[key] : null;
75
+ }
76
+
77
+ /** The body part (moveHint name) that moves ref at frame f: hand_<k>, foot_<k>, root, or null for a still point. */
78
+ function partOf(X, ref, f) {
79
+ const [, kind, rest] = /^([a-z]+):(.+)$/s.exec(ref) ?? [];
80
+ if (kind === "prop") return X.spec.props?.[rest.split(".")[0]]?.hand ? `hand_${X.spec.props[rest.split(".")[0]].hand}` : null;
81
+ if (kind === "object") {
82
+ const o = carrierOf(X, rest.replace(/\.(center|top|base)$/, ""), f), k = o && X.spec.props?.[o.prop]?.hand;
83
+ return k ? `hand_${k}` : null;
84
+ }
85
+ if (kind === "landmark") return "root";
86
+ if (kind !== "joint" && kind !== "bone") return null;
87
+ const bone = resolveBone(X.sk, rest).bone ?? "", side = /^Left/.test(bone) ? "l" : /^Right/.test(bone) ? "r" : null;
88
+ if (side && /^(Left|Right)(Hand|ForeArm)/.test(bone)) return `hand_${side}`;
89
+ if (side && /^(Left|Right)(Foot|Toe|Leg)/.test(bone)) return `foot_${side}`;
90
+ return "root";
91
+ }
92
+
93
+ /** fix for moving `part` by delta at f, when it applies to a channel or a world anchor. */
94
+ function fixFor(X, part, f, delta) {
95
+ const k = part.slice(-1);
96
+ if (!part.startsWith("hand_")) return { channel: part, f, delta };
97
+ const d = X.hand(k, f);
98
+ if (d.ch === `hand_${k}`) return { channel: d.ch, f, delta };
99
+ if (d.anchor && X.spec.anchors?.[d.anchor]?.kind === "world") return { path: `/anchors/${d.anchor}/p`, delta };
100
+ return null;
101
+ }
102
+
103
+ /** The side to move and its delta: [part, delta], hands first, then a body part, a to b (still: back toward its home, from `from`). */
104
+ function plan(X, goal, kind, f, amount, from) {
105
+ if (kind === "moved" || kind === "still") {
106
+ const ref = goal[kind], part = partOf(X, ref, f), home = movedHome(X.out, goal, from), p = refPoint(X.out, f, movedRef(ref));
107
+ const away = p && home && len(sub(p, home)) > 1e-3 ? norm(sub(p, home)) : [0, 1, 0];
108
+ return part ? [part, scale(away, kind === "still" ? -amount : amount)] : null;
109
+ }
110
+ const [a, b] = goal[kind], pa = partOf(X, a, f), pb = partOf(X, b, f);
111
+ const rank = p => (p?.startsWith("hand_") ? 2 : p ? 1 : 0), moveA = rank(pa) >= rank(pb);
112
+ const part = moveA ? pa : pb, sign = moveA ? 1 : -1;
113
+ if (!part) return null;
114
+ if (kind === "above") return [part, [0, sign * amount, 0]];
115
+ const qa = refPoint(X.out, f, a), qb = refPoint(X.out, f, b);
116
+ return qa && qb ? [part, scale(norm(sub(qb, qa)), sign * amount)] : null;
117
+ }
118
+
119
+ export const goalHints = {
120
+ /** GOAL(X, g) -> { cause, hint, fix? } | null */
121
+ GOAL(X, g) {
122
+ const found = X.spec && goalOf(X, g), f = g.worstFrame;
123
+ if (!found) return { cause: `goal ${g.subject.split(":")[0]}`, hint: `change the keys around f${f} until the goal holds` };
124
+ const { goal, name, i } = found, kind = goalKinds(goal)[0], amount = g.worst + MARGIN, w = goalWindow(X.out, X.spec.goals, i);
125
+ if (w.wait) {
126
+ const other = goal.after ?? goal.before;
127
+ return { cause: `goal ${name}`, hint: `${name} ${w.wait}: make goal "${other}" hold${goal.after !== undefined && goal.before !== undefined ? ` and "${goal.before}"` : ""} where this one needs it, or move at` };
128
+ }
129
+ const unit = kind === "speed" ? "m/s" : "m", at = kind === "near" || kind === "still" ? "<=" : ">=";
130
+ const why = ` (goal "${name}" is ${m3(g.worst)} ${unit} ${kind === "still" ? "off" : "short"}; needs ${at} ${goalNeed(goal, kind)})`;
131
+ if (kind === "speed") {
132
+ const part = partOf(X, goal.speed, f);
133
+ return { cause: part ? X.cause(part, f) : `goal ${name}`,
134
+ hint: `${goal.speed} is too slow at f${f}: put its keys around f${f} closer in time, or farther apart in space${why}` };
135
+ }
136
+ const move = plan(X, goal, kind, f, amount, w.a);
137
+ if (!move) {
138
+ if (kind === "still" && goal.still.startsWith("object:")) {
139
+ const id = goal.still.slice(7).replace(/\.(center|top|base)$/, "");
140
+ return { cause: `objects.${id}`, hint: `${id} moves at f${f}, where it should stay: hold its hinge channel or keys until then, or start them later${why}` };
141
+ }
142
+ if (kind === "still") return { cause: `goal ${name}`, hint: `${goal.still} moves at f${f} and nothing that follows the body moves it; name a joint, prop point or held object${why}` };
143
+ if (kind === "moved" && !goal.moved.startsWith("object:")) return { cause: `goal ${name}`, hint: `${goal.moved} does not move with the body at f${f}; name a joint, prop point or held object` };
144
+ if (kind === "moved") {
145
+ const id = goal.moved.slice(7).replace(/\.(center|top|base)$/, "");
146
+ return { cause: `objects.${id}`, hint: `${id} is not in a hand at f${f}, so nothing moves it: carry it (objects.${id} = { prop, grab }) or hinge it` };
147
+ }
148
+ return { cause: `goal ${name}`, hint: `neither ${goal[kind][0]} nor ${goal[kind][1]} moves with the body at f${f}; name a joint, prop point or held object` };
149
+ }
150
+ const [part, delta] = move;
151
+ if (part.startsWith("hand_")) {
152
+ const leak = leakHint(X, goal, part, f, why);
153
+ if (leak) return leak;
154
+ const body = atReach(X, part.slice(-1), f) && bodyMove(X, part.slice(-1), f, amount, why);
155
+ if (body) return { cause: X.cause("root", f), hint: body.hint, fix: body.fix };
156
+ }
157
+ const r = moveHint(X, part, f, delta, why), fix = fixFor(X, part, f, delta);
158
+ return { cause: r.cause, hint: r.hint, ...(fix ? { fix } : {}) };
159
+ },
160
+ };
@@ -0,0 +1,24 @@
1
+ // The knee's stop in QA hints (compiler/limbs.js KNEE_FOLD, KNEE_STOP). An ankle target nearer the hip
2
+ // than the knee folds leaves the ankle farther out: under the hip in a squat, down into the floor. So
3
+ // REACH leg_miss, GROUND on that foot and KNEE (folds past the stop) say the same thing: the hips are
4
+ // lower than the legs fold, bring the root up off the feet (a target above the hip: the foot out from it).
5
+ import { sub, scale, norm, len, dist, dot } from "../compiler/math.js";
6
+ import { reachAt, KNEE_FOLD, KNEE_STOP } from "../compiler/limbs.js";
7
+ import { vec, m3 } from "./fmt.js";
8
+
9
+ /** What the solver says of a knee on its stop: ` (the knee would fold 168deg; it bends to 150deg and stops at 160deg)`. */
10
+ export const kneeWhy = (lead, asked) => ` (${lead}the knee would fold ${Math.round(asked)}deg; it bends to ${KNEE_FOLD}deg and stops at ${KNEE_STOP}deg)`;
11
+
12
+ /**
13
+ * Hint for leg k at frame f whose ankle point T (the foot_k target, or the drawn ankle) is nearer the
14
+ * hip than the knee folds KNEE_FOLD: the root up by what puts T at that fold's reach when T is under the
15
+ * hip, else foot_k out from the hip by that much. `why` says why (kneeWhy).
16
+ */
17
+ export function kneeFoldHint(X, k, f, why, T) {
18
+ const S = k === "l" ? "Left" : "Right", P = X.fr(f).s.P, H = P[X.I[`${S}UpLeg`]], K = P[X.I[`${S}Leg`]], A = P[X.I[`${S}Foot`]];
19
+ const v = sub(T, H), R = reachAt(dist(H, K), dist(K, A), KNEE_FOLD) + 0.002, out = scale(norm(v), Math.max(0, R - len(v)));
20
+ // the hips up by d leave the target R from the hip: |v - d up| = R, the root above the target
21
+ const d = v[1] + Math.sqrt(Math.max(0, v[1] * v[1] - dot(v, v) + R * R));
22
+ if (v[1] < 0) return { cause: X.cause("root", f), hint: `root: raise ${m3(d)} m at f${f}${why}, or key foot_${k} farther from the hip (wider or more forward)`, fix: { channel: "root", f, delta: [0, d, 0] } };
23
+ return { cause: X.cause(`foot_${k}`, f), hint: `foot_${k}: move ${vec(out)} m out from the hip at f${f}${why}, or raise root ${m3(d)} m`, fix: { channel: `foot_${k}`, f, delta: out } };
24
+ }
@@ -0,0 +1,209 @@
1
+ // Hints for motion-type issues (reach, knees and elbows past their stop, pole flips, sliding, pops,
2
+ // floating, opted-in speed and acceleration) and for the spec's own checks (contacts, orient,
3
+ // below, appear). Orient and below try small changes of the wrist or elbow channels on the worst
4
+ // frame and scale the best one to the needed amount.
5
+ import { add, sub, scale, norm, len, dist, angle, apply, ry, clamp, dot } from "../compiler/math.js";
6
+ import { solveFrame } from "../compiler/compile.js";
7
+ import { channelsAt } from "../compiler/channels.js";
8
+ import { resolveAnchor } from "../compiler/anchors.js";
9
+ import { carried, reachAt, ELBOW_FOLD, ELBOW_STOP, KNEE_FOLD } from "../compiler/limbs.js";
10
+ import { pointOf } from "./spec-checks.js";
11
+ import { solePoints, ankleY } from "./geometry.js";
12
+ import { vec, m3 } from "./fmt.js";
13
+ import { moveHint } from "./hints-contact.js";
14
+ import { kneeFoldHint, kneeWhy } from "./hints-knee.js";
15
+ import { bodyMove } from "./hints-goal.js";
16
+
17
+ const LIMB = { Leg: ["UpLeg", "Leg", "Foot", "knee"], ForeArm: ["Arm", "ForeArm", "Hand", "elbow"] };
18
+ const sideOf = s => (s.startsWith("Left") ? "l" : "r");
19
+ const weightOf = ch => (/^(w_|armw)/.test(ch) ? 0.5 : /^(root|foot|hand)/.test(ch) ? 1 : /^(knee|elbow)/.test(ch) ? 0.2 : 0.01);
20
+
21
+ /** Channels that move joint j, ranked by how much each changed into frame f (metres-equivalent). */
22
+ function movers(X, j, f) {
23
+ const k = sideOf(j), b = X.out.baked;
24
+ let list = ["root", "yaw", "pitch"];
25
+ if (j === "Head") list = ["head_yaw", "head_pitch", "spine_pitch", "spine_yaw", "root", "yaw"];
26
+ else if (/Hand|ForeArm/.test(j)) list = [`hand_${k}`, `armw_${k}`, `elbow_${k}`, `aswing_${k}`, `abend_${k}`, ...Object.keys(b).filter(ch => new RegExp(`^w_.+_${k}$`).test(ch)), "spine_yaw", "spine_pitch", "root", "yaw"];
27
+ else if (/Foot|Leg/.test(j)) list = [`foot_${k}`, `knee_${k}`, `fpitch_${k}`, "root"];
28
+ const change = ch => {
29
+ const a = b[ch]?.[f - 1], c = b[ch]?.[f];
30
+ if (a === undefined || c === undefined || typeof a === "object" && !Array.isArray(a)) return 0;
31
+ return (Array.isArray(a) ? dist(a, c) : Math.abs(c - a)) * weightOf(ch);
32
+ };
33
+ return list.filter(ch => b[ch]).sort((p, q) => change(q) - change(p));
34
+ }
35
+
36
+ function speedHint(X, g, power) {
37
+ const j = g.subject.split(" ")[0], f = g.worstFrame, ch = movers(X, j, f)[0] ?? "root";
38
+ const { prev, next } = X.bracket(ch, f), stepped = X.keys(ch).find(k => k.step && Math.abs(k.f - f) <= 1);
39
+ if (stepped) return { cause: X.cause(ch, f), hint: `${ch}: a step key jumps at f${Math.round(stepped.f)}; ease it over 4+ frames (key the old value 4 frames earlier, drop the step)` };
40
+ const factor = Math.ceil(Math.pow(g.worst / g.limit, power) * 1.1 * 10) / 10;
41
+ const a = Math.round(prev?.f ?? f - 4), b = Math.round(next?.f ?? f + 4), extra = Math.ceil((b - a) * (factor - 1));
42
+ const anchor = /^w_(.+)_[lr]$/.exec(ch);
43
+ return { cause: X.cause(ch, f), hint: `retime: stretch f${a}-f${b} by ${factor}x (+${extra} frames, move the keys from f${b} on later)${anchor ? ` - the hand switches to anchor ${anchor[1]} too fast` : ""}; or fix.retime: true`,
44
+ fix: X.spec.fix === false ? undefined : { path: "/fix/retime", value: true } };
45
+ }
46
+
47
+ /** A check point (joint:/prop:/anchor:) on a freshly solved frame. */
48
+ function pointOn(X, fr, ref) {
49
+ const [kind, rest] = ref.split(/:(.*)/s);
50
+ if (kind === "joint") return fr.s.P[X.I[rest]];
51
+ if (kind === "prop") { const [p, pt] = rest.split("."); return fr.diag.props?.[p]?.[pt]; }
52
+ return resolveAnchor(X.out.ctx, rest, fr.s, fr.diag, fr.c);
53
+ }
54
+
55
+ /** Try channel changes on frame f; returns the best { ch, value, gain } for measure() (higher is better). */
56
+ function probe(X, f, tries, measure) {
57
+ const c0 = channelsAt(X.out.baked, f), base = measure(X.fr(f));
58
+ let best = null;
59
+ for (const [ch, value, unit] of tries) {
60
+ const gain = measure(solveFrame(X.out.ctx, { ...c0, [ch]: value }, f)) - base;
61
+ if (gain > 1e-6 && (!best || gain / unit > best.gain / best.unit)) best = { ch, value, gain, unit };
62
+ }
63
+ return { best, base, c0 };
64
+ }
65
+
66
+ const hipOrShoulder = (X, k, f, limb) => X.fr(f).s.P[X.I[`${k === "l" ? "Left" : "Right"}${LIMB[limb][0]}`]];
67
+
68
+ export const motionHints = {
69
+ REACH(X, g) {
70
+ const [, limb, k] = /^(leg|arm)_miss_([lr])$/.exec(g.subject) ?? [], f = g.worstFrame, need = g.worst + 0.002;
71
+ if (limb === "leg") {
72
+ // an ankle target nearer the hip than the knee folds (compiler/limbs.js): the hips up off it
73
+ const fold = X.fr(f).diag[`leg_fold_${k}`];
74
+ if (fold) return kneeFoldHint(X, k, f, kneeWhy(`the ankle target is ${m3(g.worst)} m inside the shin's reach: `, fold), X.fr(f).c[`foot_${k}`]);
75
+ const T = X.fr(f).c[`foot_${k}`] ?? [0, 0, 0], H = hipOrShoulder(X, k, f, "Leg"), v = sub(T, H), R = dist(H, T) - g.worst;
76
+ const disc = v[1] * v[1] - dot(v, v) + R * R, side = [v[0], 0, v[2]];
77
+ const alt = disc >= 0 ? `or root down ${m3(-v[1] - Math.sqrt(disc) + 0.002)} m${g.worst <= 0.05 ? " (fix.reach does this when on)" : ""}`
78
+ : `or root ${vec(scale(norm(side), len(side) - R * 0.95))} m toward the foot (the foot is ${m3(len(side))} m sideways of the hip: the root and foot keys disagree)`;
79
+ return { cause: X.cause(`foot_${k}`, f), hint: `foot_${k}: move ${vec(scale(norm(sub(H, T)), need))} m toward the hip at f${f}, ${alt}`, fix: { channel: `foot_${k}`, f, delta: scale(norm(sub(H, T)), need) } };
80
+ }
81
+ const S = k === "l" ? "Left" : "Right", { s, diag } = X.fr(f), dir = norm(sub(s.P[X.I[`${S}Hand`]], s.P[X.I[`${S}Arm`]]));
82
+ // a target nearer the shoulder than the elbow folds (compiler/limbs.js): out from the shoulder, or the body back from it
83
+ const fold = diag[`arm_fold_${k}`];
84
+ if (fold) {
85
+ const r = moveHint(X, `hand_${k}`, f, scale(dir, need), ` (the hand target is ${m3(g.worst)} m inside the forearm's reach: the elbow would fold ${Math.round(fold)}deg; it bends to ${ELBOW_FOLD}deg and stops at ${ELBOW_STOP}deg)`);
86
+ return { cause: r.cause, hint: `${r.hint}, or bring root ${vec(scale([-dir[0], 0, -dir[2]], need))} m back from it`, fix: r.fix };
87
+ }
88
+ const r = moveHint(X, `hand_${k}`, f, scale(dir, -need), "");
89
+ // a hand on a point the scene or the spec pins (an object anchor, one a goal or contact check names) stays: the body comes closer
90
+ const a = X.hand(k, f).anchor, refs = JSON.stringify([X.spec.goals ?? [], X.spec.checks?.contacts ?? []]);
91
+ const pinned = a && (X.spec.anchors?.[a]?.kind === "object" || refs.includes(`"anchor:${a}"`)), fit = X.spec.fits?.[X.hand(k, f).fit ?? -1];
92
+ // a fit pushes the wrist target far past reach; the body comes as far as the effector is short
93
+ const eff = fit?.effector?.prop && X.fr(f).diag.props?.[fit.effector.prop]?.[fit.effector.point];
94
+ const gap = eff ? dist(eff, add(pointOn(X, X.fr(f), `anchor:${a}`), fit.offset ?? [0, 0, 0])) + 0.01 : need;
95
+ const body = pinned && bodyMove(X, k, f, Math.min(need, gap));
96
+ if (body) return { cause: r.cause, hint: `${body.hint} (out of reach by ${m3(g.worst)} m), or ${r.hint}`, fix: body.fix };
97
+ return { cause: r.cause, hint: `${r.hint} (out of reach by ${m3(g.worst)} m), or bring root ${vec(scale([dir[0], 0, dir[2]], need))} m closer`, fix: r.fix };
98
+ },
99
+ KNEE(X, g) {
100
+ const k = sideOf(g.subject), S = k === "l" ? "Left" : "Right", f = g.worstFrame, { s, c } = X.fr(f), P = s.P, I = X.I;
101
+ if (/backward/.test(g.subject)) {
102
+ const toe = sub(P[I[`${S}ToeBase`]], P[I[`${S}Foot`]]); toe[1] = 0;
103
+ const pole = add(norm(apply(ry(-(c.yaw ?? 0)), toe)), [0, 0.3, 0]);
104
+ return { cause: X.cause(`knee_${k}`, f), hint: `knee_${k}: key ${vec(norm(pole))} (body frame, along the foot) at f${f}; a knee pole is +z forward, +x the character's left`, fix: { channel: `knee_${k}`, f, value: norm(pole) } };
105
+ }
106
+ // past the stop (the solver stops every knee at KNEE_STOP, so this pose came from outside it): the hips up off the foot
107
+ if (/ folds /.test(g.subject)) return kneeFoldHint(X, k, f, ` (the knee folds ${m3(g.worst)}deg, past its ${g.limit}deg stop; it bends freely to ${KNEE_FOLD}deg)`, P[I[`${S}Foot`]]);
108
+ const H = P[I[`${S}UpLeg`]], K = P[I[`${S}Leg`]], A = P[I[`${S}Foot`]], a = dist(H, K), b = dist(K, A);
109
+ const inner = (180 - (g.limit - 5)) * Math.PI / 180, want = Math.sqrt(a * a + b * b - 2 * a * b * Math.cos(inner));
110
+ return { cause: X.cause(`foot_${k}`, f), hint: `foot_${k}: move ${vec(scale(norm(sub(A, H)), want - dist(H, A)))} m away from the hip at f${f} (the knee folds ${m3(g.worst)} deg, limit ${g.limit})`,
111
+ fix: { channel: `foot_${k}`, f, delta: scale(norm(sub(A, H)), want - dist(H, A)) } };
112
+ },
113
+ // the solver stops every elbow at ELBOW_STOP, so a fold past it comes from outside the solver: the hand out from the shoulder
114
+ ELBOW(X, g) {
115
+ const k = sideOf(g.subject), S = k === "l" ? "Left" : "Right", f = g.worstFrame, P = X.fr(f).s.P, I = X.I;
116
+ const A = P[I[`${S}Arm`]], E = P[I[`${S}ForeArm`]], W = P[I[`${S}Hand`]], delta = scale(norm(sub(W, A)), reachAt(dist(A, E), dist(E, W), ELBOW_FOLD) - dist(A, W));
117
+ return moveHint(X, `hand_${k}`, f, delta, ` (the elbow folds ${m3(g.worst)}deg, past its ${g.limit}deg stop; it bends freely to ${ELBOW_FOLD}deg)`);
118
+ },
119
+ FLIP(X, g) {
120
+ const [, S, limb] = /^(Left|Right)(Leg|ForeArm) /.exec(g.subject) ?? [], f = g.worstFrame;
121
+ if (!limb) return null;
122
+ const k = sideOf(S), [r, m, e, pole] = LIMB[limb], I = X.I, gap = +(/ over (\d+) frames$/.exec(g.subject)?.[1] ?? 1);
123
+ // an elbow on its stop: the target passes near the shoulder and the stopped hand swings round it, whatever the pole
124
+ const fd = X.fr(f).diag, asked = limb === "ForeArm" && (fd[`arm_fold_${k}`] ?? fd[`hold_${k}`]?.fold);
125
+ if (asked) {
126
+ const held = fd[`hold_${k}`]?.fold && Object.entries(X.spec.objects ?? {}).find(([, o]) => o.keys?.length && X.spec.props?.[o.prop]?.hand === k)?.[0];
127
+ return { cause: held ? `objects.${held}.keys` : X.cause(X.hand(k, f).ch, f), hint: `the ${held ? `keys of ${held} ask` : "hand target asks"} the elbow to fold ${Math.round(asked)}deg at f${f}, past its ${ELBOW_STOP}deg stop, so the hand swings round the shoulder on the stop: move ${held ? "the keyed pose" : "the target"} out from the shoulder (REACH says how far)` };
128
+ }
129
+ const bendAt = q => { const P = X.fr(q).s.P; return angle(sub(P[I[`${S}${m}`]], P[I[`${S}${r}`]]), sub(P[I[`${S}${e}`]], P[I[`${S}${m}`]])); };
130
+ // a flip over several frames passed through the straight (or folded) frames between: the straightest names it
131
+ const fb = Array.from({ length: Math.max(1, gap - 1) }, (_, i) => (gap > 1 ? f - 1 - i : f)).reduce((p, q) => (bendAt(q) < bendAt(p) ? q : p)), bend = bendAt(fb);
132
+ const ch = `${pole}_${k}`, prevPole = X.out.baked[ch]?.[Math.max(0, f - 1)];
133
+ if (prevPole && len(prevPole) < 0.5) return { cause: X.cause(ch, f), hint: `${ch}: the pole passes near zero (length ${m3(len(prevPole))}) at f${f} while it turns between opposite keys; add an in-between pole key (e.g. the average direction, normalised) so it turns, not shrinks` };
134
+ const straight = bend < 12 ? `the limb is almost straight at f${fb} (bend ${m3(bend)} deg), so the pole cannot hold it: bring the ${limb === "Leg" ? "foot" : "hand"} target ~0.02 m toward the ${limb === "Leg" ? "hip" : "shoulder"}, or ` : "";
135
+ return { cause: X.cause(ch, f), hint: `${ch}: ${straight}keep the pole on one side through f${f}${prevPole ? ` (key ${vec(prevPole)} there)` : ""}`, fix: prevPole && { channel: ch, f, value: prevPole } };
136
+ },
137
+ SLIDE(X, g) {
138
+ const k = sideOf(g.subject), S = k === "l" ? "Left" : "Right", key = g.subject.includes("heel") ? "heel" : "ball";
139
+ const f0 = Math.max(0, g.first - 1), at = f => solePoints(X.sk, X.fr(f).s, S)[key];
140
+ const slid = Math.hypot(at(g.last)[0] - at(f0)[0], at(g.last)[2] - at(f0)[2]), hold = X.out.baked[`foot_${k}`]?.[f0];
141
+ // the ankle stays and the foot turns flat on the floor (fyaw): the turn is the slide
142
+ const turn = X.out.baked[`fyaw_${k}`], end = X.out.baked[`foot_${k}`]?.[g.last];
143
+ if (turn && hold && end && Math.hypot(end[0] - hold[0], end[2] - hold[2]) < slid / 2 && Math.abs(turn[g.last] - turn[f0]) > 1) return { cause: X.cause(`fyaw_${k}`, g.worstFrame),
144
+ hint: `fyaw_${k}: the flat foot turns ${m3(turn[f0])} -> ${m3(turn[g.last])} deg over f${f0}-f${g.last} and the ${key} slides ${m3(slid)} m; key fyaw_${k} ${m3(turn[f0])} at f${g.last} so it turns once the foot is up, or lift the heel first (fpitch_${k} +) so it pivots on the ball`,
145
+ fix: { channel: `fyaw_${k}`, f: g.last, value: turn[f0] } };
146
+ return { cause: X.cause(`foot_${k}`, g.worstFrame), hint: `foot_${k}: hold it still over f${f0}-f${g.last}${hold ? ` (key ${vec(hold)} at both ends)` : ""}; the ${key} slides ${m3(slid)} m${slid < 0.01 ? "; fix.slide locks this when on" : ""}`,
147
+ fix: hold && { channel: `foot_${k}`, f: g.last, value: hold } };
148
+ },
149
+ SPEED: (X, g) => speedHint(X, g, 1),
150
+ POP: (X, g) => speedHint(X, g, 0.5),
151
+ ACC: (X, g) => speedHint(X, g, 0.5),
152
+ FLOAT: (X, g) => ({ cause: X.cause("root", g.worstFrame), hint: `over f${g.first}-f${g.last} lower the root or key foot_l/foot_r back onto the ground, the floor or the top of an object under the foot (the ankle stands ${ankleY(X.sk)} m above it); a jump needs the hips to rise and fall; a seat or ledge the hips rest on goes in checks.supports (a foot on an object's top needs none)` }),
153
+ LENGTH: () => ({ cause: "compiler", hint: "a bone changed length: report the spec, this is a compiler fault" }),
154
+ CONTACT(X, g) {
155
+ const ck = (X.spec.checks?.contacts ?? []).find(c => g.subject.startsWith(`${c.name}:`)), f = g.worstFrame, i = X.rel(f);
156
+ if (!ck) return null;
157
+ const a = pointOf(X.out, i, ck.point), b = pointOf(X.out, i, ck.target), delta = sub(b, a);
158
+ const prop = /^prop:([^.]+)/.exec(ck.point), k = prop ? X.spec.props?.[prop[1]]?.hand : sideOf(ck.point.slice(6));
159
+ const { diag } = X.fr(f), over = Math.max(diag[`reach_over_${k}`] ?? 0, diag[`arm_miss_${k}`] ?? 0);
160
+ const anchor = /^anchor:(.+)$/.exec(ck.target)?.[1], fit = (X.spec.fits ?? []).findIndex(ft => ft.side === k && ft.anchor === anchor);
161
+ const cause = fit >= 0 ? `fits[${fit}] (${k} -> anchor ${anchor})` : moveHint(X, `hand_${k}`, f, delta).cause;
162
+ const toRoot = scale(norm([delta[0], 0, delta[2]]), over + 0.01), fold = diag[`arm_fold_${k}`];
163
+ if (over > 0.002 && fold) {
164
+ const S = X.fr(f).s.P[X.I[`${k === "l" ? "Left" : "Right"}Arm`]], back = scale(norm([S[0] - b[0], 0, S[2] - b[2]]), over + 0.01);
165
+ return { cause, hint: `inside the forearm's reach by ${m3(over)} m at f${f} (the elbow would fold ${Math.round(fold)}deg; it stops at ${ELBOW_STOP}deg): bring root ${vec(back)} m back from ${anchor ?? "the target"}, or move the target out from the shoulder`, fix: { channel: "root", f, delta: back } };
166
+ }
167
+ if (over > 0.002) return { cause, hint: `out of reach by ${m3(over)} m at f${f}: bring root ${vec(toRoot)} m toward ${anchor ?? "the target"}, or move the target`, fix: { channel: "root", f, delta: toRoot } };
168
+ const r = moveHint(X, `hand_${k}`, f, delta);
169
+ return { cause, hint: `${r.hint}${fit < 0 && anchor ? `; or add fits: [{ side: "${k}", anchor: "${anchor}", effector: ${prop ? `{ prop: "${prop[1]}", point: "${ck.point.split(".")[1]}" }` : "{ joints: [...] }"} }]` : ""}`, fix: r.fix };
170
+ },
171
+ ORIENT(X, g) {
172
+ const ck = (X.spec.checks?.orient ?? []).find(c => g.subject.startsWith(`${c.name}:`)), f = g.worstFrame;
173
+ if (!ck) return null;
174
+ const prop = /^prop:([^.]+)/.exec(ck.from), k = prop ? X.spec.props?.[prop[1]]?.hand : sideOf(ck.from.slice(6)), minCos = ck.minCos ?? 0.64;
175
+ const along = fr => (ck.along === "up" ? [0, 1, 0] : ck.along === "face" ? carried(X.sk, fr.s, X.I.Head, [0, 0, 1]) : norm(ck.along));
176
+ const cos = fr => dot(norm(sub(pointOn(X, fr, ck.to), pointOn(X, fr, ck.from))), along(fr));
177
+ const c = channelsAt(X.out.baked, f), tw = c[`wtwist_${k}`] ?? 0, fx = c[`wflex_${k}`] ?? 0;
178
+ const { best, base } = probe(X, f, [[`wtwist_${k}`, clamp(tw + 15, -85, 85), 15], [`wtwist_${k}`, clamp(tw - 15, -85, 85), 15], [`wflex_${k}`, clamp(fx + 15, -70, 70), 15], [`wflex_${k}`, clamp(fx - 15, -70, 70), 15]], cos);
179
+ if (!best) return { cause: X.cause(`wtwist_${k}`, f), hint: `turn the prop another way: wrist twist/flex do not help at f${f} (cos ${m3(base)}, needs ${minCos}); change elbow_${k} or the prop points` };
180
+ const from = best.ch.startsWith("wtwist") ? tw : fx, lim = best.ch.startsWith("wtwist") ? 85 : 70;
181
+ const want = clamp(from + (best.value - from) * Math.min(4, (minCos - base + 0.03) / best.gain), -lim, lim);
182
+ return { cause: X.cause(best.ch, f), hint: `${best.ch}: key ${m3(want)} deg at f${f} (now ${m3(from)}; cos ${m3(base)}, needs ${minCos})`, fix: { channel: best.ch, f, value: want } };
183
+ },
184
+ BELOW(X, g) {
185
+ const ck = (X.spec.checks?.below ?? []).find(c => g.subject.startsWith(`${c.name}:`)), f = g.worstFrame;
186
+ if (!ck) return null;
187
+ const k = sideOf(ck.joint), ch = `${/Leg/.test(ck.joint) ? "knee" : "elbow"}_${k}`, pole = channelsAt(X.out.baked, f)[ch] ?? [0, -1, -0.3];
188
+ const drop = fr => fr.s.P[X.I[ck.ref]][1] - fr.s.P[X.I[ck.joint]][1];
189
+ // candidate poles (body frame), nearest to the current one first among those that are enough
190
+ const need = (ck.min ?? 0.08) + 0.01, c0 = channelsAt(X.out.baked, f), base = drop(X.fr(f));
191
+ const tries = [[0, -1, 0], [0.5, -1, 0], [-0.5, -1, 0], [0, -1, -0.4], [0, -1, 0.4], [0.5, -1, 0.5], [-0.5, -1, 0.5], [0.5, -1, -0.4], [-0.5, -1, -0.4]]
192
+ .map(p => ({ p, d: drop(solveFrame(X.out.ctx, { ...c0, [ch]: p }, f)), turn: angle(p, pole) }));
193
+ const ok = tries.filter(t => t.d >= need).sort((p, q) => p.turn - q.turn)[0], best = ok ?? tries.sort((p, q) => q.d - p.d)[0];
194
+ if (!(best.d > base)) return { cause: X.cause(ch, f), hint: `${ch}: no pole change lowers ${ck.joint} at f${f}; lower the hand target instead` };
195
+ return { cause: X.cause(ch, f), hint: `${ch}: key ${vec(best.p)} (body frame: +y up, +z forward) at f${f} (${ck.joint} ${m3(base)} m below ${ck.ref} -> ${m3(best.d)}, needs ${ck.min ?? 0.08})`, fix: { channel: ch, f, value: best.p } };
196
+ },
197
+ APPEAR(X, g) {
198
+ const ck = (X.spec.checks?.appear ?? []).find(c => g.subject.startsWith(`${c.name}:`)), f = g.worstFrame;
199
+ if (!ck) return null;
200
+ let best = null;
201
+ for (let q = Math.max(X.out.f0 ?? 0, f - 24); q <= Math.min((X.out.f0 ?? 0) + X.out.frames.length - 1, f + 24); q++) {
202
+ const d = len(sub(pointOf(X.out, X.rel(q), `joint:${ck.joint}`), pointOf(X.out, X.rel(q), `anchor:${ck.anchor}`)));
203
+ if (!best || d < best.d) best = { q, d };
204
+ }
205
+ const tol = ck.tol ?? 0.035;
206
+ return { cause: X.cause(ck.channel, f), hint: best.d <= tol ? `${ck.channel}: move the step from f${f} to f${best.q} (${ck.joint} ${m3(best.d)} m from ${ck.anchor} there)`
207
+ : `${ck.joint} never gets within ${tol} m of ${ck.anchor} near f${f} (closest ${m3(best.d)} m at f${best.q}): hold its weight w_${ck.anchor}_${sideOf(ck.joint)} at 1 around the switch` };
208
+ },
209
+ };
@@ -0,0 +1,91 @@
1
+ // Hints for object issues (C3, C6, 7.1). A hint function takes the explain context X
2
+ // (qa/explain.js) and the group g and returns { cause, hint, fix? } (fix: see qa/patch.js).
3
+ // GRAB looks within +-24 frames for the frame nearest the grab where the hand is within the
4
+ // limit and proposes fix { path: "/objects/<id>/grab", value: f }; failing that it
5
+ // moves the hand to the object (moveHint) with a fix on what drives the hand
6
+ // COLLIDE for a carried object ("<part> x <id>", "<id> x <other>") moves the holding hand,
7
+ // so the object comes out; the other hand's arm in it while that hand is on an object
8
+ // anchor on it moves the anchor's local point out instead; any other COLLIDE is the body hint (hints-contact.js)
9
+ // REACH for a keyed object ("<id>: 6.2 cm off its keys", key-checks.js); else the arm and leg hint (hints-motion.js)
10
+ import { contactHints, moveHint } from "./hints-contact.js";
11
+ import { motionHints } from "./hints-motion.js";
12
+ import { keyHint } from "./key-checks.js";
13
+ import { CAPSULES, segPoints, sdBox, boxNormal } from "./geometry.js";
14
+ import { grabGap, boxOverlap } from "./object-checks.js";
15
+ import { isCarried, propOrigin } from "../carried.js";
16
+ import { objectBoxesAt, partsOf, spanOf } from "../object-pose.js";
17
+ import { groupsOf } from "../scene.js";
18
+ import { sub, scale, len, apply, T, add, clamp } from "../compiler/math.js";
19
+
20
+ const MARGIN = 0.003, SEARCH = 24;
21
+
22
+ /** A fix that moves hand k by delta at f, on whatever drives it there (hand channel, fit or world anchor). */
23
+ function handFix(X, k, f, delta) {
24
+ const d = X.hand(k, f);
25
+ if (d.ch === `hand_${k}`) return { channel: d.ch, f, delta };
26
+ if (d.fit !== null && d.fit !== undefined) return X.spec.fits[d.fit].offset ? { path: `/fits/${d.fit}/offset`, delta } : { path: `/fits/${d.fit}/offset`, value: delta };
27
+ if (d.anchor && X.spec.anchors?.[d.anchor]?.kind === "world") return { path: `/anchors/${d.anchor}/p`, delta };
28
+ return undefined;
29
+ }
30
+
31
+ function moveHand(X, k, f, delta, why) {
32
+ const r = moveHint(X, `hand_${k}`, f, delta, why), fix = handFix(X, k, f, delta);
33
+ return fix ? { ...r, fix } : r;
34
+ }
35
+
36
+ /** True when hand k is on an object anchor (weight > 0.5) on record `id` or a body or group under it at f. */
37
+ function anchoredOn(X, k, f, id) {
38
+ const a = X.spec.anchors?.[X.hand(k, f).anchor], scene = X.out.ctx.scene;
39
+ return a?.kind === "object" && [scene.objects?.[a.object]?.ancestors, groupsOf(scene)[a.object]?.ancestors, [a.object]].some(ids => ids?.includes(id));
40
+ }
41
+
42
+ /** Closest point of a box (base pivot) to p. */
43
+ function closest(box, p) {
44
+ const q = apply(T(box.R), sub(p, box.pos)), c = [0, box.half[1], 0];
45
+ return add(box.pos, apply(box.R, q.map((v, i) => clamp(v, c[i] - box.half[i], c[i] + box.half[i]))));
46
+ }
47
+
48
+ export const objectHints = {
49
+ REACH: (X, g) => keyHint(X, g) ?? motionHints.REACH(X, g),
50
+ /** GRAB(X, g) -> { cause, hint, fix? } */
51
+ GRAB(X, g) {
52
+ const id = g.subject.split(":")[0], grab = g.worstFrame, o = X.spec?.objects?.[id];
53
+ const plain = { cause: `objects.${id}.grab`, hint: `${id}: move the hand to it by f${grab}, or set objects.${id}.grab to the frame the hand arrives` };
54
+ if (!X.out || !isCarried(o)) return plain;
55
+ let best = null;
56
+ for (let f = grab - SEARCH; f <= grab + SEARCH; f++) {
57
+ const gap = grabGap(X.out, id, f);
58
+ if (gap !== null && gap <= g.limit && (!best || Math.abs(f - grab) < Math.abs(best[0] - grab))) best = [f, gap];
59
+ }
60
+ if (best) return { cause: plain.cause, hint: `${id}: set objects.${id}.grab to ${best[0]} (the hand is ${(best[1] * 100).toFixed(1)} cm from it there, ${(g.worst * 100).toFixed(1)} cm at f${grab})`, fix: { path: `/objects/${id}/grab`, value: best[0] } };
61
+ const { sk, spec, scene } = X.out.ctx, p = propOrigin(sk, spec, X.fr(grab).s, o);
62
+ const target = partsOf(scene, id).map(part => closest(scene.objects[part], p)).reduce((a, b) => (len(sub(b, p)) < len(sub(a, p)) ? b : a));
63
+ const gap = len(sub(target, p)), delta = scale(sub(target, p), gap > 1e-9 ? (gap - g.limit / 2) / gap : 0);
64
+ const r = moveHand(X, spec.props[o.prop].hand, grab, delta, ` (to ${id} at grab)`);
65
+ return { ...r, hint: `${r.hint}; or set objects.${id}.grab to the frame the hand arrives` };
66
+ },
67
+ /** COLLIDE(X, g) for a carried object, else the body hint. */
68
+ COLLIDE(X, g) {
69
+ const [, a, b] = /^(\S+) x (.+)$/.exec(g.subject) ?? [], objects = X.spec?.objects ?? {};
70
+ const id = isCarried(objects[b]) ? b : isCarried(objects[a]) ? a : null;
71
+ if (!id || !X.out) return contactHints.COLLIDE(X, g);
72
+ const o = objects[id], k = X.spec.props[o.prop].hand, [grab, release] = spanOf(X.out, o), need = g.worst - g.limit + MARGIN;
73
+ const f = g.worstFrame, held = f >= grab && f <= release, at = Math.min(f, release), boxes = objectBoxesAt(X.out, id, f) ?? [];
74
+ if (id === b) {
75
+ const cap = CAPSULES.find(c => c[0] === a), { s } = X.fr(f);
76
+ if (!cap || !boxes.length) return null;
77
+ const pts = segPoints(s.P[X.I[cap[1]]], s.P[X.I[cap[2]]]);
78
+ const [box, p] = boxes.flatMap(bx => pts.map(q => [bx, q])).reduce((m, x) => (sdBox(x[1], x[0]) < sdBox(m[1], m[0]) ? x : m));
79
+ const n = boxNormal(p, box), j = /^(hand|farm|uarm)_([lr])$/.exec(a)?.[2];
80
+ // the other hand on an object anchor on what hand k carries rides with it: moving the carry cannot part them, its point moves out
81
+ if (held && j && j !== k && anchoredOn(X, j, f, id)) return moveHint(X, `hand_${j}`, f, scale(n, need), ` (out of ${id})`);
82
+ return held ? moveHand(X, k, f, scale(n, -need), ` (${id} off ${a})`) : moveHint(X, a, f, scale(n, need), ` (out of ${id})`);
83
+ }
84
+ const ob = X.out.objectsAt(X.rel(f))[b];
85
+ if (!ob || !boxes.length) return null;
86
+ // out on the side it came from: where it was the frame before the hit (or at grab)
87
+ const was = objectBoxesAt(X.out, id, Math.max(grab, g.first - 1)) ?? boxes;
88
+ const hit = boxes.map((bx, i) => boxOverlap(bx, ob, sub(was[i].pos, ob.pos))).reduce((m, x) => (x.depth > m.depth ? x : m));
89
+ return moveHand(X, k, at, scale(hit.axis, hit.depth - g.limit + MARGIN), held ? ` (${id} out of ${b})` : ` (${id} is put down in ${b}; it stays where the hand lets go at f${release})`);
90
+ },
91
+ };
@@ -0,0 +1,42 @@
1
+ // Motion QA entry: general checks on every take, the checks the spec declares, the objects it
2
+ // moves (object-checks.js), its goals (goals.js) and the style checks it opted into by naming
3
+ // their limit (style.js; otherwise style is measured, not judged: observeMotion). Groups are
4
+ // ranked by severity.js.
5
+ import { generalChecks, QA_LIMITS } from "./checks.js";
6
+ import { styleChecks, floatChecks } from "./style.js";
7
+ import { specChecks } from "./spec-checks.js";
8
+ import { objectChecks } from "./object-checks.js";
9
+ import { goalChecks } from "./goals.js";
10
+ import { wristIssues } from "./wrist-checks.js";
11
+ import { explainGroups } from "./explain.js";
12
+ import { LOW_IS_BAD, groupKey, rankGroups } from "./severity.js";
13
+
14
+ /** Group issues by kind + subject (severity.js groupKey); each group reports count, worst value and frame range. */
15
+ function summarizeIssues(issues) {
16
+ const groups = new Map();
17
+ for (const i of issues) {
18
+ const key = groupKey({ kind: i.kind, subject: i.msg });
19
+ const g = groups.get(key) ?? { kind: i.kind, subject: i.msg, count: 0, worst: i.value, worstFrame: i.frame, first: i.frame, last: i.frame, limit: i.limit };
20
+ g.count++; g.first = Math.min(g.first, i.frame); g.last = Math.max(g.last, i.frame);
21
+ const worse = LOW_IS_BAD.has(i.kind) ? i.value < g.worst : i.value > g.worst;
22
+ if (worse) { g.worst = i.value; g.worstFrame = i.frame; g.subject = i.msg; }
23
+ groups.set(key, g);
24
+ }
25
+ return rankGroups([...groups.values()]);
26
+ }
27
+
28
+ /**
29
+ * opts.explain (default true) adds cause + hint (and patch, qa/patch.js) to the top groups
30
+ * (explain.js); opts.userSpec is the spec as the agent sent it, which patches address. Frames
31
+ * are absolute even when the compile was windowed (out.f0). qa.goals is there when spec.goals is.
32
+ */
33
+ export function runMotionQa(sk, out, spec, opts = {}) {
34
+ const checks = spec.checks ?? {}, goals = goalChecks(out, spec.goals ?? []);
35
+ const issues = [...generalChecks(sk, out, { allow: checks.allow, limits: checks.limits, mouthPoints: checks.mouthPoints }), ...specChecks(out, checks),
36
+ ...objectChecks(sk, out, spec), ...floatChecks(out, checks),
37
+ ...styleChecks(sk, out, checks, { ...QA_LIMITS, ...checks.limits }), ...wristIssues(sk, out), ...goals.issues];
38
+ if (out.f0) for (const i of issues) i.frame += out.f0;
39
+ const groups = summarizeIssues(issues);
40
+ if (opts.explain !== false) explainGroups(sk, out, spec, groups, { userSpec: opts.userSpec });
41
+ return { ok: issues.length === 0, total: issues.length, ...(spec.goals ? { goals: goals.results } : {}), groups };
42
+ }
@@ -0,0 +1,82 @@
1
+ // QA of held-object keys (C14, ../object-keys.js). From the first to the last key the object
2
+ // leads the hand; the object is drawn in the hand, so what the arm or the wrist could not reach
3
+ // leaves the object off the pose its keys ask for. That residual is measured on the object, as the
4
+ // farthest corner of its part boxes from where the keys put it:
5
+ // REACH "<id>: 6.2 cm off its keys", value metres, limit checks.limits.reach (default 0.01)
6
+ // The hint names the key and why the hand fell short (diag.hold_<side>, compiler/hold.js): the
7
+ // arm out of reach, the grip nearer the shoulder than the elbow folds (compiler/limbs.js), or a
8
+ // wrist turn past its limit after the elbow swung and the forearm turned (lane K8: the whole arm
9
+ // follows the object, compiler/arm-orient.js).
10
+ // Measured, not judged (C16, output.observations.held): every key as drawn, so the agent sees how
11
+ // far each one is from what the hand can hold without a probe.
12
+ import { keyedEntries, curveIn, keyedPose } from "../object-keys.js";
13
+ import { rootPoseAt, partsOf } from "../object-pose.js";
14
+ import { relative } from "../carried.js";
15
+ import { groupsOf } from "../scene.js";
16
+ import { WRIST_LIMITS } from "../compiler/arm-orient.js";
17
+ import { ELBOW_FOLD, ELBOW_STOP } from "../compiler/limbs.js";
18
+ import { add, apply, dist, norm, angle, matFromQuat } from "../compiler/math.js";
19
+
20
+ const SUBJECT = /^(.+): [\d.]+ cm off its keys$/;
21
+ const WRIST_WORDS = { twist: "twist about the forearm", flex: "flex", dev: "deviation (+ toward the thumb)" };
22
+
23
+ /** The corners of id's part boxes in its root frame (the box spans y 0..2 half on its base pivot). */
24
+ function cornersOf(scene, id) {
25
+ const root = scene.objects[id] ?? groupsOf(scene)[id];
26
+ return partsOf(scene, id).flatMap(p => {
27
+ const box = scene.objects[p], rel = relative(root, box), [x, y, z] = box.half;
28
+ return [-x, x].flatMap(a => [0, 2 * y].flatMap(b => [-z, z].map(c => add(rel.pos, apply(rel.R, [a, b, c])))));
29
+ });
30
+ }
31
+
32
+ /** REACH issues of every keyed carry, frames as out.frames indexes. */
33
+ export function keyIssues(out, L, push) {
34
+ const { scene } = out.ctx, f0 = out.f0 ?? 0;
35
+ for (const [id] of keyedEntries(out.ctx.spec)) {
36
+ const curve = curveIn(out, id), corners = cornersOf(scene, id);
37
+ if (!corners.length) continue;
38
+ for (let f = Math.max(f0, Math.ceil(curve.t[0])); f <= Math.min(f0 + out.frames.length - 1, Math.floor(curve.t[curve.t.length - 1])); f++) {
39
+ const want = keyedPose(curve, f), got = rootPoseAt(out, id, f);
40
+ const off = Math.max(...corners.map(c => dist(add(want.pos, apply(want.R, c)), add(got.pos, apply(got.R, c)))));
41
+ if (off > L.reach) push("REACH", `${id}: ${(off * 100).toFixed(1)} cm off its keys`, f - f0, off, L.reach);
42
+ }
43
+ }
44
+ }
45
+
46
+ /** REACH for a keyed object ("<id>: 6.2 cm off its keys") -> { cause, hint }, else null. */
47
+ export function keyHint(X, g) {
48
+ const id = SUBJECT.exec(g.subject)?.[1], o = X.spec?.objects?.[id];
49
+ if (!o?.keys?.length) return null;
50
+ const f = g.worstFrame, k = X.spec.props?.[o.prop]?.hand, h = X.fr(f).diag[`hold_${k}`] ?? {};
51
+ const near = o.keys.reduce((best, key, i) => (Math.abs(key.f - f) < Math.abs(o.keys[best].f - f) ? i : best), 0);
52
+ const wrist = Object.entries(WRIST_LIMITS).filter(([n, [lo, hi]]) => h[n] < lo || h[n] > hi).map(([n, [lo, hi]]) => `the wrist ${WRIST_WORDS[n]} needs ${Math.round(h[n])}deg (limit ${lo}..${hi})`);
53
+ // the arm already swung the elbow and turned the forearm (compiler/arm-orient.js): say so with what the wrist still needs
54
+ if (wrist.length) wrist[wrist.length - 1] += ` after the elbow swung ${Math.round(h.swivel ?? 0)}deg and the forearm turned`;
55
+ // the grip nearer the shoulder than the elbow folds (h.fold, the flexion it asked): out from the shoulder, not nearer the hand
56
+ const fold = h.miss > 0.005 && h.fold, short = fold ? `the grip is ${(h.miss * 100).toFixed(1)} cm inside the forearm's reach (the elbow would fold ${Math.round(h.fold)}deg; it bends to ${ELBOW_FOLD}deg and stops at ${ELBOW_STOP}deg)` : `the arm is ${(h.miss * 100).toFixed(1)} cm short`;
57
+ const why = [...(h.miss > 0.005 ? [short] : []), ...wrist];
58
+ const roll = o.keys[near].aim && o.keys[near].roll === undefined ? "" : "; an aim key without roll takes the roll the arm holds most easily";
59
+ return { cause: `objects.${id}.keys[${near}] f${o.keys[near].f}`,
60
+ hint: `${id} at f${f}: ${why.length ? why.join(", ") : "the hand cannot hold it there"}; ${fold ? "key a pose farther from the shoulder, or turn or move the body away from it" : "key a pose nearer the hand, or bring the body to it"} (root, yaw, spine_yaw)${roll}` };
61
+ }
62
+
63
+ const r1 = v => Math.round(v * 10) / 10, r2 = v => Math.round(v * 100) / 100;
64
+ /** observations.held units: what a row of a keyed object says. */
65
+ export const HELD_UNITS = "held: per key, deg = the drawn +Y off the asked one (aim, or rot's +Y), cm = the drawn base off pos, got = the drawn +Y (when 1 deg off or more), rot = what an aim key used";
66
+
67
+ /**
68
+ * Every key of every keyed carry as drawn: { <id>: [{ f, deg, cm, got?, rot? }] } in key order
69
+ * (frames as the spec writes them), or null when nothing is keyed.
70
+ */
71
+ export function heldReadings(out) {
72
+ const held = keyedEntries(out.ctx.spec);
73
+ if (!held.length) return null;
74
+ return Object.fromEntries(held.map(([id, o]) => {
75
+ const curve = curveIn(out, id), keys = [...o.keys].sort((a, b) => a.f - b.f);
76
+ return [id, keys.map((k, i) => {
77
+ const got = rootPoseAt(out, id, Math.round(curve.t[i])), y = [got.R[0][1], got.R[1][1], got.R[2][1]];
78
+ const want = k.aim ? norm(k.aim) : apply(matFromQuat(curve.q[i]), [0, 1, 0]), deg = r1(angle(want, y));
79
+ return { f: k.f, deg, cm: r1(dist(k.pos, got.pos) * 100), ...(deg >= 1 ? { got: y.map(r2) } : {}), ...(k.aim ? { rot: curve.rots[i] } : {}) };
80
+ })];
81
+ }));
82
+ }