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,228 @@
1
+ // Style quantities: how fast a joint moves, how hard it speeds up, how far the centre of mass
2
+ // leaves the feet and how deep a knee or elbow bends. They depend on what the motion is for (a
3
+ // bat swing moves the wrists at 9 m/s), so QA measures them (output.observations) and does not
4
+ // judge them. A spec that names a style limit in checks.limits asks for that quantity to be a
5
+ // check again: speedCore/speedLimb -> SPEED, accCore/accLimb -> ACC, knee -> KNEE (bend),
6
+ // balance -> BALANCE. A pop is not style: one frame whose acceleration stands out from the
7
+ // frames around it is an artifact (a step key, a pole flip, a snapped anchor), so POP stays a
8
+ // QA error, and so is a FLOAT: the body off the ground without falling (floatChecks). A pop is a
9
+ // spike, not a large value: a hard but continuous acceleration (a swing's launch) is style.
10
+ // See docs/motion-api-v3.1.md C13.
11
+ import { add, sub, scale, len, dot, angle } from "../compiler/math.js";
12
+ import { CAPSULES, segPoints, sdBox } from "./geometry.js";
13
+ import { groundSole, groundY } from "./ground.js";
14
+
15
+ const SPEED_JOINTS = ["Hips", "Head", "LeftHand", "RightHand", "LeftFoot", "RightFoot", "LeftLeg", "RightLeg", "LeftForeArm", "RightForeArm"];
16
+ const CORE = new Set(["Hips", "Head"]);
17
+ const BENDS = [["knee_l", "LeftUpLeg", "LeftLeg", "LeftFoot"], ["knee_r", "RightUpLeg", "RightLeg", "RightFoot"],
18
+ ["elbow_l", "LeftArm", "LeftForeArm", "LeftHand"], ["elbow_r", "RightArm", "RightForeArm", "RightHand"]];
19
+ /** A pop's acceleration is more than this many times the level around it (popLevel). */
20
+ const POP_RATIO = 2;
21
+ /** FLOAT: the share of gravity an airborne run may miss (0.75: the hips fall at a quarter of g). */
22
+ const FLOAT_LIMIT = 0.75;
23
+ /** Peak speed (m/s) under which a joint counts as still and peaks leaves it out. */
24
+ const STILL = 0.05;
25
+ const r2 = x => Math.round(x * 100) / 100;
26
+
27
+ /**
28
+ * Per SPEED_JOINT: speed v[f] (m/frame, from f-1), acceleration a[f] (m/frame^2, from f-2) and
29
+ * its vector A[f]; 0 where undefined.
30
+ */
31
+ function kinematics(sk, out) {
32
+ const F = out.frames, n = F.length;
33
+ return SPEED_JOINTS.map(j => {
34
+ const X = F.map(fr => fr.s.P[sk.index[j]]), v = new Float64Array(n), a = new Float64Array(n), A = X.map(() => [0, 0, 0]);
35
+ for (let f = 1; f < n; f++) v[f] = len(sub(X[f], X[f - 1]));
36
+ for (let f = 2; f < n; f++) { A[f] = sub(sub(X[f], X[f - 1]), sub(X[f - 1], X[f - 2])); a[f] = len(A[f]); }
37
+ return { j, core: CORE.has(j), v, a, A };
38
+ });
39
+ }
40
+
41
+ /**
42
+ * The acceleration level around frame f that a pop is measured against (m/frame^2), the larger of
43
+ * far the largest acceleration 2 and 3 frames away. A one-frame event shows in up to three
44
+ * neighbouring frames (a step key in f and f+1, a one-frame glitch in f-1..f+1), so the
45
+ * frames next to f are not read here;
46
+ * burst what the frames next to f carry in f's own direction, less what the other one turns
47
+ * back. At 24 fps a hard move gathers its acceleration into 2 or 3 frames that all push
48
+ * the same way (a swing's launch, the hips turning round): a burst, not a pop, however
49
+ * hard. A glitch pushes back on both sides (+1, -2, +1), a step on one (+1, -1), and a
50
+ * jitter turns back on one side as the other follows (+, -, -, +): those carry none.
51
+ * k = { a, A } (kinematics).
52
+ */
53
+ function popLevel({ a, A }, f) {
54
+ let far = 0;
55
+ for (const q of [f - 3, f - 2, f + 2, f + 3]) if (q >= 2 && q < a.length) far = Math.max(far, a[q]);
56
+ const along = a[f] > 0 ? [f - 1, f + 1].filter(q => q >= 2 && q < a.length).map(q => dot(A[q], A[f]) / a[f]) : [];
57
+ return along.length ? Math.max(far, Math.max(...along) + Math.min(0, ...along)) : far;
58
+ }
59
+
60
+ /** True when k.a[f] (m/frame^2) is a pop: above floor and POP_RATIO times popLevel(k, f); k = { a, A } (kinematics). */
61
+ export const isPop = (k, f, floor) => k.a[f] > floor && k.a[f] > POP_RATIO * popLevel(k, f);
62
+
63
+ /** Hard POP issues (limits L: accCore, accLimb are the floor); push(kind, msg, f, value, limit). */
64
+ export function popChecks(sk, out, L, push) {
65
+ for (const k of kinematics(sk, out)) {
66
+ const floor = k.core ? L.accCore : L.accLimb;
67
+ for (let f = 2; f < k.a.length; f++) if (isPop(k, f, floor)) push("POP", `${k.j} accel ${(k.a[f] * 100).toFixed(2)} cm/f^2 in one frame`, f, k.a[f], floor);
68
+ }
69
+ }
70
+
71
+ /** Trunk capsules that rest a body on the floor (lying, sitting on it): within this of the floor, the floor carries it. */
72
+ const FLOOR_REST = ["hips", "torso_lo", "torso_hi"], FLOOR_TOL = 0.04;
73
+ /** True when the floor under the trunk or a declared support (checks.supports) carries the body at frame f. */
74
+ function supported(out, f, supports) {
75
+ const { P } = out.frames[f].s, I = out.ctx.sk.index;
76
+ if (CAPSULES.some(([part, a, b, r]) => FLOOR_REST.includes(part) && Math.min(P[I[a]][1], P[I[b]][1]) - r < FLOOR_TOL)) return true;
77
+ return supports.some(sp => {
78
+ const box = out.objectsAt(f)[sp.object], cap = CAPSULES.find(c => c[0] === (sp.part ?? "hips"));
79
+ return box && cap && Math.min(...segPoints(P[I[cap[1]]], P[I[cap[2]]]).map(p => sdBox(p, box))) - cap[3] < (sp.tol ?? 0.04);
80
+ });
81
+ }
82
+
83
+ /**
84
+ * Per frame: null where a declared support carries the body, "air" with no foot on the ground,
85
+ * else the balance margin in metres: how far the centre of mass (hips, spine, head) sits inside
86
+ * the box of the feet on the ground, the floor or an object's top (ground.js; < 0: outside it).
87
+ * While the hips travel every foot counts.
88
+ */
89
+ function balanceMargins(out, supports = []) {
90
+ const sk = out.ctx.sk, I = sk.index, F = out.frames;
91
+ return F.map(({ s }, f) => {
92
+ if (supported(out, f, supports)) return null;
93
+ const { P } = s;
94
+ const moving = f > 0 && f < F.length - 1 && Math.hypot(F[f + 1].s.P[I.Hips][0] - F[f - 1].s.P[I.Hips][0], F[f + 1].s.P[I.Hips][2] - F[f - 1].s.P[I.Hips][2]) / 2 > 0.006;
95
+ const feet = [];
96
+ for (const S of ["Left", "Right"]) {
97
+ const sp = groundSole(out, f, S);
98
+ if (moving || Math.min(sp.heel[1], sp.ball[1]) < 0.02) feet.push(sp.heel, sp.ball, sp.tip);
99
+ }
100
+ if (!feet.length) return "air";
101
+ const com = scale(["Hips", "Spine1", "Spine2", "Head"].reduce((acc, j) => add(acc, P[I[j]]), [0, 0, 0]), 0.25);
102
+ const xs = feet.map(p => p[0]), zs = feet.map(p => p[2]);
103
+ return Math.min(com[0] - Math.min(...xs), Math.max(...xs) - com[0], com[2] - Math.min(...zs), Math.max(...zs) - com[2]);
104
+ });
105
+ }
106
+
107
+ /** True when a hand rests on the ground at frame f: its wrist or knuckles within HAND_DOWN of the ground under them. */
108
+ const HAND_DOWN = 0.06;
109
+ function handDown(out, f) {
110
+ const { P } = out.frames[f].s, I = out.ctx.sk.index;
111
+ return ["Left", "Right"].some(S => [`${S}Hand`, `${S}HandMiddle1`].some(j => I[j] !== undefined && P[I[j]][1] - groundY(out, f, P[I[j]]) < HAND_DOWN));
112
+ }
113
+
114
+ /** Runs [a, b] (indexes of out.frames) with both soles 2 cm or more above the ground (ground.js), no hand on it and no declared support. */
115
+ function airRuns(out, supports = []) {
116
+ const n = out.frames.length, runs = [];
117
+ const air = out.frames.map((_, f) => !supported(out, f, supports)
118
+ && ["Left", "Right"].every(S => { const sp = groundSole(out, f, S); return Math.min(sp.heel[1], sp.ball[1]) >= 0.02; }) && !handDown(out, f));
119
+ for (let a = 0; a < n; a++) {
120
+ if (!air[a]) continue;
121
+ let b = a;
122
+ while (b + 1 < n && air[b + 1]) b++;
123
+ runs.push([a, b]);
124
+ a = b;
125
+ }
126
+ return runs;
127
+ }
128
+
129
+ /**
130
+ * Hard FLOAT issues: 4 or more frames off the ground (airRuns) in which the hips do not fall.
131
+ * A jump or a drop is ballistic: over the run the hips' vertical speed drops by gravity (the
132
+ * speed into the run minus the speed out of it, per frame, is g). A body held up by nothing
133
+ * keeps it; under a quarter of g is a float. value = the share of g missing (1 = no fall).
134
+ */
135
+ export function floatChecks(out, checks = {}) {
136
+ const F = out.frames, n = F.length, I = out.ctx.sk.index, g = 9.81 / (out.fps ?? 24) ** 2, f0 = out.f0 ?? 0, issues = [];
137
+ const y = f => F[Math.max(0, Math.min(n - 1, f))].s.P[I.Hips][1];
138
+ for (const [a, b] of airRuns(out, checks.supports)) {
139
+ const frames = b - a + 1;
140
+ if (frames < 4) continue;
141
+ const vin = a > 0 ? y(a) - y(a - 1) : y(a + 1) - y(a), vout = b < n - 1 ? y(b + 1) - y(b) : y(b) - y(b - 1);
142
+ const missing = 1 - (vin - vout) / frames / g;
143
+ if (missing <= FLOAT_LIMIT) continue;
144
+ for (let f = a; f <= b; f++) issues.push({ kind: "FLOAT", msg: `no foot on the ground f${a + f0}-f${b + f0}, hips fall at ${Math.round(Math.max(0, 1 - missing) * 100)}% of g`, frame: f, value: +missing.toFixed(4), limit: FLOAT_LIMIT });
145
+ }
146
+ return issues;
147
+ }
148
+
149
+ const bendAt = (sk, s, [, a, b, c]) => angle(sub(s.P[sk.index[b]], s.P[sk.index[a]]), sub(s.P[sk.index[c]], s.P[sk.index[b]]));
150
+
151
+ /**
152
+ * The style checks the spec opted into by naming their limit in checks.limits (own keys only);
153
+ * L is QA_LIMITS merged with them. Issues as generalChecks makes them (f indexes out.frames).
154
+ */
155
+ export function styleChecks(sk, out, checks, L) {
156
+ const own = checks?.limits ?? {}, on = k => Object.hasOwn(own, k), issues = [];
157
+ const push = (kind, msg, f, value, limit) => issues.push({ kind, msg, frame: f, value: +value.toFixed(4), limit });
158
+ for (const k of kinematics(sk, out)) {
159
+ const { j, core, v, a } = k, vk = core ? "speedCore" : "speedLimb", ak = core ? "accCore" : "accLimb";
160
+ for (let f = 1; f < v.length; f++) {
161
+ if (on(vk) && v[f] > L[vk]) push("SPEED", `${j} ${(v[f] * 100).toFixed(1)} cm/frame`, f, v[f], L[vk]);
162
+ // a pop is already a POP group
163
+ if (on(ak) && f > 1 && a[f] > L[ak] && !isPop(k, f, L[ak])) push("ACC", `${j} accel ${(a[f] * 100).toFixed(2)} cm/f^2`, f, a[f], L[ak]);
164
+ }
165
+ }
166
+ if (on("knee")) out.frames.forEach(({ s }, f) => {
167
+ for (const bend of BENDS.slice(0, 2)) {
168
+ const deg = bendAt(sk, s, bend);
169
+ if (deg > L.knee) push("KNEE", `${bend[0] === "knee_l" ? "Left" : "Right"} knee bent ${deg.toFixed(0)}deg`, f, deg, L.knee);
170
+ }
171
+ });
172
+ if (on("balance")) balanceMargins(out, checks.supports).forEach((m, f) => {
173
+ if (m === "air") push("BALANCE", "no foot on the ground", f, 0, 0);
174
+ else if (m !== null && -m > L.balance) {
175
+ const com = scale(["Hips", "Spine1", "Spine2", "Head"].reduce((acc, j) => add(acc, out.frames[f].s.P[sk.index[j]]), [0, 0, 0]), 0.25);
176
+ push("BALANCE", `centre of mass ${com[0].toFixed(2)},${com[2].toFixed(2)} outside the feet`, f, -m, L.balance);
177
+ }
178
+ });
179
+ return issues;
180
+ }
181
+
182
+ /**
183
+ * output.observations: measurements, not verdicts. Frames are absolute (out.f0 added).
184
+ * { units, peaks: { <joint>: { speed: [m/s, f], accel: [m/s^2, f] } } (joints that move: STILL),
185
+ * balance: { min: [m, f], outside?: [f0, f1], air?: [[f0, f1], ...] (at most 4 runs with both
186
+ * feet off the ground: the floor or an object's top) }, bend: { knee_l: [deg, f], ... } }
187
+ */
188
+ export function observeMotion(sk, out, checks = {}) {
189
+ const f0 = out.f0 ?? 0, fps = out.fps ?? 24, peak = (arr, k) => {
190
+ let at = 0;
191
+ for (let f = 1; f < arr.length; f++) if (arr[f] > arr[at]) at = f;
192
+ return [Math.round(arr[at] * k * 100) / 100, at + f0];
193
+ };
194
+ const peaks = Object.fromEntries(kinematics(sk, out).map(({ j, v, a }) => [j, { speed: peak(v, fps), accel: peak(a, fps * fps) }]).filter(([, p]) => p.speed[0] >= STILL));
195
+ const margins = balanceMargins(out, checks.supports), span = test => {
196
+ const fs = margins.map((m, f) => (test(m) ? f + f0 : -1)).filter(f => f >= 0);
197
+ return fs.length ? [fs[0], fs[fs.length - 1]] : null;
198
+ }, air = airRuns(out, checks.supports).slice(0, 4).map(([a, b]) => [a + f0, b + f0]);
199
+ let min = null;
200
+ margins.forEach((m, f) => { if (typeof m === "number" && (!min || m < min[0])) min = [r2(m), f + f0]; });
201
+ const outside = span(m => typeof m === "number" && m < 0);
202
+ const bend = Object.fromEntries(BENDS.map(b => {
203
+ let best = [0, f0];
204
+ out.frames.forEach(({ s }, f) => { const d = bendAt(sk, s, b); if (d > best[0]) best = [d, f + f0]; });
205
+ return [b[0], [Math.round(best[0]), best[1]]];
206
+ }));
207
+ return { units: "[value, frame]; speed m/s, accel m/s^2, balance m inside the feet (< 0 outside), bend deg (0 straight)",
208
+ peaks, balance: { min, ...(outside ? { outside } : {}), ...(air.length ? { air } : {}) }, bend };
209
+ }
210
+
211
+ /**
212
+ * Per frame, how many times too fast the motion is by what QA reports (fix/retime.js): a pop
213
+ * against its surroundings (square root: acceleration falls with the square of the stretch) and
214
+ * the speed or acceleration limits the spec opted into. L = QA_LIMITS merged with own.
215
+ */
216
+ export function speedExcess(sk, out, own, L) {
217
+ const on = k => Object.hasOwn(own ?? {}, k), e = new Float64Array(out.frames.length);
218
+ for (const k of kinematics(sk, out)) {
219
+ const { core, v, a } = k, vk = core ? "speedCore" : "speedLimb", ak = core ? "accCore" : "accLimb";
220
+ for (let f = 1; f < v.length; f++) {
221
+ if (on(vk)) e[f] = Math.max(e[f], v[f] / L[vk]);
222
+ if (f < 2) continue;
223
+ if (isPop(k, f, L[ak])) e[f] = Math.max(e[f], Math.sqrt(a[f] / Math.max(L[ak], POP_RATIO * popLevel(k, f))));
224
+ if (on(ak)) e[f] = Math.max(e[f], Math.sqrt(a[f] / L[ak]));
225
+ }
226
+ }
227
+ return e;
228
+ }
@@ -0,0 +1,69 @@
1
+ // QA WRIST (lane wristqa): a holding wrist at its anatomical limit is broken whatever the motion is for
2
+ // (C13 keeps style measured, not judged; a wrist the arm had to clamp is neither). On the frames a hand
3
+ // holds (wrist-obs.js holding: a fit, a keyed carry, a grip or a carry), the wrist's turn on its forearm
4
+ // is read where the arm asked for it: a keyed carry's need before hold.js clamped it (diag.hold_<k>),
5
+ // else the turn the solved pose shows at the joint, against the whole limits: the wtwist channel turns the
6
+ // hand alone (limbs.js wrist, the forearm bone takes no share), and a grip stays inside the comfortable twist.
7
+ // An axis more than AT deg past its limit (arm-orient.js WRIST_LIMITS: the clamp engaged, or a turn no wrist makes) is an issue:
8
+ // WRIST "hand_r flex 72deg (limit 70)", value the degrees asked, limit the limit
9
+ // Past the comfortable range only is not judged: output.observations.wrist counts it.
10
+ // The hint moves the body, not the wrist. Which move frees it depends on the elbow's path the hold plans
11
+ // over the whole carry (hold-plan.js), so no single frame predicts it (a 10 cm hip drop computed to free
12
+ // the wrist at f90 of bench S6 kept it at its limit once compiled; a 20 deg lean freed 3 of its 4
13
+ // frames): repair compiles the leans instead (repair-tune.js WRIST) and keeps the one that lowers the summed
14
+ // severity with every goal held.
15
+ import { WRIST_LIMITS, wristNeed } from "../compiler/arm-orient.js";
16
+ import { palmAxes } from "../compiler/limbs.js";
17
+ import { apply, len, mul, scale, sub, T } from "../compiler/math.js";
18
+ import { holding } from "./wrist-obs.js";
19
+
20
+ const AT = 0.5, HELD = 0.5, AXES = ["flex", "dev", "twist"], SIDES = { l: "Left", r: "Right" };
21
+ const WORDS = { flex: v => (v >= 0 ? "flex" : "extension"), dev: v => (v >= 0 ? "radial deviation" : "ulnar deviation"), twist: () => "twist" };
22
+
23
+ /** The worst axis of a wrist need against its limits: { axis, value, edge, past } (past > AT is an issue). */
24
+ function worstAxis(need) {
25
+ let best = null;
26
+ for (const n of AXES) {
27
+ const [lo, hi] = WRIST_LIMITS[n], v = need[n], edge = v >= 0 ? hi : lo, past = v >= 0 ? v - hi : lo - v;
28
+ if (!best || past > best.past) best = { axis: n, value: v, edge, past };
29
+ }
30
+ return best;
31
+ }
32
+
33
+ /** The wrist need of hand k on frame i: the hold's (before its clamp) when a keyed carry leads it, else the joint's on the solved pose. */
34
+ function needAt(sk, out, k, i) {
35
+ const { s, diag } = out.frames[i], h = diag[`hold_${k}`];
36
+ if ((h?.w ?? 0) > HELD) return h;
37
+ const S = SIDES[k], I = sk.index, hb = I[`${S}Hand`], fa = I[`${S}ForeArm`], rw = sk.restWorldRot;
38
+ const axT = scale(sub(s.P[hb], s.P[fa]), 1 / Math.max(1e-9, len(sub(s.P[hb], s.P[fa])))), neutral = mul(s.R[fa], mul(T(rw[fa]), rw[hb]));
39
+ return wristNeed(neutral, s.R[hb], axT, apply(mul(neutral, T(s.R[hb])), palmAxes(sk, s, S).l));
40
+ }
41
+
42
+ /** WRIST issues of every holding hand ({ kind, msg, frame, value, limit }, frame an out.frames index). */
43
+ export function wristIssues(sk, out) {
44
+ const issues = [], f0 = out.f0 ?? 0;
45
+ for (const k of ["l", "r"]) out.frames.forEach((_, i) => {
46
+ if (!holding(out, k, i, i + f0)) return;
47
+ const w = worstAxis(needAt(sk, out, k, i));
48
+ if (w.past <= AT) return;
49
+ const lim = Math.abs(w.edge);
50
+ issues.push({ kind: "WRIST", msg: `hand_${k} ${WORDS[w.axis](w.value)} ${Math.round(Math.abs(w.value))}deg (limit ${Math.round(lim)})`, frame: i, value: +Math.abs(w.value).toFixed(2), limit: lim });
51
+ });
52
+ return issues;
53
+ }
54
+
55
+ /** WRIST group -> { cause, hint }: what bends the wrist (fix.self's elbow turn, or where the body stands), and the body moves repair tries (repair-tune.js WRIST). */
56
+ function wristHint(X, g) {
57
+ const m = /^hand_([lr]) /.exec(g.subject), k = m?.[1];
58
+ if (!k) return null;
59
+ const f = g.worstFrame, where = `at f${f}${g.count > 1 ? ` (f${g.first}-f${g.last}, ${g.count} frames)` : ""}`;
60
+ // fix.self turned the elbow about the shoulder-wrist line and the hand kept its turn: the wrist took the turn
61
+ const turn = Math.round(X.out.ctx?.swivel?.[k]?.get(f) ?? 0);
62
+ if (Math.abs(turn) >= 1) return { cause: `fix.self elbow turn ${turn}deg at f${f}`, hint: `${g.subject} ${where}: fix.self turned the elbow ${turn} deg to keep the arm out of the body and the hand kept its turn, so the wrist took it; key elbow_${k} (the elbow pole) or the hand's target so the arm clears the body without the turn` };
63
+ const held = (X.out.frames[X.rel(f)]?.diag[`hold_${k}`]?.w ?? 0) > HELD && Object.keys(X.spec.objects ?? {}).find(id => X.spec.props?.[X.spec.objects[id]?.prop]?.hand === k);
64
+ const lean = X.fr(f).c.spine_pitch ?? 0;
65
+ return { cause: held ? `objects.${held}.keys near f${f}` : X.cause(`armw_${k}`, f),
66
+ hint: `${g.subject} ${where}: the arm cannot turn the hand that far from where the body stands; lean the upper body over the hand (spine_pitch ${Math.round(lean)} at f${f}: try ${Math.round(lean + 20)}) or bring the body to it (root), ${held ? `or key ${held} so the hand need not bend that far (its aim or pos)` : "or move what the hand reaches"}; repair: { rounds } tries leans of 10-30 deg here and keeps one that helps` };
67
+ }
68
+
69
+ export const wristHints = { WRIST: wristHint };
@@ -0,0 +1,60 @@
1
+ // Measured, not judged (output.observations.wrist, lane wrist): how far each wrist bends while its hand
2
+ // holds something. A hand holds on the frames a fit's weight is on (reachGrip, any fit), a keyed carry
3
+ // leads it (diag.hold_<k>), a grip turns it (diag.grip_<k>) or it carries a body. On those frames the
4
+ // hand's turn relative to its forearm is read the way compiler/hold.js turns it (arm-orient.js
5
+ // wristNeed): flex (+ toward the palm), deviation (+ toward the thumb, radial; - ulnar) and the twist the
6
+ // wrist joint shows (the forearm bone takes FOREARM_SHARE of a turn, so the joint's comfortable twist
7
+ // is that share of the forearm's). Frames past the comfortable ranges (arm-orient.js WRIST_COMFORT) and
8
+ // at the anatomical limits (WRIST_LIMITS) are counted.
9
+ // Why: a hand drawn by keys bent 70 deg (the limit) with 33 deg of ulnar deviation for frames while QA
10
+ // stayed clean (bench S6), and no answer said so. The shoulder too (lane elbow): a cup held at the mouth with
11
+ // the upper arm 130-160 deg up, the elbow 20 cm over the shoulder, in most bench cup takes.
12
+ import { wristNeed, WRIST_LIMITS, WRIST_COMFORT, FOREARM_SHARE } from "../compiler/arm-orient.js";
13
+ import { palmAxes } from "../compiler/limbs.js";
14
+ import { apply, len, mul, scale, sub, T } from "../compiler/math.js";
15
+ import { handOf } from "../object-pose.js";
16
+
17
+ const HOLD = 0.5, AT = 1, LEVEL = 90;
18
+ const JOINT = { twist: 1 - FOREARM_SHARE, flex: 1, dev: 1 };
19
+ const range = (k, table) => table[k].map(v => v * JOINT[k]);
20
+
21
+ export const WRIST_UNITS = "wrist: deg on the frames a hand holds (a fit, a carry or a grip on); flex + toward the palm, dev + radial / - ulnar, twist at the wrist joint; [min, f, max, f], frames past the comfortable range and at the limit; grip: the grip a keyed carry of a thin body keeps (grip-choice.js), cost [it, today's] (comfort-priced wrist, reach and body over the keys); elevation [max deg, f] of the upper arm (90 level), raised: frames the elbow is over the shoulder";
22
+
23
+ /** True when the hand of side k holds something on frame i of out (absolute frame f); qa/wrist-checks.js reads it too. */
24
+ export function holding(out, k, i, f) {
25
+ const { diag } = out.frames[i], baked = out.baked ?? {};
26
+ if ((diag[`hold_${k}`]?.w ?? 0) > HOLD || (diag[`grip_${k}`]?.w ?? 0) > HOLD) return true;
27
+ for (const fit of out.ctx.spec.fits ?? []) if (fit.side === k && (baked[`w_${fit.anchor}_${k}`]?.[f] ?? 0) > HOLD) return true;
28
+ return Object.keys(out.ctx.spec.objects ?? {}).some(id => out.ctx.spec.objects[id]?.prop && handOf(out, id, f) === k);
29
+ }
30
+
31
+ /** observeWrist(sk, out) -> { hand_l?, hand_r? } or null when no hand holds anything. Frames are absolute. */
32
+ export function observeWrist(sk, out) {
33
+ const I = sk.index, rw = sk.restWorldRot, f0 = out.f0 ?? 0, res = {};
34
+ for (const [S, k] of [["Left", "l"], ["Right", "r"]]) {
35
+ const h = I[`${S}Hand`], fa = I[`${S}ForeArm`], a = I[`${S}Arm`], row = { frames: 0, past: 0, limit: 0, raised: 0 };
36
+ out.frames.forEach(({ s }, i) => {
37
+ const f = i + f0;
38
+ if (!holding(out, k, i, f)) return;
39
+ const axT = scale(sub(s.P[h], s.P[fa]), 1 / Math.max(1e-9, len(sub(s.P[h], s.P[fa])))), neutral = mul(s.R[fa], mul(T(rw[fa]), rw[h]));
40
+ const need = wristNeed(neutral, s.R[h], axT, apply(mul(neutral, T(s.R[h])), palmAxes(sk, s, S).l));
41
+ row.frames++;
42
+ for (const n of ["flex", "dev", "twist"]) {
43
+ const v = Math.round(need[n]), cur = row[n];
44
+ row[n] = cur ? [Math.min(cur[0], v), v < cur[0] ? f : cur[1], Math.max(cur[2], v), v > cur[2] ? f : cur[3]] : [v, f, v, f];
45
+ }
46
+ // past the comfortable range by more than a degree; at a limit within a degree of it
47
+ const outside = (table, by) => ["flex", "dev", "twist"].some(n => { const [lo, hi] = range(n, table); return need[n] < lo + by || need[n] > hi - by; });
48
+ if (outside(WRIST_COMFORT, -AT)) row.past++;
49
+ if (outside(WRIST_LIMITS, AT)) row.limit++;
50
+ // the shoulder (lane elbow): the upper arm's elevation from hanging down, and the frames the elbow is over the shoulder's level
51
+ const up = sub(s.P[fa], s.P[a]), el = Math.round((Math.acos(Math.max(-1, Math.min(1, -up[1] / Math.max(1e-9, len(up))))) * 180) / Math.PI);
52
+ if (!row.elevation || el > row.elevation[0]) row.elevation = [el, f];
53
+ if (el > LEVEL) row.raised++;
54
+ });
55
+ if (row.frames) res[`hand_${k}`] = row;
56
+ }
57
+ // the grip a keyed carry chose for where it goes (grip-choice.js): what the hand of that carry holds
58
+ for (const g of out.ctx.hold?.grips?.() ?? []) if (res[`hand_${g.hand}`]) res[`hand_${g.hand}`].grip = { object: g.id, chose: g.name, cost: [g.cost, g.today] };
59
+ return Object.keys(res).length ? res : null;
60
+ }
@@ -0,0 +1,129 @@
1
+ // C2. Point references, the one grammar goals, probes and the timeline use (spec.checks keep
2
+ // their older joint/prop/anchor subset, validate-common.js refOk):
3
+ // joint:<Bone> | bone:<Bone> the bone's joint (the two are the same; names go through bones.js)
4
+ // prop:<prop>.<point> a point of a held prop (spec.props; "origin" always exists)
5
+ // anchor:<name> a spec anchor, resolved as the compiler resolves it
6
+ // landmark:mouth the measured face landmark
7
+ // person:<id>.<Bone> another character's joint where its installed take has it (people.js)
8
+ // object:<id>[.center|.top|.base] a body or a group (object-pose.js objectBoxesAt): the centre,
9
+ // top-face centre or bottom-face centre of the AABB of its
10
+ // part boxes' corners (default center)
11
+ // Frames are absolute; a frame outside the compiled window (out.f0 ..) answers null.
12
+ import { REF_KINDS } from "./schema/fields.js";
13
+ import { resolveBone } from "./bones.js";
14
+ import { objectBoxesAt, partsOf } from "./object-pose.js";
15
+ import { objectOk } from "./compiler/validate-common.js";
16
+ import { resolveAnchor } from "./compiler/anchors.js";
17
+ import { bonePoint } from "./compiler/skeleton.js";
18
+ import { didYouMean } from "./compiler/vocab.js";
19
+ import { add, apply, sub, len } from "./compiler/math.js";
20
+ import { sdBox } from "./qa/geometry.js";
21
+ import { personJoint, peopleList } from "./people.js";
22
+
23
+ const SYNTAX = { joint: "joint:<Bone>", bone: "bone:<Bone>", prop: "prop:<prop>.<point>", anchor: "anchor:<name>", landmark: "landmark:mouth", object: "object:<id>[.center|.top|.base]", person: "person:<id>.<Bone>" };
24
+
25
+ function parse(ref) {
26
+ const m = /^([a-z]+):(.+)$/s.exec(String(ref));
27
+ if (!m) return null;
28
+ const [, kind, rest] = m;
29
+ if (kind === "prop") { const [name, point] = rest.split("."); return { kind, name, point: point ?? "" }; }
30
+ if (kind === "object") { const o = /^(.+)\.(center|top|base)$/s.exec(rest); return { kind, name: o ? o[1] : rest, point: o ? o[2] : null }; }
31
+ if (kind === "person") { const dot = rest.lastIndexOf("."); return { kind, name: dot > 0 ? rest.slice(0, dot) : rest, point: dot > 0 ? rest.slice(dot + 1) : "" }; }
32
+ return { kind, name: rest, point: null };
33
+ }
34
+
35
+ function boxesPoint(boxes, which) {
36
+ const lo = [Infinity, Infinity, Infinity], hi = [-Infinity, -Infinity, -Infinity];
37
+ for (const b of boxes) for (const x of [-1, 1]) for (const y of [0, 2]) for (const z of [-1, 1]) {
38
+ const p = add(b.pos, apply(b.R, [x * b.half[0], y * b.half[1], z * b.half[2]]));
39
+ for (let i = 0; i < 3; i++) { lo[i] = Math.min(lo[i], p[i]); hi[i] = Math.max(hi[i], p[i]); }
40
+ }
41
+ const c = [(lo[0] + hi[0]) / 2, (lo[1] + hi[1]) / 2, (lo[2] + hi[2]) / 2];
42
+ return which === "top" ? [c[0], hi[1], c[2]] : which === "base" ? [c[0], lo[1], c[2]] : c;
43
+ }
44
+
45
+ /** World point [x, y, z] of `ref` at absolute frame f of compile result out, or null. */
46
+ export function refPoint(out, f, ref) {
47
+ const fr = out.frames?.[f - (out.f0 ?? 0)], r = parse(ref);
48
+ if (!fr || !r) return null;
49
+ const { s, diag, c } = fr, sk = out.ctx.sk;
50
+ switch (r.kind) {
51
+ case "joint": case "bone": { const { bone } = resolveBone(sk, r.name); return bone ? s.P[sk.index[bone]].slice() : null; }
52
+ case "prop": { const p = diag.props?.[r.name]?.[r.point]; return Array.isArray(p) ? p.slice() : null; }
53
+ case "anchor": return out.ctx.spec.anchors?.[r.name] ? resolveAnchor(out.ctx, r.name, s, { ...diag, frame: f }, c) : null;
54
+ case "landmark": return sk.landmarks?.[r.name] ? bonePoint(s, sk.index.Head, sk.landmarks[r.name]) : null;
55
+ case "object": { const boxes = objectBoxesAt(out, r.name, f); return boxes ? boxesPoint(boxes, r.point) : null; }
56
+ case "person": return personJoint(out.ctx.scene, r.name, r.point, f);
57
+ default: return null;
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Metres between refs a and b at absolute frame f, or null when either is unknown there. A bare
63
+ * object:<id> (no .center/.top/.base) means "to the object's surface": the distance from the
64
+ * other point to the nearest part box (sdBox, 0 inside); two bare objects measure a's centre to
65
+ * b's surface. Any other pair is the distance between the two points.
66
+ */
67
+ export function refDistance(out, f, a, b) {
68
+ const bare = ref => { const r = parse(ref); return r?.kind === "object" && r.point === null ? r : null; };
69
+ const surf = bare(b) ? [a, bare(b)] : bare(a) ? [b, bare(a)] : null;
70
+ if (surf) {
71
+ const p = refPoint(out, f, surf[0]), boxes = objectBoxesAt(out, surf[1].name, f);
72
+ return p && boxes ? Math.max(0, Math.min(...boxes.map(box => sdBox(p, box)))) : null;
73
+ }
74
+ const p = refPoint(out, f, a), q = refPoint(out, f, b);
75
+ return p && q ? len(sub(p, q)) : null;
76
+ }
77
+
78
+ /**
79
+ * The prefixed refs a bare name could mean in this spec, rig and scene (`; did you mean
80
+ * "object:empty-2"?`), or "". A field that takes more than one ref kind stays strict.
81
+ */
82
+ function bareHint(spec, { sk, scene }, ref, kinds) {
83
+ if (typeof ref !== "string" || !ref) return "";
84
+ const id = ref.replace(/\.(center|top|base)$/, ""), [prop, point] = ref.split(".");
85
+ const bone = sk && (kinds.includes("joint") || kinds.includes("bone")) ? resolveBone(sk, ref).bone : null;
86
+ const known = [
87
+ kinds.includes("object") && (scene?.objects?.[id] || partsOf(scene, id).length) && `object:${ref}`,
88
+ bone && `${kinds.includes("joint") ? "joint" : "bone"}:${bone}`,
89
+ kinds.includes("anchor") && spec.anchors?.[ref] && `anchor:${ref}`,
90
+ kinds.includes("prop") && spec.props?.[prop] && (point === "origin" || spec.props[prop].points?.[point]) && `prop:${ref}`,
91
+ kinds.includes("landmark") && ref === "mouth" && "landmark:mouth",
92
+ ].filter(Boolean);
93
+ return known.length ? `; did you mean ${known.map(k => `"${k}"`).join(" or ")}?` : "";
94
+ }
95
+
96
+ /**
97
+ * Validates ref against the spec (props, anchors), the rig (bones) and the scene (objects and
98
+ * groups). Reports one exact message through err, prefixed `${at} "${ref}"`, and answers whether
99
+ * the ref is good. kinds limits the grammar (default all of REF_KINDS). A bare name (no kind:)
100
+ * is refused with the forms it could mean (bareHint).
101
+ */
102
+ export function checkRef(spec, err, { sk, scene }, at, ref, kinds = REF_KINDS) {
103
+ const r = parse(ref), tag = `${at} "${ref}"`;
104
+ if (!r || !kinds.includes(r.kind)) { err(`${tag} must be ${kinds.map(k => SYNTAX[k]).join(", ")}${r ? "" : bareHint(spec, { sk, scene }, ref, kinds)}`); return false; }
105
+ if (r.kind === "joint" || r.kind === "bone") {
106
+ const { bone, hint } = sk ? resolveBone(sk, r.name) : { bone: r.name };
107
+ if (!bone) err(`${tag}: unknown bone${hint}`);
108
+ return Boolean(bone);
109
+ }
110
+ if (r.kind === "prop") {
111
+ const ok = Boolean(spec.props?.[r.name] && (r.point === "origin" || spec.props[r.name].points?.[r.point]));
112
+ if (!ok) err(`${tag}: spec.props has no point ${r.name}.${r.point}`);
113
+ return ok;
114
+ }
115
+ if (r.kind === "anchor") {
116
+ if (!spec.anchors?.[r.name]) err(`${tag}: no anchor "${r.name}" in spec.anchors${didYouMean(r.name, Object.keys(spec.anchors ?? {}))}`);
117
+ return Boolean(spec.anchors?.[r.name]);
118
+ }
119
+ if (r.kind === "landmark") {
120
+ if (r.name !== "mouth") err(`${tag}: the only landmark is "mouth"`);
121
+ return r.name === "mouth";
122
+ }
123
+ if (r.kind === "person") {
124
+ const ok = Boolean(personJoint(scene, r.name, r.point, 0));
125
+ if (!ok) err(`${tag}: no person "${r.name}" with joint "${r.point}" (people with an installed take: ${peopleList(scene)})`);
126
+ return ok;
127
+ }
128
+ return objectOk(err, scene, tag, r.name, { groups: true });
129
+ }
@@ -0,0 +1,121 @@
1
+ // C6. Repair's second move: a QA group just past its limit that carries no patch. Its cause names
2
+ // what made it, and a skill arg or a key time maps to the geometry too indirectly for a hint to
3
+ // predict (in the cup take, firstStep -1 cm cleared a thigh hit but a 5.04 cm/f^2 pop appeared;
4
+ // step did nothing). A compile is cheap, so repair tries a few small changes instead and keeps
5
+ // the best (repair.js), like an automatic { range } that stays near the agent's value:
6
+ // cause "... use[i] <skill> ..." each arg in SKILL_INFO[skill].tune, 1 then 2 steps
7
+ // either way, from the arg as sent or its default
8
+ // SPEED/ACC/POP "between keys[a] fA and keys[b] fB" (the agent's keys)
9
+ // key a 1 or 2 frames earlier, key b later
10
+ // GOAL up to 10 cm short (with or without a patch): where the body stands at the worst frame,
11
+ // 1 then 2 steps either way: the agent's root keys
12
+ // there (x, z), else the last standIdle/walkTo before
13
+ // it (SKILL_INFO[skill].place); then the lean, the
14
+ // spine_pitch of the agent's keys around it (5 deg)
15
+ // WRIST (qa/wrist-checks.js) at the worst frame: a lean there (a spine_pitch key 10, 20 or 30 deg
16
+ // forward of the lean before it, or 10 back, eased back to it 12
17
+ // frames either side when no lean key is nearer), then the lean
18
+ // keys around it 5 or 10 deg either way; which move frees
19
+ // a held wrist depends on the elbow path the hold plans, so it is
20
+ // compiled (on bench S6 the body's floor steps only made it worse)
21
+ // ; none when fix.self's elbow turn bent it (cause "fix.self ..."): a lean
22
+ // does not undo the turn (car exit: 21 compiles, 20 s, 57 -> 51 deg)
23
+ // Every candidate is a JSON Patch on the agent's spec; the order is fixed, so the search is
24
+ // deterministic. Least-change candidates come first, so a tie keeps the smaller edit.
25
+ import { excessOf } from "./qa/severity.js";
26
+ import { SKILL_INFO } from "./skills/info.js";
27
+
28
+ const NEAR = 0.5, STEPS = [-1, 1, -2, 2], PLACE_MAX = 0.1, LEAN = 5;
29
+ const isNum = x => typeof x === "number" && Number.isFinite(x);
30
+
31
+ /** A group no more than half its limit (or 5 mm, for small limits) past it. */
32
+ const nearLimit = g => excessOf(g) <= NEAR * Math.max(Math.abs(g.limit), 0.01);
33
+
34
+ /** use[i] <skill>: one patch per tune arg and step, smallest steps first. */
35
+ function skillCandidates(cause, spec) {
36
+ const m = /\buse\[(\d+)\] (\w+)/.exec(cause), i = Number(m?.[1]), u = m && spec.use?.[i];
37
+ const info = m && u?.skill === m[2] ? SKILL_INFO[u.skill] : null;
38
+ if (!info?.tune) return [];
39
+ const out = [];
40
+ for (const n of [1, 2]) for (const [arg, step] of Object.entries(info.tune)) {
41
+ const schema = info.args[arg] ?? {}, now = u.args?.[arg] ?? schema.default;
42
+ if (!isNum(now)) continue;
43
+ for (const sg of STEPS.filter(s => Math.abs(s) === n)) {
44
+ const raw = now + sg * step, value = schema.type === "integer" ? Math.round(raw) : Math.round(raw * 1e4) / 1e4;
45
+ if (!(value > 0) || value === now) continue;
46
+ out.push(u.args ? [{ op: u.args[arg] === undefined ? "add" : "replace", path: `/use/${i}/args/${arg}`, value }]
47
+ : [{ op: "add", path: `/use/${i}/args`, value: { [arg]: value } }]);
48
+ }
49
+ }
50
+ return out;
51
+ }
52
+
53
+ /** SPEED/ACC/POP between two of the agent's keys: more frames between them, 1 then 2. */
54
+ function keyCandidates(g, cause, spec) {
55
+ if (!["SPEED", "ACC", "POP"].includes(g.kind)) return [];
56
+ const m = /between keys\[(\d+)\] f(\d+) and keys\[(\d+)\] f(\d+)/.exec(cause);
57
+ if (!m) return [];
58
+ const [a, fa, b, fb] = m.slice(1).map(Number), keys = spec.keys ?? [];
59
+ if (keys[a]?.f !== fa || keys[b]?.f !== fb) return [];
60
+ const last = Number.isInteger(spec.frames) ? spec.frames - 1 : Infinity, out = [];
61
+ for (const n of [1, 2]) {
62
+ if (fa - n >= 0) out.push([{ op: "replace", path: `/keys/${a}/f`, value: fa - n }]);
63
+ if (fb + n <= last) out.push([{ op: "replace", path: `/keys/${b}/f`, value: fb + n }]);
64
+ }
65
+ return out;
66
+ }
67
+
68
+ const round4 = v => Math.round(v * 1e4) / 1e4;
69
+
70
+ /** One step (n = 1 or 2, either way) of what places the body at frame f, then of the lean. */
71
+ function placeSteps(spec, f, n) {
72
+ const keys = spec.keys ?? [], out = [];
73
+ const skill = (spec.use ?? []).map((u, i) => ({ u, i })).filter(({ u }) => (u?.skill === "standIdle" || u?.skill === "walkTo") && u.f0 <= f).sort((a, b) => a.u.f0 - b.u.f0).at(-1);
74
+ const roots = keys.map((k, j) => ({ k, j })).filter(({ k }) => Array.isArray(k?.set?.root) && k.set.root.length === 3 && isNum(k.f) && k.f > (skill?.u.f0 ?? -1));
75
+ if (roots.some(({ k }) => k.f <= f)) {
76
+ for (const axis of [0, 2]) for (const sg of [-n, n]) out.push(roots.map(({ k, j }) => ({ op: "replace", path: `/keys/${j}/set/root`, value: k.set.root.map((v, a) => (a === axis ? round4(v + sg * 0.03) : v)) })));
77
+ } else if (skill) {
78
+ const { u, i } = skill, place = SKILL_INFO[u.skill].place ?? {}, steps = u.args?.steps ?? 8;
79
+ for (const [arg, step] of Object.entries(place)) {
80
+ if (arg === "step" && steps <= 2) continue;
81
+ const now = u.args?.[arg] ?? SKILL_INFO[u.skill].args[arg]?.default;
82
+ const set = value => [u.args ? { op: u.args[arg] === undefined ? "add" : "replace", path: `/use/${i}/args/${arg}`, value } : { op: "add", path: `/use/${i}/args`, value: { [arg]: value } }];
83
+ if (Array.isArray(now)) for (const axis of [0, now.length - 1]) for (const sg of [-n, n]) out.push(set(now.map((v, a) => (a === axis ? round4(v + sg * step) : v))));
84
+ else if (isNum(now)) for (const sg of [-n, n]) if (now + sg * step > 0.05) out.push(set(round4(now + sg * step)));
85
+ }
86
+ }
87
+ const pitched = keys.map((k, j) => ({ f: k?.f, j })).filter(({ j }) => isNum(keys[j]?.set?.spine_pitch) && isNum(keys[j].f)).sort((a, b) => a.f - b.f);
88
+ const lean = [pitched.filter(p => p.f <= f).at(-1), pitched.find(p => p.f > f)].filter(Boolean).map(p => p.j);
89
+ for (const sg of lean.length ? [n, -n] : []) out.push(lean.map(j => ({ op: "replace", path: `/keys/${j}/set/spine_pitch`, value: keys[j].set.spine_pitch + sg * LEAN })));
90
+ return out;
91
+ }
92
+
93
+ const LEANS = [10, 20, 30, -10], EASE = 12;
94
+
95
+ /** WRIST: a spine_pitch key at f that leans the upper body by d from the lean before it, eased back over EASE frames where no lean key is nearer. */
96
+ function leanSteps(spec, f) {
97
+ const keys = spec.keys ?? [], at = Math.round(f), last = Number.isInteger(spec.frames) ? spec.frames - 1 : Infinity;
98
+ const pitched = keys.filter(k => isNum(k?.f) && isNum(k?.set?.spine_pitch)).sort((a, b) => a.f - b.f);
99
+ const prev = pitched.filter(k => k.f < at).at(-1), next = pitched.find(k => k.f > at), base = prev?.set.spine_pitch ?? 0;
100
+ const same = keys.findIndex(k => k?.f === at && k.set && typeof k.set === "object");
101
+ return LEANS.map(d => {
102
+ const value = base + d, ops = [same >= 0 ? { op: keys[same].set.spine_pitch === undefined ? "add" : "replace", path: `/keys/${same}/set/spine_pitch`, value } : { op: "add", path: "/keys/-", value: { f: at, set: { spine_pitch: value } } }];
103
+ if ((!prev || at - prev.f > EASE) && at - EASE >= 0) ops.push({ op: "add", path: "/keys/-", value: { f: at - EASE, set: { spine_pitch: base } } });
104
+ if ((!next || next.f - at > EASE) && at + EASE <= last) ops.push({ op: "add", path: "/keys/-", value: { f: at + EASE, set: { spine_pitch: next?.set.spine_pitch ?? base } } });
105
+ return ops;
106
+ });
107
+ }
108
+
109
+ /**
110
+ * tuneCandidates(g, spec) -> patch[] (each a JSON Patch on spec; [] when the group is not near its
111
+ * limit, already carries a patch, or its cause names nothing tunable; a GOAL group up to 10 cm
112
+ * short also gets the body placement steps)
113
+ */
114
+ export function tuneCandidates(g, spec) {
115
+ if (typeof g.cause !== "string") return [];
116
+ if (g.kind === "WRIST" && g.cause.startsWith("fix.self")) return [];
117
+ if (g.kind === "WRIST" && isNum(g.worstFrame)) return [...leanSteps(spec, g.worstFrame), ...[1, 2].flatMap(n => placeSteps(spec, g.worstFrame, n)).filter(p => p.every(op => op.path.endsWith("/spine_pitch")))];
118
+ const near = !g.patch && nearLimit(g) ? [...skillCandidates(g.cause, spec), ...keyCandidates(g, g.cause, spec)] : [];
119
+ if (g.kind !== "GOAL" || !isNum(g.worstFrame) || excessOf(g) > PLACE_MAX) return near;
120
+ return [...near, ...placeSteps(spec, g.worstFrame, 1), ...placeSteps(spec, g.worstFrame, 2)];
121
+ }