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,102 @@
1
+ // One compile as motion.compile runs it: frames "auto" resolves (skills/frames-auto.js), open
2
+ // parameters are searched (search.js), unnamed checks get their index as a name (on a copy,
3
+ // qa/check-names.js), skills expand (skills/), the spec is validated strictly (leniently for a
4
+ // saved take compiled again), the compiler bakes and auto-fixes the frames, QA measures them with
5
+ // a cause and a hint per issue group against the agent's own spec, the style quantities are
6
+ // measured (qa/style.js observeMotion; keyed objects: every key as drawn, qa/key-checks.js
7
+ // heldReadings; each hand and foot against the nearest object, qa/near.js observeNear; each holding
8
+ // wrist's bend, qa/wrist-obs.js observeWrist; a skill
9
+ // paced to fit a short span: observations.paced; a skill whose release or
10
+ // settle the last frame cut: observations.cut), and the timeline and the
11
+ // requested probes read the result.
12
+ // liftRequest first takes what the agent obviously meant: spec.probes are request probes, and a
13
+ // bare goals[].moved id is object:<id>; relaxSpec (lenient.js) fixes mechanical slips in the expanded
14
+ // spec before validation and the answer lists each fix in notes.
15
+ import { compileMotion } from "./compiler/compile.js";
16
+ import { validateSpec } from "./compiler/validate.js";
17
+ import { runMotionQa } from "./qa/index.js";
18
+ import { observeMotion } from "./qa/style.js";
19
+ import { nameChecks } from "./qa/check-names.js";
20
+ import { expandUse } from "./skills/index.js";
21
+ import { resolveFrames } from "./skills/frames-auto.js";
22
+ import { markFramesAuto } from "./spec-store.js";
23
+ import { findParams, searchParams } from "./search.js";
24
+ import { timelineOf } from "./timeline.js";
25
+ import { runProbes } from "./probes.js";
26
+ import { canonicalGoals } from "./compiler/validate-goals.js";
27
+ import { heldReadings, HELD_UNITS } from "./qa/key-checks.js";
28
+ import { observeNear, NEAR_UNITS } from "./qa/near.js";
29
+ import { observeWrist, WRIST_UNITS } from "./qa/wrist-obs.js";
30
+ import { planWalks, applyWalkPlans } from "./walk-plan.js";
31
+ import { relaxSpec } from "./lenient.js";
32
+
33
+ const ms = t => Math.round(performance.now() - t);
34
+
35
+ /** A spec after retime: frame-range checks follow the keys they were written against. */
36
+ function warpChecks(spec, map) {
37
+ const c = structuredClone(spec.checks ?? {}), w = r => (Array.isArray(r) ? [Math.round(map(r[0])), Math.round(map(r[1]))] : r);
38
+ for (const name of ["contacts", "orient", "below"]) for (const k of c[name] ?? []) k.when = w(k.when);
39
+ for (const k of c.clearance ?? []) k.frames = w(k.frames);
40
+ return { ...spec, checks: c };
41
+ }
42
+
43
+ /**
44
+ * liftRequest(spec, probes) -> { spec, probes }. spec.probes (sent inside the spec, or added by
45
+ * a patch) join the request's probes after them and leave the spec, so they are never stored and
46
+ * never change its specId; bare goals[].moved ids get their object: prefix (validate-goals.js).
47
+ * The spec comes back as the same object when nothing changes.
48
+ */
49
+ export function liftRequest(spec, probes) {
50
+ if (!spec || typeof spec !== "object" || Array.isArray(spec)) return { spec, probes };
51
+ const goals = canonicalGoals(spec.goals);
52
+ if (spec.probes === undefined && goals === spec.goals) return { spec, probes };
53
+ const { probes: inSpec, ...rest } = spec;
54
+ const lifted = inSpec === undefined ? probes : probes ? [...probes, ...(Array.isArray(inSpec) ? inSpec : [inSpec])] : inSpec;
55
+ return { spec: goals === spec.goals ? rest : { ...rest, goals }, probes: lifted };
56
+ }
57
+
58
+ /**
59
+ * spec -> { out, qa, observations, spec (frames and open values filled in), expanded, used,
60
+ * fixed, search, timeline, probes?, notes?, ms }. opts.checks replaces every check (the spec's and its skills') for a
61
+ * re-measure; opts.lenient ignores unknown fields (take-source.js); opts.probes are measured
62
+ * (with spec.probes, liftRequest).
63
+ */
64
+ export function compileSpec(sent, { sk, scene }, opts = {}) {
65
+ const { spec, probes: asked } = liftRequest(sent, opts.probes);
66
+ const t0 = performance.now(), ctx = { sk, scene, lenient: opts.lenient === true }, notes = [];
67
+ const checks = opts.checks && nameChecks({ checks: opts.checks }).checks;
68
+ let plans = [];
69
+ const expand = s => {
70
+ const { spec: exp, used } = expandUse(nameChecks(applyWalkPlans(s, plans)), ctx);
71
+ // plain JSON objects compile about twice as fast as structuredClone'd ones (V8 shapes)
72
+ const full = relaxSpec(JSON.parse(JSON.stringify(checks ? { ...exp, checks } : exp)), scene, notes);
73
+ validateSpec(full, ctx);
74
+ return { full, used };
75
+ };
76
+ // a walkTo with arrive gets its cadence planned on probe compiles of the walk alone (walk-plan.js); the spec keeps arrive
77
+ plans = planWalks(spec, { sk, scene, trial: s => { const full = expand(s).full; return runMotionQa(sk, compileMotion({ fps: 24, ...full }, ctx), full, { explain: false }); } });
78
+ const framed = spec?.frames === "auto" && plans.length ? { ...spec, frames: resolveFrames(applyWalkPlans(spec, plans), ctx).frames } : resolveFrames(spec, ctx), params = findParams(framed);
79
+ let resolved = framed, search = null;
80
+ if (params.length) ({ spec: resolved, search } = searchParams(framed, params, ctx, s => expand(s).full));
81
+ // the store keeps the "auto" intent beside the resolved number (spec-store.js)
82
+ if (spec?.frames === "auto") markFramesAuto(resolved);
83
+ const t1 = performance.now();
84
+ const { full: expanded, used } = expand(resolved);
85
+ const out = compileMotion({ fps: 24, ...expanded }, ctx);
86
+ const compileMs = ms(t1), t2 = performance.now();
87
+ const measured = out.timeMap ? warpChecks(expanded, out.timeMap) : expanded;
88
+ const qa = runMotionQa(sk, out, measured, { userSpec: resolved }), observations = observeMotion(sk, out, measured.checks), held = heldReadings(out);
89
+ if (held) Object.assign(observations, { units: `${observations.units}; ${HELD_UNITS}`, held });
90
+ const near = observeNear(sk, out), wrist = observeWrist(sk, out);
91
+ if (near) Object.assign(observations, { units: `${observations.units}; ${NEAR_UNITS}`, near });
92
+ if (wrist) Object.assign(observations, { units: `${observations.units}; ${WRIST_UNITS}`, wrist });
93
+ // a skill that paced its beats to fit a short span says so where every answer keeps it (C7 tier 1)
94
+ const paced = used.filter(u => u.paced).map(u => `${u.skill} paced ${u.paced.toFixed(2)} to fit f${u.f0}..f${u.end}`);
95
+ if (paced.length) observations.paced = paced;
96
+ const cut = used.filter(u => u.cut).map(u => `${u.skill}: ${u.cut}`);
97
+ if (cut.length) observations.cut = cut;
98
+ if (plans.length) observations.planned = plans.map(p => p.note);
99
+ const timeline = timelineOf(out, { used, spec: expanded }), probes = asked && runProbes(out, asked, { spec: expanded, sk, scene, notes });
100
+ return { out, qa, observations, spec: resolved, expanded, used, fixed: out.fixed, search, timeline, ...(probes ? { probes } : {}), ...(notes.length ? { notes } : {}),
101
+ ms: { compile: compileMs, qa: ms(t2), total: ms(t0) } };
102
+ }
@@ -0,0 +1,51 @@
1
+ // Play a take v3 on a live rig: local quaternions straight onto the Mixamo control bones
2
+ // (first depth-first bone of each name; the nested identity "skinned" copies are never
3
+ // written) and the hips position in rig space. The rig root carries the character's
4
+ // placement and the 0.01 centimetre scale, so metres become centimetres here.
5
+ import { normalizeBoneName } from "../poses.js";
6
+
7
+ const CM_PER_M = 100;
8
+ const resolved = new WeakMap();
9
+
10
+ function controlBones(rig, names) {
11
+ let entry = resolved.get(rig);
12
+ if (entry && entry.key === names.join()) return entry;
13
+ const first = new Map();
14
+ rig.traverse(o => {
15
+ if (!o.isBone) return;
16
+ const n = normalizeBoneName(o.name);
17
+ if (!first.has(n)) first.set(n, o);
18
+ });
19
+ const bones = names.map(name => {
20
+ const target = normalizeBoneName(name);
21
+ for (const [n, bone] of first) if (n === target || n.endsWith(target)) return bone;
22
+ return null;
23
+ });
24
+ // Bind translations: a take never moves a bone except the hips, so every other bone goes
25
+ // back to its bind position (another player may have stretched it).
26
+ const binds = rig.userData?.poseBind;
27
+ const bindPos = bones.map(b => {
28
+ const p = b && binds?.get(b)?.position;
29
+ return b ? (p ? [p.x, p.y, p.z] : b.position.toArray()) : null;
30
+ });
31
+ entry = { key: names.join(), bones, bindPos, hips: names.indexOf("Hips") };
32
+ resolved.set(rig, entry);
33
+ return entry;
34
+ }
35
+
36
+ export const isTakeV3 = motion => motion?.schema === "cozyclay.take.v3";
37
+
38
+ export function applyTakeFrame(rig, take, frame) {
39
+ if (!rig || !take) return;
40
+ const { bones, bindPos, hips } = controlBones(rig, take.bones);
41
+ const f = Math.max(0, Math.min(take.frameCount - 1, Math.round(frame) || 0)), B = take.bones.length;
42
+ for (let b = 0; b < B; b++) {
43
+ const bone = bones[b];
44
+ if (!bone) continue;
45
+ const i = (f * B + b) * 4;
46
+ bone.quaternion.set(take.rot[i], take.rot[i + 1], take.rot[i + 2], take.rot[i + 3]);
47
+ if (b === hips) bone.position.set(take.hips[f * 3] * CM_PER_M, take.hips[f * 3 + 1] * CM_PER_M, take.hips[f * 3 + 2] * CM_PER_M);
48
+ else bone.position.set(bindPos[b][0], bindPos[b][1], bindPos[b][2]);
49
+ }
50
+ rig.updateMatrixWorld(true);
51
+ }
@@ -0,0 +1,91 @@
1
+ // C5. Probes: measurements the agent asks for on this compile (motion.compile { probes }),
2
+ // whether or not it installs. At most 16 probes; each is checked first (schema/fields.js
3
+ // PROBE_FIELDS, refs.js checkRef with the full grammar) and every problem is thrown at once as
4
+ // MOTION_SPEC_INVALID, one per line, e.g.
5
+ // probes[0].distance[0] "joint:LeftHnad": unknown bone; did you mean "LeftHand"?
6
+ // Bone names may be loose ("hips", "hand_l", bones.js resolveBone); they are turned into the
7
+ // rig's names before measuring. Answers per name (a probe without a name is "probes[i]"):
8
+ // distance [a, b] { min, minAt, max, maxAt, mean, first, last } (refs.js refDistance; metres
9
+ // to 6 decimals; every `every` frames, default 1; null when no frame has a value)
10
+ // position ref { at: [{ f, p: [x,y,z] }] } (metres to 4 decimals; at most 16
11
+ // rows; every defaults to the step that fits the range in 16 rows, a smaller one grows to it)
12
+ // frames: [f0, f1] absolute and inclusive, or one frame (default the whole take); a window partly past
13
+ // the take is cut to it. Both fixes leave a note in ctx.notes (lenient.js; output.adjusted).
14
+ import { PROBE_FIELDS } from "./schema/fields.js";
15
+ import { checkRef, refPoint, refDistance } from "./refs.js";
16
+ import { resolveBone } from "./bones.js";
17
+ import { didYouMean, fmt } from "./compiler/vocab.js";
18
+ import { clampProbe, fitEvery } from "./lenient.js";
19
+
20
+ const MAX_PROBES = 16, MAX_ROWS = 16, MAX_ERRORS = 12, SHAPE = "{ name, distance: [refA, refB] | position: ref, frames: [f0, f1], every }";
21
+ const r6 = v => Math.round(v * 1e6) / 1e6, r4 = v => Math.round(v * 1e4) / 1e4;
22
+
23
+ /** joint:/bone: refs with the rig's own bone name ("joint:hips" -> "joint:Hips"). */
24
+ function exactRef(sk, ref) {
25
+ const m = /^(joint|bone):(.+)$/s.exec(ref);
26
+ return m ? `${m[1]}:${resolveBone(sk, m[2]).bone ?? m[2]}` : ref;
27
+ }
28
+
29
+ function rangeOf(p, at, last, err, notes) {
30
+ const fr = p.frames === undefined ? [0, last] : clampProbe(p.frames, last, at, notes);
31
+ if (!fr) err(`${at}.frames must be [f0, f1], integer frames within 0..${last} with f0 <= f1, got ${fmt(p.frames)}`);
32
+ return fr;
33
+ }
34
+
35
+ /** Checked probes [{ name, refs, kind, range, every }]; throws MOTION_SPEC_INVALID listing every problem. */
36
+ function checkProbes(out, probes, { spec, sk, scene, notes }) {
37
+ const errors = [], err = m => { if (errors.length < MAX_ERRORS) errors.push(m); }, last = out.frameCount - 1, list = [], names = new Map();
38
+ if (!Array.isArray(probes)) err(`probes must be an array of ${SHAPE}`);
39
+ else if (probes.length > MAX_PROBES) err(`probes has ${probes.length} entries; at most ${MAX_PROBES} per compile`);
40
+ else probes.forEach((p, i) => {
41
+ const at = `probes[${i}]`;
42
+ if (!p || typeof p !== "object" || Array.isArray(p)) return err(`${at} must be ${SHAPE}`);
43
+ for (const k of Object.keys(p)) if (!PROBE_FIELDS.includes(k)) err(`${at} has unknown field "${k}"${didYouMean(k, PROBE_FIELDS)}; a probe is ${SHAPE}`);
44
+ const name = p.name ?? at;
45
+ if (typeof name !== "string" || !name) err(`${at}.name must be a non-empty string, got ${fmt(p.name)}`);
46
+ else if (names.has(name)) err(`${at}.name "${name}" is already used by ${names.get(name)}; probe names must differ`);
47
+ else names.set(name, at);
48
+ if ((p.distance === undefined) === (p.position === undefined)) return err(`${at} needs exactly one of distance: [refA, refB] or position: ref`);
49
+ const kind = p.distance !== undefined ? "distance" : "position";
50
+ const refs = kind === "distance" ? p.distance : [p.position];
51
+ if (kind === "distance" && !(Array.isArray(refs) && refs.length === 2 && refs.every(r => typeof r === "string"))) return err(`${at}.distance must be [refA, refB], two point refs, got ${fmt(p.distance)}`);
52
+ if (kind === "position" && typeof p.position !== "string") return err(`${at}.position must be a point ref, got ${fmt(p.position)}`);
53
+ const good = refs.map((r, j) => checkRef(spec, err, { sk, scene }, kind === "distance" ? `${at}.distance[${j}]` : `${at}.position`, r)).every(Boolean);
54
+ const range = rangeOf(p, at, last, err, notes);
55
+ if (p.every !== undefined && !(Number.isInteger(p.every) && p.every >= 1)) return err(`${at}.every must be a positive integer, got ${fmt(p.every)}`);
56
+ if (!good || !range) return;
57
+ const span = range[1] - range[0] + 1, asked = p.every ?? (kind === "position" ? Math.ceil(span / MAX_ROWS) : 1);
58
+ const every = kind === "position" ? fitEvery(asked, span, MAX_ROWS, at, notes) : asked;
59
+ list.push({ name, kind, refs: refs.map(r => exactRef(sk, r)), range, every });
60
+ });
61
+ if (errors.length) throw new Error(`MOTION_SPEC_INVALID: ${errors.join("\n")}${errors.length >= MAX_ERRORS ? "\n(more problems not listed)" : ""}`);
62
+ return list;
63
+ }
64
+
65
+ function frameList([f0, f1], every) {
66
+ const out = [];
67
+ for (let f = f0; f <= f1; f += every) out.push(f);
68
+ return out;
69
+ }
70
+
71
+ function distanceOf(out, p) {
72
+ const vals = frameList(p.range, p.every).map(f => [f, refDistance(out, f, p.refs[0], p.refs[1])]).filter(([, v]) => Number.isFinite(v));
73
+ if (!vals.length) return { min: null, minAt: null, max: null, maxAt: null, mean: null, first: null, last: null };
74
+ let lo = vals[0], hi = vals[0], sum = 0;
75
+ for (const fv of vals) { if (fv[1] < lo[1]) lo = fv; if (fv[1] > hi[1]) hi = fv; sum += fv[1]; }
76
+ return { min: r6(lo[1]), minAt: lo[0], max: r6(hi[1]), maxAt: hi[0], mean: r6(sum / vals.length), first: r6(vals[0][1]), last: r6(vals[vals.length - 1][1]) };
77
+ }
78
+
79
+ function positionOf(out, p) {
80
+ return { at: frameList(p.range, p.every).map(f => { const q = refPoint(out, f, p.refs[0]); return { f, p: q ? q.map(r4) : null }; }) };
81
+ }
82
+
83
+ /**
84
+ * runProbes(out, probes, { spec, sk, scene }) -> { [name]: result }
85
+ * @param {object} out compileMotion result
86
+ * @param {object[]} probes the request's probes
87
+ * @param {{ spec: object, sk: object, scene: object, notes?: string[] }} ctx spec = the expanded spec; notes gets the windows cut and every grown
88
+ */
89
+ export function runProbes(out, probes, ctx) {
90
+ return Object.fromEntries(checkProbes(out, probes, ctx).map(p => [p.name, p.kind === "distance" ? distanceOf(out, p) : positionOf(out, p)]));
91
+ }
@@ -0,0 +1,34 @@
1
+ // BODY on the fitted skin (src/motion3/body.js): an upper arm, forearm or hand going into any other
2
+ // part of the body (torso, hips, head, legs, the other arm) more than L.skin. Each limb probes as
3
+ // spheres on its bone; the body answers from its signed distance grids. The parts are named as the
4
+ // capsules were (qa/geometry.js CAPSULES), so "hand_r into torso_hi" reads the same as before; a
5
+ // spec's checks.allow { part, object: <part>, depth } lets a declared contact touch deeper.
6
+ import { bodyFor, limbDepth } from "../body.js";
7
+
8
+ const REGIONS = [
9
+ [/^Hips$/, "hips"], [/^(Spine|Spine1)$/, "torso_lo"], [/^Spine2$/, "torso_hi"], [/^Neck$/, "neck"], [/^Head/, "head"],
10
+ [/Shoulder$/, "shoulder"], [/UpLeg$/, "thigh"], [/Leg$/, "shin"], [/(Foot|Toe)/, "foot"], [/ForeArm$/, "farm"], [/Arm$/, "uarm"], [/Hand$/, "hand"],
11
+ ];
12
+ /** The QA part name of bone b ("thigh_l", "torso_hi", ...). */
13
+ export function regionOf(sk, b) {
14
+ const name = sk.names[b] ?? "", side = /^Left/.test(name) ? "_l" : /^Right/.test(name) ? "_r" : "";
15
+ const hit = REGIONS.find(([re]) => re.test(name));
16
+ return hit ? `${hit[1]}${side}` : name;
17
+ }
18
+
19
+ export const ARM_BONES = ["Arm", "ForeArm", "Hand"].flatMap(n => [`Left${n}`, `Right${n}`]);
20
+
21
+ /** L: limits { skin } (m); push(kind, msg, frame, value, limit); allowed(part, into): a declared depth or null. */
22
+ export function bodyChecks(sk, out, L, push, allowed) {
23
+ const body = bodyFor(sk);
24
+ if (!body) return;
25
+ const limbs = ARM_BONES.map(n => sk.index[n]);
26
+ out.frames.forEach(({ s }, f) => {
27
+ for (const b of limbs) {
28
+ const hit = limbDepth(body, sk, s, b);
29
+ if (hit.into < 0) continue;
30
+ const part = regionOf(sk, b), into = regionOf(sk, hit.into), lim = Math.max(allowed(part, into) ?? 0, L.skin);
31
+ if (hit.depth > lim) push("BODY", `${part} into ${into}`, f, hit.depth, lim);
32
+ }
33
+ });
34
+ }
@@ -0,0 +1,14 @@
1
+ // Unnamed checks get a name from their place in the agent's own spec ("contacts[0]"), so their
2
+ // QA groups say which check failed and two unnamed checks stay two groups (severity.js groupKey
3
+ // keeps a bracketed index). Runs on a copy before the skills expand, so skill checks keep theirs.
4
+
5
+ const NAMED = ["contacts", "orient", "below", "clearance", "appear"];
6
+
7
+ /** spec -> a copy whose unnamed spec.checks entries are named "<list>[<index>]" (spec when none are). */
8
+ export function nameChecks(spec) {
9
+ const c = spec?.checks;
10
+ if (!c || typeof c !== "object" || !NAMED.some(k => Array.isArray(c[k]) && c[k].some(e => e && typeof e === "object" && e.name === undefined))) return spec;
11
+ const checks = { ...c };
12
+ for (const k of NAMED) if (Array.isArray(c[k])) checks[k] = c[k].map((e, i) => (e && typeof e === "object" && e.name === undefined ? { ...e, name: `${k}[${i}]` } : e));
13
+ return { ...spec, checks };
14
+ }
@@ -0,0 +1,125 @@
1
+ // General motion checks (every take): scene collisions, ground, IK reach, planted-foot slide,
2
+ // bone length, one-frame pops (style.js), knee and elbow flips, knees bending backward, knees and
3
+ // elbows folded past their stop (KNEE: the shin in the thigh; ELBOW: the forearm in the upper arm,
4
+ // compiler/limbs.js), self contact, hands and held props entering the head (and the torso,
5
+ // contact-checks.js; arms into any other body part on the fitted skin, body-checks.js). Speed, acceleration, knee bend and balance are style: style.js measures them
6
+ // and checks them only when the spec names their limit (checks.limits).
7
+ import { add, sub, scale, apply, len, dist, cross, norm, dot, angle } from "../compiler/math.js";
8
+ import { CAPSULES, segPoints, sdBox, segDist, headDepth, solePoints } from "./geometry.js";
9
+ import { contactChecks } from "./contact-checks.js";
10
+ import { bodyChecks } from "./body-checks.js";
11
+ import { popChecks } from "./style.js";
12
+ import { groundSole } from "./ground.js";
13
+ import { footChecks } from "./foot-checks.js";
14
+ import { ELBOW_STOP, KNEE_STOP } from "../compiler/limbs.js";
15
+
16
+ export const QA_LIMITS = { allow: 0.005, groundAllow: 0.01, reach: 0.01, slide: 0.0015, plantH: 0.012,
17
+ speedCore: 0.04, speedLimb: 0.16, accCore: 0.015, accLimb: 0.05, flip: 60, knee: 150, headAllow: 0.003, self: 0,
18
+ body: 0.005, skin: 0.015, hands: 0.01, object: 0.005, grab: 0.03, balance: 0.08 };
19
+ /** FLIP reads a bend plane only where the limb is bent this far (deg) from straight and from folded flat. */
20
+ const FLIP_BEND = 20;
21
+ /** KNEE (folds) and ELBOW: degrees past KNEE_STOP or ELBOW_STOP that count (the IK sits on the stop to rounding). */
22
+ const STOP_TOL = 0.01;
23
+
24
+ export const allowance = (allow, part, obj) => {
25
+ for (const a of allow ?? []) if (part.startsWith(a.part) && (a.object === obj || (a.object.endsWith("*") && obj.startsWith(a.object.slice(0, -1))))) return a.depth;
26
+ return null;
27
+ };
28
+
29
+ export function generalChecks(sk, out, opts = {}) {
30
+ const L = { ...QA_LIMITS, ...opts.limits }, issues = [], I = sk.index, F = out.frames, n = F.length;
31
+ const push = (kind, msg, f, value, limit) => issues.push({ kind, msg, frame: f, value: +value.toFixed(4), limit });
32
+ for (let f = 0; f < n; f++) {
33
+ const { s, diag } = F[f], objs = out.objectsAt(f);
34
+ // bounding spheres of the boxes: a capsule farther away than its own reach cannot touch one
35
+ const spheres = Object.entries(objs).filter(([, box]) => box.half && !box.ignore)
36
+ .map(([id, box]) => [id, box, add(box.pos, apply(box.R, [0, box.half[1], 0])), len(box.half)]);
37
+ for (const [part, a, b, r] of CAPSULES) {
38
+ const A = s.P[I[a]], B = s.P[I[b]], mid = scale(add(A, B), 0.5), reach = r + dist(A, B) / 2;
39
+ let pts = null;
40
+ for (const [id, box, c, rb] of spheres) {
41
+ const lim = allowance(opts.allow, part, id) ?? L.allow;
42
+ if (reach - (dist(mid, c) - rb) <= lim) continue;
43
+ pts ??= segPoints(A, B);
44
+ const pen = r - Math.min(...pts.map(p => sdBox(p, box)));
45
+ if (pen > lim) push("COLLIDE", `${part} x ${id}`, f, pen, lim);
46
+ }
47
+ }
48
+ footChecks(sk, s, f, spheres, L, (part, id) => allowance(opts.allow, part, id), push);
49
+ for (const side of ["Left", "Right"]) {
50
+ const sp = solePoints(sk, s, side), low = Math.min(sp.heel[1], sp.ball[1], sp.tip[1]);
51
+ if (low < -L.groundAllow) push("GROUND", `${side} sole below floor`, f, -low, L.groundAllow);
52
+ }
53
+ for (const [k, v] of Object.entries(diag)) if (/miss_[lr]$/.test(k) && v > L.reach) push("REACH", k, f, v, L.reach);
54
+ // hands, forearms and held props must stay out of the head
55
+ const probes = [];
56
+ for (const S of ["Left", "Right"]) probes.push(...segPoints(s.P[I[`${S}Hand`]], s.P[I[`${S}HandMiddle3`]], 5).map(p => [`hand_${S[0].toLowerCase()}`, p, 0.022]));
57
+ for (const [pn, pts] of Object.entries(diag.props ?? {})) for (const [k, p] of Object.entries(pts)) if (Array.isArray(p) && k !== "origin" && !(opts.mouthPoints ?? []).includes(`${pn}.${k}`)) probes.push([`prop_${pn}`, p, 0.004]);
58
+ for (const [nm, p, r] of probes) { const d = headDepth(sk, s, p) + r; if (d > L.headAllow) push("FACE", `${nm} into head`, f, d, L.headAllow); }
59
+ if (segDist(s.P[I.LeftLeg], s.P[I.LeftFoot], s.P[I.RightLeg], s.P[I.RightFoot]) < 0.1 + L.self) push("SELF", "shins touch", f, 0.1, 0.1);
60
+ if (segDist(s.P[I.LeftUpLeg], s.P[I.LeftLeg], s.P[I.RightUpLeg], s.P[I.RightLeg]) < 0.135 + L.self) push("SELF", "thighs overlap", f, 0.135, 0.135);
61
+ const tilted = norm(sub(s.P[I.Spine], s.P[I.Hips]))[1] < 0.5;
62
+ for (const S of ["Left", "Right"]) {
63
+ const th = s.P[I[`${S}UpLeg`]], kn = s.P[I[`${S}Leg`]], an = s.P[I[`${S}Foot`]];
64
+ const bend = angle(sub(kn, th), sub(an, kn));
65
+ // the foot's heading on the ground; the toe sits below the ankle, which must not count. With the pelvis turned past
66
+ // 60 deg from upright (a flip, a cartwheel, lying) the ground says nothing about the leg: the foot's front is the toe
67
+ // across the shin, which an upright leg reads the same way
68
+ let toeDir = sub(s.P[I[`${S}ToeBase`]], an);
69
+ if (tilted) { const shin = norm(sub(an, kn)); toeDir = sub(toeDir, scale(shin, dot(toeDir, shin))); } else toeDir[1] = 0;
70
+ const mid = sub(kn, [(th[0] + an[0]) / 2, (th[1] + an[1]) / 2, (th[2] + an[2]) / 2]);
71
+ if (bend > 3 && dot(mid, toeDir) < -0.002) push("KNEE", `${S} knee bends backward`, f, bend, 0);
72
+ // a knee folded past its stop puts the shin in the thigh (the solver stops at KNEE_STOP)
73
+ if (bend > KNEE_STOP + STOP_TOL) push("KNEE", `${S} knee folds ${bend.toFixed(0)}deg`, f, bend, KNEE_STOP);
74
+ // an elbow folded past its stop puts the forearm in the upper arm (the solver stops at ELBOW_STOP)
75
+ const fold = angle(sub(s.P[I[`${S}ForeArm`]], s.P[I[`${S}Arm`]]), sub(s.P[I[`${S}Hand`]], s.P[I[`${S}ForeArm`]]));
76
+ if (fold > ELBOW_STOP + STOP_TOL) push("ELBOW", `${S} elbow folds ${fold.toFixed(0)}deg`, f, fold, ELBOW_STOP);
77
+ }
78
+ }
79
+ const allowed = (part, into) => allowance(opts.allow, part, into);
80
+ contactChecks(sk, out, L, push, allowed);
81
+ bodyChecks(sk, out, L, push, allowed);
82
+ // temporal checks; a planted heel or ball is low over the ground under it, the floor or an object's top (ground.js)
83
+ for (const S of ["Left", "Right"]) {
84
+ const sole = F.map((_, f) => groundSole(out, f, S));
85
+ for (let f = 1; f < n; f++) {
86
+ const a = sole[f - 1], b = sole[f];
87
+ for (const key of ["heel", "ball"]) {
88
+ const planted = b[key][1] < L.plantH && a[key][1] < L.plantH && Math.abs(b[key][1] - a[key][1]) < 0.0005;
89
+ const hv = Math.hypot(b[key][0] - a[key][0], b[key][2] - a[key][2]);
90
+ if (planted && hv > L.slide) push("SLIDE", `${S} ${key} slides`, f, hv, L.slide);
91
+ }
92
+ }
93
+ }
94
+ for (let j = 0; j < sk.names.length; j++) {
95
+ const p = sk.parent[j]; if (p < 0) continue;
96
+ const L0 = dist(sk.restWorldPos[j], sk.restWorldPos[p]);
97
+ const drift = Math.max(...F.map(fr => Math.abs(dist(fr.s.P[j], fr.s.P[p]) - L0)));
98
+ if (drift > 0.001) push("LENGTH", `${sk.names[p]}-${sk.names[j]}`, 0, drift, 0.001);
99
+ }
100
+ popChecks(sk, out, L, push);
101
+ flipChecks(sk, F, L, push);
102
+ return issues;
103
+ }
104
+
105
+ /**
106
+ * FLIP: a knee's or elbow's bend plane turns more than L.flip degrees a frame. The plane is read
107
+ * only on frames the limb is bent FLIP_BEND or more from straight and from folded flat: near
108
+ * either end a few millimetres set it, so the hand or foot moving a few centimetres turns it any
109
+ * way (session df6acdcc: the right elbow folded to 171 deg as the wrist passed the shoulder, and
110
+ * its plane turned 71 deg in a frame while the elbow moved 3 cm). Across the frames between, the
111
+ * turn is shared out per frame, so a pole that flips while the limb is straight still shows.
112
+ */
113
+ function flipChecks(sk, F, L, push) {
114
+ const I = sk.index, bent = Math.sin(FLIP_BEND * Math.PI / 180);
115
+ for (const S of ["Left", "Right"]) for (const [a, b, c] of [[`${S}UpLeg`, `${S}Leg`, `${S}Foot`], [`${S}Arm`, `${S}ForeArm`, `${S}Hand`]]) {
116
+ let prev = null, at = 0;
117
+ for (let f = 0; f < F.length; f++) {
118
+ const P = F[f].s.P, up = sub(P[I[b]], P[I[a]]), down = sub(P[I[c]], P[I[b]]), nv = cross(up, down);
119
+ if (len(nv) < bent * len(up) * len(down)) continue;
120
+ const u = norm(nv), turn = prev ? angle(u, prev) : 0, gap = f - at;
121
+ if (turn / gap > L.flip) push("FLIP", `${b} bend plane jumps ${turn.toFixed(0)}deg${gap > 1 ? ` over ${gap} frames` : ""}`, f, turn / gap, L.flip);
122
+ prev = u; at = f;
123
+ }
124
+ }
125
+ }
@@ -0,0 +1,51 @@
1
+ // Contact checks on top of the general set (ported from the car-exit prototype QA): hands and
2
+ // held prop points entering the torso, the two hands overlapping, and hinged scene objects
3
+ // (spec.objects, e.g. a door) passing through the rest of the scene. Corners only for objects.
4
+ import { add, sub, scale, dot, len, apply, clamp } from "../compiler/math.js";
5
+ import { CAPSULES, sdBox, segDist } from "./geometry.js";
6
+ import { movesRecord } from "../carried.js";
7
+
8
+ const TORSO = CAPSULES.filter(c => c[0].startsWith("torso"));
9
+ const HAND_R = CAPSULES.find(c => c[0] === "hand_l")[3];
10
+ const PROP_R = 0.004;
11
+
12
+ function pointSegDist(p, a, b) {
13
+ const ab = sub(b, a), t = clamp(dot(sub(p, a), ab) / Math.max(dot(ab, ab), 1e-12), 0, 1);
14
+ return len(sub(p, add(a, scale(ab, t))));
15
+ }
16
+
17
+ function corners(o) {
18
+ const out = [];
19
+ for (const x of [-1, 1]) for (const y of [0, 2]) for (const z of [-1, 1]) out.push(add(o.pos, apply(o.R, [x * o.half[0], y * o.half[1], z * o.half[2]])));
20
+ return out;
21
+ }
22
+
23
+ /** L: limits { body, hands, object } (m); push(kind, msg, frame, value, limit); allowed(part, into): a declared depth or null. */
24
+ export function contactChecks(sk, out, L, push, allowed = () => null) {
25
+ const I = sk.index, hinged = Object.entries(out.ctx.spec.objects ?? {}).filter(([, o]) => movesRecord(o)).map(([id]) => id);
26
+ for (let f = 0; f < out.frames.length; f++) {
27
+ const { s, diag } = out.frames[f], P = s.P;
28
+ // hands into the torso: body-checks.js, on the fitted skin; held prop points stay on the capsules
29
+ const probes = [];
30
+ for (const [pn, pts] of Object.entries(diag.props ?? {})) for (const [k, p] of Object.entries(pts)) if (Array.isArray(p) && k !== "origin") probes.push([`prop_${pn}`, p, PROP_R]);
31
+ for (const [part, a, b, r] of TORSO) {
32
+ const worst = new Map();
33
+ for (const [nm, p, pr] of probes) worst.set(nm, Math.max(worst.get(nm) ?? -Infinity, r + pr - pointSegDist(p, P[I[a]], P[I[b]])));
34
+ for (const [nm, d] of worst) if (d > L.body) push("BODY", `${nm} into ${part}`, f, d, L.body);
35
+ }
36
+ const overlap = 2 * HAND_R - segDist(P[I.LeftHand], P[I.LeftHandMiddle3], P[I.RightHand], P[I.RightHandMiddle3]);
37
+ const handsLim = Math.max(allowed("hand_l", "hand_r") ?? 0, L.hands);
38
+ if (overlap > handsLim) push("SELF", "hands overlap", f, overlap, handsLim);
39
+ if (!hinged.length) continue;
40
+ const objs = out.objectsAt(f);
41
+ for (const id of hinged) {
42
+ if (!objs[id]?.half) continue;
43
+ const cs = corners(objs[id]);
44
+ for (const [oid, box] of Object.entries(objs)) {
45
+ if (hinged.includes(oid) || !box.half || box.ignore) continue;
46
+ const depth = -Math.min(...cs.map(p => sdBox(p, box)));
47
+ if (depth > L.object) push("OBJECT", `${id} into ${oid}`, f, depth, L.object);
48
+ }
49
+ }
50
+ }
51
+ }
@@ -0,0 +1,80 @@
1
+ // Actionable QA: every issue group gets a `cause` (the spec element most responsible: a key and
2
+ // channel, an anchor, a fit, a generator step, a skill) and a `hint` (what to change, with a
3
+ // direction and an amount in world metres or degrees, at a frame). Hints are derived from the
4
+ // geometry of the worst frame and from the keys that bracket it. A hint function returns
5
+ // { cause, hint, fix? }; fix rides on the group unseen (non-enumerable) for qa/patch.js.
6
+ import { contactHints } from "./hints-contact.js";
7
+ import { motionHints } from "./hints-motion.js";
8
+ import { objectHints } from "./hints-object.js";
9
+ import { goalHints } from "./hints-goal.js";
10
+ import { wristHints } from "./wrist-checks.js";
11
+ import { attachPatches } from "./patch.js";
12
+
13
+ const HINTS = { ...contactHints, ...motionHints, ...objectHints, ...goalHints, ...wristHints };
14
+
15
+ function keyTable(out) {
16
+ const byCh = new Map();
17
+ for (const k of out.keys ?? []) for (const ch of Object.keys(k.set ?? {})) {
18
+ if (!byCh.has(ch)) byCh.set(ch, []);
19
+ byCh.get(ch).push({ f: k.f, src: k.src ?? "keys", step: k.step?.includes(ch) });
20
+ }
21
+ for (const list of byCh.values()) list.sort((a, b) => a.f - b.f);
22
+ return byCh;
23
+ }
24
+
25
+ /** The context every hint function reads: frames by absolute index, keys, drivers. */
26
+ function context(sk, out, spec) {
27
+ const table = keyTable(out);
28
+ const X = {
29
+ sk, out, spec, I: sk.index,
30
+ fr: f => out.frames[Math.max(0, Math.min(out.frames.length - 1, f - (out.f0 ?? 0)))],
31
+ rel: f => Math.max(0, Math.min(out.frames.length - 1, f - (out.f0 ?? 0))),
32
+ keys: ch => table.get(ch) ?? [],
33
+ bracket(ch, f) {
34
+ const list = X.keys(ch);
35
+ let prev = null, next = null;
36
+ for (const k of list) { if (k.f <= f) prev = k; else { next = k; break; } }
37
+ return { prev, next };
38
+ },
39
+ /** "keys[12] f200 hand_r" when a key sits within 2 frames, else the two keys around f. */
40
+ cause(ch, f) {
41
+ const { prev, next } = X.bracket(ch, f);
42
+ if (!prev && !next) return `${ch} (not keyed)`;
43
+ const near = prev && f - prev.f <= 2 ? prev : next && next.f - f <= 2 ? next : null;
44
+ if (near) return `${near.src} f${Math.round(near.f)} ${ch}`;
45
+ if (!next) return `${ch} held since ${prev.src} f${Math.round(prev.f)}`;
46
+ if (!prev) return `${ch} before ${next.src} f${Math.round(next.f)}`;
47
+ return `${ch} between ${prev.src} f${Math.round(prev.f)} and ${next.src === prev.src ? "" : `${next.src} `}f${Math.round(next.f)}`;
48
+ },
49
+ /** What drives hand k at frame f: an anchor weight, the hand target, or the FK arm. */
50
+ hand(k, f) {
51
+ const c = X.fr(f).c;
52
+ if (!(c[`armw_${k}`] > 0.5)) return { label: `arm ${k} (FK: aswing_${k}/abend_${k}, or armw_${k} 1 and hand_${k})`, ch: `aswing_${k}` };
53
+ let best = null;
54
+ for (const [ch, w] of Object.entries(c)) {
55
+ const mm = /^w_(.+)_([lr])$/.exec(ch);
56
+ if (mm && mm[2] === k && w > 0.5 && (!best || w > best.w)) best = { w, anchor: mm[1], ch };
57
+ }
58
+ if (!best) return { label: `hand_${k}`, ch: `hand_${k}` };
59
+ const fit = (spec.fits ?? []).findIndex(ft => ft.anchor === best.anchor && ft.side === k);
60
+ return { label: `anchor ${best.anchor}`, ch: best.ch, anchor: best.anchor, fit: fit >= 0 ? fit : null };
61
+ },
62
+ };
63
+ return X;
64
+ }
65
+
66
+ /** Adds cause + hint (+ patch) to the first `limit` groups (mutates and returns them); userSpec is what patches address. */
67
+ export function explainGroups(sk, out, spec, groups, { limit = 16, userSpec } = {}) {
68
+ if (!groups.length) return groups;
69
+ const X = context(sk, out, spec);
70
+ for (const g of groups.slice(0, limit)) {
71
+ try {
72
+ const r = HINTS[g.kind]?.(X, g);
73
+ if (r) { g.cause = r.cause; g.hint = r.hint; }
74
+ if (r?.fix) Object.defineProperty(g, "fix", { value: r.fix, enumerable: false, configurable: true });
75
+ } catch (error) {
76
+ g.cause = "unknown"; g.hint = `no hint (${error.message})`;
77
+ }
78
+ }
79
+ return attachPatches(groups, { X, userSpec });
80
+ }
@@ -0,0 +1,5 @@
1
+ // Compact number formatting for QA hints (millimetre precision).
2
+ export const m3 = x => { const r = Math.round(x * 1000) / 1000; return Object.is(r, -0) ? 0 : r; };
3
+
4
+ /** Compact vector: [+0.021, 0, -0.004]. */
5
+ export const vec = v => `[${v.map(x => { const r = m3(x); return r > 0 ? `+${r}` : `${r}`; }).join(", ")}]`;
@@ -0,0 +1,72 @@
1
+ // COLLIDE for the feet: the foot's skin against the scene bodies, as "foot_<k> x <id>". The shin capsule ends at
2
+ // the ankle and GROUND reads the floor only, so a swinging toe went 9.6 cm into a stair riser with QA clean
3
+ // (session e7edb718). The probes are points on the fitted skin of the foot and toe bones (body.js grids: the
4
+ // cells within half a step of the skin, one per SPACING m cube, carried by their bone); a rig without a fitted
5
+ // body uses the sole points. A foot on a body's top is a step, not a collision: a probe the top face is nearest
6
+ // to may sit up to the ground's allowance in it (QA_LIMITS.groundAllow, as GROUND gives the floor); through a
7
+ // side face the usual allowance holds. A part-wise allow entry ({ part: "foot", object }) sets both.
8
+ import { add, apply, dist, dot, scale } from "../compiler/math.js";
9
+ import { bodyFor } from "../body.js";
10
+ import { sdBox, boxNormal, solePoints } from "./geometry.js";
11
+
12
+ const UP = [0, 1, 0], TOP = 0.7, SPACING = 0.025;
13
+ const skins = new Map();
14
+
15
+ /** Skin points of a foot in its bones' bind frames: [[bone index, local point], ...] (null for a rig without a fitted body). */
16
+ function footSkin(sk, side) {
17
+ const key = `${sk.model}:${side}`;
18
+ if (skins.has(key)) return skins.get(key);
19
+ const body = bodyFor(sk), out = body ? [] : null;
20
+ for (const g of body?.grids ?? []) {
21
+ if (g.b !== sk.index[`${side}Foot`] && g.b !== sk.index[`${side}ToeBase`]) continue;
22
+ const [nx, ny, nz] = g.dims, seen = new Set();
23
+ for (let i = 0; i < nx; i++) for (let j = 0; j < ny; j++) for (let k = 0; k < nz; k++) {
24
+ if (Math.abs(g.data[(i * ny + j) * nz + k] / 1000) > body.step / 2) continue;
25
+ const q = [g.min[0] + i * body.step, g.min[1] + j * body.step, g.min[2] + k * body.step], cell = q.map(v => Math.floor(v / SPACING)).join();
26
+ if (!seen.has(cell)) { seen.add(cell); out.push([g.b, q]); }
27
+ }
28
+ }
29
+ skins.set(key, out);
30
+ return out;
31
+ }
32
+
33
+ /** The foot's collision probes at state s: world points on its skin (the sole points without a fitted body). */
34
+ export function footProbes(sk, s, side) {
35
+ const skin = footSkin(sk, side);
36
+ if (!skin?.length) {
37
+ const sp = solePoints(sk, s, side), mid = (a, b) => scale(add(a, b), 0.5);
38
+ return [sp.heel, mid(sp.heel, sp.ball), sp.ball, mid(sp.ball, sp.tip), sp.tip];
39
+ }
40
+ return skin.map(([b, q]) => add(s.P[b], apply(s.R[b], q)));
41
+ }
42
+
43
+ /**
44
+ * How deep a foot's probes go into a box, through its top and through its sides: { top, side } in metres
45
+ * (-Infinity: not in), each with the probe that goes deepest that way ({ topAt, sideAt }).
46
+ */
47
+ export function footDepths(probes, box) {
48
+ const out = { top: -Infinity, side: -Infinity, topAt: null, sideAt: null };
49
+ for (const p of probes) {
50
+ const d = -sdBox(p, box);
51
+ if (d <= 0) continue;
52
+ const way = dot(boxNormal(p, box), UP) > TOP ? "top" : "side";
53
+ if (d > out[way]) { out[way] = d; out[`${way}At`] = p; }
54
+ }
55
+ return out;
56
+ }
57
+
58
+ /**
59
+ * Push COLLIDE "foot_<k> x <id>" for each foot in each body at frame f. spheres: [id, box, centre, radius] of
60
+ * the bodies (checks.js); allowed(part, id) the spec's allow depth or null.
61
+ */
62
+ export function footChecks(sk, s, f, spheres, L, allowed, push) {
63
+ for (const [side, k] of [["Left", "l"], ["Right", "r"]]) {
64
+ const probes = footProbes(sk, s, side), part = `foot_${k}`;
65
+ for (const [id, box, c, rb] of spheres) {
66
+ if (probes.every(p => dist(p, c) > rb)) continue;
67
+ const d = footDepths(probes, box), own = allowed(part, id), sideLim = own ?? L.allow, topLim = own ?? L.groundAllow;
68
+ if (d.side > sideLim) push("COLLIDE", `${part} x ${id}`, f, d.side, sideLim);
69
+ else if (d.top > topLim) push("COLLIDE", `${part} x ${id}`, f, d.top, topLim);
70
+ }
71
+ }
72
+ }