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
@@ -1,326 +0,0 @@
1
- """Scene terms for the known-character tracker: boxes, floor, stance, occlusion.
2
-
3
- World frame: Studio / three.js, metres, +Y up, floor plane y = floor_y (0).
4
- Boxes are oriented about +Y only (props stay upright, `src/ardy/collision-blockers.js`):
5
- a box point is `center + Ry(yaw) @ local` with `Ry` the three.js rotation
6
- (`tools/gt-render/scene-box.mjs` boxCorners), `|local| <= halfExtents`.
7
-
8
- Accepted box records (`parse_scene`):
9
- - today's bench scene.json: `{min, max}` (plus optional `centre`, `halfExtents`, `yawDeg`)
10
- - shot context: `{center|centre, halfExtents, yaw (radians) | yawDeg, perFrame?}` where
11
- `perFrame` is a list of per-frame centres `[x, y, z]` or `{center|centre, yaw|yawDeg}`
12
- for props that travel or turn.
13
- - a list of such records, or `{boxes: [...]}`.
14
-
15
- Everything that feeds a loss takes plain torch tensors (vertices or joints) and is
16
- differentiable; the HMM and ray tests are discrete and run without gradients.
17
- """
18
- from __future__ import annotations
19
-
20
- import json
21
- import math
22
- from pathlib import Path
23
- from typing import NamedTuple
24
-
25
- import numpy as np
26
- import torch
27
-
28
- # cskel27 indices: LeftFoot, LeftToeBase / RightFoot, RightToeBase (src/ardy/cskel27.js).
29
- FOOT_NAMES = ("left", "right")
30
- FOOT_JOINTS = ((25, 26), (21, 22))
31
-
32
-
33
- class SceneError(ValueError):
34
- """Malformed scene input. `code` is the machine-readable reason."""
35
-
36
- def __init__(self, code: str, detail: str):
37
- super().__init__(f"{code}: {detail}")
38
- self.code = code
39
-
40
-
41
- class Box(NamedTuple):
42
- center: np.ndarray # (3,)
43
- half_extents: np.ndarray # (3,)
44
- yaw: float # radians about +Y
45
- per_frame_center: np.ndarray | None # (T, 3)
46
- per_frame_yaw: np.ndarray | None # (T,)
47
-
48
-
49
- class SceneBoxes(NamedTuple):
50
- centers: torch.Tensor # (B, T, 3)
51
- half: torch.Tensor # (B, 3)
52
- yaw: torch.Tensor # (B, T)
53
-
54
-
55
- def _vec3(value, what: str) -> np.ndarray:
56
- try:
57
- out = np.asarray(value, dtype=np.float64)
58
- except (TypeError, ValueError):
59
- raise SceneError("bad-box", f"{what} must be 3 numbers, got {value!r}") from None
60
- if out.shape != (3,) or not np.all(np.isfinite(out)):
61
- raise SceneError("bad-box", f"{what} must be 3 finite numbers, got {value!r}")
62
- return out
63
-
64
-
65
- def _yaw(record: dict, default: float, what: str) -> float:
66
- if "yaw" in record:
67
- value = record["yaw"]
68
- elif "yawDeg" in record:
69
- value = record["yawDeg"] * math.pi / 180 if isinstance(record["yawDeg"], (int, float)) else record["yawDeg"]
70
- else:
71
- return default
72
- if not isinstance(value, (int, float)) or not math.isfinite(value):
73
- raise SceneError("bad-box", f"{what} yaw must be a finite number, got {value!r}")
74
- return float(value)
75
-
76
-
77
- def parse_box(record, index: int = 0) -> Box:
78
- what = f"box {index}"
79
- if not isinstance(record, dict):
80
- raise SceneError("bad-box", f"{what} must be an object, got {type(record).__name__}")
81
- center = record.get("center", record.get("centre"))
82
- if center is not None or "halfExtents" in record:
83
- if center is None or "halfExtents" not in record:
84
- raise SceneError("bad-box", f"{what} needs both center and halfExtents")
85
- center, half = _vec3(center, f"{what} center"), _vec3(record["halfExtents"], f"{what} halfExtents")
86
- elif "min" in record and "max" in record:
87
- lo, hi = _vec3(record["min"], f"{what} min"), _vec3(record["max"], f"{what} max")
88
- center, half = (lo + hi) / 2, (hi - lo) / 2
89
- else:
90
- raise SceneError("bad-box", f"{what} needs {{center, halfExtents}} or {{min, max}}")
91
- if not np.all(half > 0):
92
- raise SceneError("degenerate-box", f"{what} has non-positive size {(2 * half).tolist()}")
93
- yaw = _yaw(record, 0.0, what)
94
- per_center = per_yaw = None
95
- frames = record.get("perFrame")
96
- if frames is not None:
97
- if not isinstance(frames, list) or not frames:
98
- raise SceneError("bad-box", f"{what} perFrame must be a non-empty list")
99
- per_center, per_yaw = np.empty((len(frames), 3)), np.empty(len(frames))
100
- for t, entry in enumerate(frames):
101
- if isinstance(entry, dict):
102
- per_center[t] = _vec3(entry.get("center", entry.get("centre")), f"{what} perFrame[{t}] center")
103
- per_yaw[t] = _yaw(entry, yaw, f"{what} perFrame[{t}]")
104
- else:
105
- per_center[t], per_yaw[t] = _vec3(entry, f"{what} perFrame[{t}]"), yaw
106
- return Box(center, half, yaw, per_center, per_yaw)
107
-
108
-
109
- def parse_scene(scene) -> list[Box]:
110
- """Boxes from a scene.json / shot-context value (None -> no boxes)."""
111
- if scene is None:
112
- return []
113
- if isinstance(scene, dict) and "boxes" in scene:
114
- scene = scene["boxes"]
115
- records = scene if isinstance(scene, list) else [scene]
116
- return [parse_box(record, i) for i, record in enumerate(records)]
117
-
118
-
119
- def load_scene(path) -> list[Box]:
120
- return parse_scene(json.loads(Path(path).read_text()))
121
-
122
-
123
- def box_tensors(boxes: list[Box], frames: int, *, device=None, dtype=torch.float32) -> SceneBoxes:
124
- """Per-frame box tensors; static boxes are repeated over `frames`."""
125
- centers, yaws = [], []
126
- for i, box in enumerate(boxes):
127
- if box.per_frame_center is not None:
128
- if len(box.per_frame_center) != frames:
129
- raise SceneError("box-frames-mismatch", f"box {i} has {len(box.per_frame_center)} perFrame entries for {frames} frames")
130
- centers.append(box.per_frame_center)
131
- yaws.append(box.per_frame_yaw)
132
- else:
133
- centers.append(np.broadcast_to(box.center, (frames, 3)))
134
- yaws.append(np.full(frames, box.yaw))
135
- as_t = lambda a, shape: torch.as_tensor(np.asarray(a, dtype=np.float64).reshape(shape), dtype=dtype, device=device)
136
- b = len(boxes)
137
- return SceneBoxes(
138
- centers=as_t(centers, (b, frames, 3)),
139
- half=as_t([box.half_extents for box in boxes], (b, 3)),
140
- yaw=as_t(yaws, (b, frames)),
141
- )
142
-
143
-
144
- def _boxes_like(boxes: SceneBoxes, points: torch.Tensor) -> SceneBoxes:
145
- """Boxes on the points' device and dtype (differentiable; a no-op when they already match)."""
146
- return SceneBoxes(*(v.to(device=points.device, dtype=points.dtype) for v in boxes))
147
-
148
-
149
- def _cpu64(value) -> torch.Tensor:
150
- """Detached float64 CPU tensor from a tensor on any device, an array or a list."""
151
- if isinstance(value, torch.Tensor):
152
- return value.detach().to(device="cpu", dtype=torch.float64)
153
- return torch.as_tensor(np.asarray(value, dtype=np.float64))
154
-
155
-
156
- def to_box_local(points: torch.Tensor, boxes: SceneBoxes) -> torch.Tensor:
157
- """points (T, N, 3) -> box-local coordinates (T, N, B, 3), on the points' device."""
158
- boxes = _boxes_like(boxes, points)
159
- d = points[:, :, None, :] - boxes.centers.transpose(0, 1)[:, None, :, :]
160
- c, s = torch.cos(boxes.yaw).T[:, None, :], torch.sin(boxes.yaw).T[:, None, :]
161
- dx, dy, dz = d.unbind(-1)
162
- return torch.stack((c * dx - s * dz, dy, s * dx + c * dz), dim=-1)
163
-
164
-
165
- def box_sdf(points: torch.Tensor, boxes: SceneBoxes) -> torch.Tensor:
166
- """Signed distance (T, N, B) of points (T, N, 3) to each box; positive outside."""
167
- q = to_box_local(points, boxes).abs() - boxes.half[None, None].to(device=points.device, dtype=points.dtype)
168
- sq = torch.relu(q).square().sum(-1)
169
- # sqrt has an infinite derivative at 0: route inside points through a constant.
170
- outside = torch.where(sq > 0, torch.sqrt(torch.where(sq > 0, sq, torch.ones_like(sq))), torch.zeros_like(sq))
171
- return outside + torch.clamp(q.max(-1).values, max=0)
172
-
173
-
174
- def floor_sdf(points: torch.Tensor, floor_y: float = 0.0) -> torch.Tensor:
175
- return points[..., 1] - floor_y
176
-
177
-
178
- def penetration_loss(points: torch.Tensor, boxes: SceneBoxes, *, floor_y: float = 0.0, margin: float = 0.0) -> dict[str, torch.Tensor]:
179
- """{"box", "floor"}: sum relu(margin - sd)^2 over points (T, N, 3) and every box / the floor."""
180
- return {
181
- "box": torch.relu(margin - box_sdf(points, boxes)).square().sum(),
182
- "floor": torch.relu(margin - floor_sdf(points, floor_y)).square().sum(),
183
- }
184
-
185
-
186
- @torch.no_grad()
187
- def penetration_stats(points: torch.Tensor, boxes: SceneBoxes, *, floor_y: float = 0.0) -> dict:
188
- """Diagnostics: deepest box / floor penetration in cm and the frames with any penetration."""
189
- box_depth = torch.relu(-box_sdf(points, boxes)).amax(dim=(1, 2)) if boxes.half.shape[0] else points.new_zeros(points.shape[0])
190
- floor_depth = torch.relu(-floor_sdf(points, floor_y)).amax(dim=1)
191
- worst = torch.maximum(box_depth, floor_depth)
192
- return {
193
- "maxBoxCm": float(box_depth.max()) * 100 if box_depth.numel() else 0.0,
194
- "maxFloorCm": float(floor_depth.max()) * 100 if floor_depth.numel() else 0.0,
195
- "frames": [int(t) for t in torch.nonzero(worst > 0).flatten()],
196
- }
197
-
198
-
199
- @torch.no_grad()
200
- def support_height(points: torch.Tensor, boxes: SceneBoxes, *, floor_y: float = 0.0, step_tol: float = 0.1) -> torch.Tensor:
201
- """(T, N) height of the surface under each point: the floor or the top of a box whose
202
- footprint contains the point and whose top is at most `step_tol` above it."""
203
- support = torch.full(points.shape[:2], float(floor_y), dtype=points.dtype, device=points.device)
204
- if not boxes.half.shape[0]:
205
- return support
206
- boxes = _boxes_like(boxes, points)
207
- local = to_box_local(points, boxes)
208
- half = boxes.half[None, None]
209
- over = (local[..., 0].abs() <= half[..., 0]) & (local[..., 2].abs() <= half[..., 2])
210
- top = (boxes.centers[..., 1] + boxes.half[:, 1:2]).T[:, None, :] # (T, 1, B)
211
- eligible = over & (top <= points[..., 1:2] + step_tol)
212
- tops = torch.where(eligible, top.expand_as(eligible), torch.full_like(eligible, -math.inf, dtype=points.dtype))
213
- return torch.maximum(support, tops.amax(-1))
214
-
215
-
216
- def foot_points(joints: torch.Tensor) -> torch.Tensor:
217
- """cskel27 joints (T, 27, 3) -> (T, 2, 2, 3): feet (left, right) x (ankle, toe)."""
218
- return joints[:, torch.as_tensor(FOOT_JOINTS, device=joints.device)]
219
-
220
-
221
- @torch.no_grad()
222
- def stance_hmm(
223
- feet,
224
- fps: float,
225
- boxes: SceneBoxes | None = None,
226
- *,
227
- floor_y: float = 0.0,
228
- contact_height=None,
229
- stay: float = 0.95,
230
- height_scale: float = 0.05,
231
- speed_scale: float = 0.3,
232
- sharpness: float = 4.0,
233
- step_tol: float = 0.1,
234
- ) -> np.ndarray:
235
- """Per-foot 2-state (swing/contact) Viterbi. Returns bool (T, F), True = contact.
236
-
237
- feet: (T, F, 3) or (T, F, K, 3) world positions of the current estimate (K contact
238
- joints per foot, e.g. `foot_points`). Per joint, h = height above its support minus
239
- its planted height (`contact_height`, broadcastable to (F, K); default: the joint's
240
- 5th-percentile height over the clip), v = horizontal speed. Normalised evidence
241
- z = min_k sqrt((max(h, 0) / height_scale)^2 + (v / speed_scale)^2) (any planted joint
242
- means contact); p(contact) = sigmoid(sharpness * (1 - z)); stay probability `stay`.
243
- """
244
- # Discrete pass: everything (feet, boxes, contact_height) crosses to float64 CPU here.
245
- x = _cpu64(feet)
246
- if x.ndim == 3:
247
- x = x[:, :, None, :]
248
- if x.ndim != 4 or x.shape[-1] != 3:
249
- raise SceneError("bad-feet", f"feet must be (T, F, 3) or (T, F, K, 3), got {tuple(x.shape)}")
250
- if not torch.isfinite(x).all():
251
- raise SceneError("non-finite", "foot positions contain NaN or inf")
252
- t_len, f_len, k_len, _ = x.shape
253
- flat = x.reshape(t_len, f_len * k_len, 3)
254
- if boxes is None:
255
- support = torch.full(flat.shape[:2], float(floor_y), dtype=torch.float64)
256
- else:
257
- support = support_height(flat, SceneBoxes(*(_cpu64(v) for v in boxes)), floor_y=floor_y, step_tol=step_tol)
258
- height = (flat[..., 1] - support).reshape(t_len, f_len, k_len).numpy()
259
- if contact_height is None:
260
- ref = np.percentile(height, 5, axis=0)
261
- else:
262
- ref = np.broadcast_to(_cpu64(contact_height).numpy(), (f_len, k_len))
263
- xz = x[..., [0, 2]].numpy()
264
- speed = np.linalg.norm(np.gradient(xz, axis=0), axis=-1) * fps if t_len > 1 else np.zeros((t_len, f_len, k_len))
265
- z = np.sqrt((np.maximum(height - ref, 0) / height_scale) ** 2 + (speed / speed_scale) ** 2).min(-1) # (T, F)
266
- logit = sharpness * (1 - z)
267
- # -log sigmoid(l) = softplus(-l); cost[..., 0] swing, cost[..., 1] contact.
268
- cost = np.stack((np.logaddexp(0, logit), np.logaddexp(0, -logit)), axis=-1)
269
- switch = -math.log(1 - stay) + math.log(stay)
270
- out = np.zeros((t_len, f_len), dtype=bool)
271
- for f in range(f_len):
272
- acc, back = cost[0, f].copy(), np.zeros((t_len, 2), dtype=np.int64)
273
- for t in range(1, t_len):
274
- nxt = np.empty(2)
275
- for s in range(2):
276
- via = acc + np.where(np.arange(2) == s, 0.0, switch)
277
- back[t, s] = int(np.argmin(via))
278
- nxt[s] = via[back[t, s]] + cost[t, f, s]
279
- acc = nxt
280
- state = int(np.argmin(acc))
281
- for t in range(t_len - 1, -1, -1):
282
- out[t, f] = state == 1
283
- state = back[t, state]
284
- return out
285
-
286
-
287
- def skate_loss(feet: torch.Tensor, stance, fps: float) -> torch.Tensor:
288
- """Sum of squared horizontal velocity (m/s) of foot joints between consecutive
289
- frames that are both stance. feet (T, F, 3) or (T, F, K, 3), stance bool (T, F)."""
290
- if feet.ndim == 3:
291
- feet = feet[:, :, None, :]
292
- stance = torch.as_tensor(stance, dtype=torch.bool, device=feet.device)
293
- both = (stance[1:] & stance[:-1])[..., None] # (T-1, F, 1)
294
- vel = (feet[1:, ..., [0, 2]] - feet[:-1, ..., [0, 2]]) * fps
295
- return (vel.square().sum(-1) * both).sum()
296
-
297
-
298
- def camera_center(camera) -> np.ndarray:
299
- """World camera position from camera.json (`worldToCamera`, OpenCV 4x4) or a 4x4 array."""
300
- matrix = _cpu64(camera["worldToCamera"] if isinstance(camera, dict) else camera).numpy()
301
- if matrix.shape != (4, 4) or not np.all(np.isfinite(matrix)):
302
- raise SceneError("bad-camera", "worldToCamera must be a finite 4x4 matrix")
303
- return -matrix[:3, :3].T @ matrix[:3, 3]
304
-
305
-
306
- @torch.no_grad()
307
- def ray_occlusion(points: torch.Tensor, camera_pos, boxes: SceneBoxes, *, eps: float = 1e-6) -> torch.Tensor:
308
- """bool (T, N): the segment camera -> point passes through a box interior (the point is
309
- hidden behind a box, or inside one). A point on a box's camera-facing face is visible."""
310
- t_len, n = points.shape[:2]
311
- if not boxes.half.shape[0]:
312
- return torch.zeros((t_len, n), dtype=torch.bool, device=points.device)
313
- boxes = _boxes_like(boxes, points)
314
- cam = _cpu64(camera_pos).to(device=points.device, dtype=points.dtype)
315
- origin = to_box_local(cam.expand(t_len, 1, 3), boxes) # (T, 1, B, 3)
316
- d = to_box_local(points, boxes) - origin # (T, N, B, 3)
317
- half = boxes.half[None, None].to(points.dtype)
318
- flat = d.abs() < 1e-12
319
- safe = torch.where(flat, torch.ones_like(d), d)
320
- t0, t1 = (-half - origin) / safe, (half - origin) / safe
321
- inside_slab = origin.abs() < half
322
- lo = torch.where(flat, torch.where(inside_slab, -math.inf, math.inf), torch.minimum(t0, t1))
323
- hi = torch.where(flat, torch.where(inside_slab, math.inf, -math.inf), torch.maximum(t0, t1))
324
- near = torch.clamp(lo.amax(-1), min=0)
325
- far = torch.clamp(hi.amin(-1), max=1)
326
- return (far - near > eps).any(-1)
@@ -1,123 +0,0 @@
1
- """Independent name contracts for cskel27 versus COCO index spaces.
2
-
3
- The synthetic recovery fixture imports the objective's map, so it cannot by
4
- itself catch a shared off-by-one. These assertions use exported rig names and
5
- an independent anatomical correspondence instead of regenerating that map.
6
- """
7
- import json
8
- from pathlib import Path
9
- import warnings
10
-
11
- import numpy as np
12
- import pytest
13
- import torch
14
-
15
- from lr_viterbi import ARM_PAIRS, COCO_NAMES, FACE_PAIRS, LEG_PAIRS, state_permutation
16
- from masks import ANKLES, EXTREMITIES, TORSO_PAIRS, UPPER
17
- from objective import (ARM_TRUST_JOINTS, COCO_JOINTS, JOINT_CAPS_DEG,
18
- articulation_reliability, trust_region_loss)
19
- from rig import State
20
- from scene import FOOT_JOINTS, FOOT_NAMES, foot_points
21
-
22
-
23
- @pytest.fixture(scope='module')
24
- def joint_names():
25
- path = Path.home() / 'cclay-ingest/cozyfit/testdata/rig-y-bot-tpose.npz'
26
- with np.load(path, allow_pickle=False) as rig:
27
- meta = json.loads(bytes(rig['meta_json'].astype(np.uint8)))
28
- names = meta['cskel27Joints']
29
- assert len(names) == 27 and names[0] == 'Hips'
30
- return names
31
-
32
-
33
- def test_coco_correspondence_and_visibility_indices_by_name(joint_names):
34
- expected = ['LeftArm', 'RightArm', 'LeftForeArm', 'RightForeArm', 'LeftHand', 'RightHand',
35
- 'LeftUpLeg', 'RightUpLeg', 'LeftLeg', 'RightLeg', 'LeftFoot', 'RightFoot']
36
- assert [joint_names[j] for j in COCO_JOINTS[5:]] == expected
37
- # Visibility is anatomical: selecting it by this map must hide the matching
38
- # detector-side body landmark, not the next joint along the right arm.
39
- for coco, name in enumerate(expected, start=5):
40
- hidden = torch.zeros(1, 27, dtype=torch.bool)
41
- hidden[0, joint_names.index(name)] = True
42
- assert hidden[:, COCO_JOINTS][0, 5:].nonzero().flatten().tolist() == [coco - 5]
43
-
44
-
45
- def test_trust_weighting_by_exported_bilateral_joint_names(joint_names):
46
- strong = {side + part for side in ('Left', 'Right') for part in ('Shoulder', 'Arm', 'ForeArm', 'Hand')}
47
- assert {joint_names[j] for j in ARM_TRUST_JOINTS} == strong
48
- state = State.identity(1)
49
- initial = state.local_rot_mats()
50
- angle = 0.4
51
- c, s = np.cos(angle), np.sin(angle)
52
- turn = torch.tensor([[c, 0, s], [0, 1, 0], [-s, 0, c]], dtype=torch.float32)
53
- leg = initial.clone(); leg[:, joint_names.index('RightUpLeg')] = turn
54
- unit = trust_region_loss(state.transl, leg, state.transl, initial).item()
55
- assert unit > 0
56
- for joint, name in enumerate(joint_names):
57
- rotations = initial.clone(); rotations[:, joint] = turn
58
- actual = trust_region_loss(state.transl, rotations, state.transl, initial).item()
59
- multiplier = 2 if name == 'Hips' else 4 if name in strong else 1
60
- assert actual == pytest.approx(multiplier * unit), name
61
-
62
-
63
- def test_joint_limit_caps_are_anatomically_symmetric(joint_names):
64
- assert len(JOINT_CAPS_DEG) == len(joint_names)
65
- caps = dict(zip(joint_names, JOINT_CAPS_DEG))
66
- expected = {'Shoulder': 90, 'Arm': 175, 'ForeArm': 175, 'Hand': 110,
67
- 'HandEnd': 150, 'HandThumb1': 180, 'UpLeg': 160, 'Leg': 175, 'Foot': 100, 'ToeBase': 80}
68
- for part, degrees in expected.items():
69
- assert caps['Left' + part] == caps['Right' + part] == degrees
70
-
71
-
72
- def test_foot_and_lr_selections_use_their_own_index_spaces(joint_names):
73
- assert FOOT_NAMES == ('left', 'right')
74
- expected_feet = [['LeftFoot', 'LeftToeBase'], ['RightFoot', 'RightToeBase']]
75
- assert [[joint_names[j] for j in foot] for foot in FOOT_JOINTS] == expected_feet
76
- tagged = torch.arange(27)[None, :, None].expand(1, -1, 3)
77
- selected = foot_points(tagged)[0, :, :, 0].tolist()
78
- assert [[joint_names[j] for j in foot] for foot in selected] == expected_feet
79
- for pairs, parts in [(ARM_PAIRS, ['Shoulder', 'Elbow', 'Wrist']),
80
- (LEG_PAIRS, ['Hip', 'Knee', 'Ankle']), (FACE_PAIRS, ['Eye', 'Ear'])]:
81
- assert [(COCO_NAMES[a], COCO_NAMES[b]) for a, b in pairs] == [('left' + p, 'right' + p) for p in parts]
82
- full = state_permutation('full_swap')
83
- assert all(full[a] == b and full[b] == a for a, b in ARM_PAIRS + LEG_PAIRS + FACE_PAIRS)
84
-
85
-
86
- def test_mask_prompt_indices_by_coco_name():
87
- assert [[COCO_NAMES[j] for j in pair] for pair in TORSO_PAIRS] == [
88
- ['leftShoulder', 'rightShoulder'], ['leftHip', 'rightHip']]
89
- assert [COCO_NAMES[j] for j in EXTREMITIES] == ['nose', 'leftWrist', 'rightWrist', 'leftAnkle', 'rightAnkle']
90
- assert [COCO_NAMES[j] for j in UPPER] == ['nose', 'leftWrist', 'rightWrist']
91
- assert [COCO_NAMES[j] for j in ANKLES] == ['leftAnkle', 'rightAnkle']
92
-
93
-
94
- def test_reliability_lower_median_strict_cuda_determinism():
95
- assert torch.cuda.is_available(), 'Box acceptance requires CUDA'
96
- projected = np.arange(4 * 17 * 2, dtype=np.float32).reshape(4, 17, 2) / 3
97
- assigned = np.zeros((4, 17, 3), np.float32)
98
- assigned[..., 2] = 1
99
- assigned[:, :5, 2] = 0
100
- assigned[1, 6::2, 2] = 0.2
101
- assigned[2, 10:, 2] = 0
102
- assigned[3, :, 2] = 0
103
- seen = np.ones((4, 17), bool)
104
- seen[2, 5] = False
105
- expected = []
106
- for uv, obs, visible in zip(projected, assigned, seen):
107
- valid = visible[5:] & (obs[5:, 2] >= 0.5)
108
- body = (uv - obs[:, :2])[5:][valid]
109
- center = np.sort(body, axis=0)[(len(body) - 1) // 2] if len(body) else np.zeros(2)
110
- excess = np.maximum(np.linalg.norm(uv - obs[:, :2] - center, axis=-1) / 3 - 1, 0) / 2
111
- expected.append(obs[:, 2] * visible / (1 + excess ** 2) ** 2)
112
- enabled = torch.are_deterministic_algorithms_enabled()
113
- warn_only = torch.is_deterministic_algorithms_warn_only_enabled()
114
- try:
115
- torch.use_deterministic_algorithms(True)
116
- with warnings.catch_warnings():
117
- warnings.simplefilter('error', UserWarning)
118
- actual = articulation_reliability(torch.tensor(projected, device='cuda'),
119
- torch.tensor(assigned, device='cuda'),
120
- torch.tensor(seen, device='cuda'), 3)
121
- np.testing.assert_allclose(actual.cpu().numpy(), np.asarray(expected), atol=1e-6, rtol=1e-5)
122
- finally:
123
- torch.use_deterministic_algorithms(enabled, warn_only=warn_only)
@@ -1,195 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import json
4
- import sys
5
- from pathlib import Path
6
-
7
- import numpy as np
8
-
9
- from eval_lr import evaluate_item, main as eval_main, truth_reference_error
10
- from lr_viterbi import (
11
- ARM_PAIRS,
12
- BODY_INDICES,
13
- LEG_PAIRS,
14
- STATE_NAMES,
15
- solve_lr_viterbi,
16
- state_permutation,
17
- )
18
-
19
-
20
- def synthetic_clip(frames: int = 64, seed: int = 17):
21
- rng = np.random.default_rng(seed)
22
- base = np.zeros((frames, 17, 2), dtype=np.float64)
23
- # A moving torso plus distinct, smooth limbs. The labels are deliberately
24
- # geometric only; no colour or appearance signal is present.
25
- for t in range(frames):
26
- phase = 0.13 * t
27
- base[t, 0] = [120 + 1.5 * t, 70]
28
- base[t, 1:5] = [[119, 63], [121, 63], [117, 66], [123, 66]]
29
- base[t, 5] = [100 + 2 * np.sin(phase), 120 + 0.3 * t]
30
- base[t, 6] = [140 - 2 * np.sin(phase), 120 + 0.3 * t]
31
- base[t, 7] = [82 + 3 * np.sin(phase), 155 + 2 * np.cos(phase)]
32
- base[t, 8] = [158 - 3 * np.sin(phase), 155 + 2 * np.cos(phase)]
33
- base[t, 9] = [70 + 4 * np.sin(phase), 190 + 2 * np.cos(phase)]
34
- base[t, 10] = [170 - 4 * np.sin(phase), 190 + 2 * np.cos(phase)]
35
- base[t, 11] = [108 + 1.5 * np.sin(phase), 220 + 0.4 * t]
36
- base[t, 12] = [132 - 1.5 * np.sin(phase), 220 + 0.4 * t]
37
- base[t, 13] = [102 + 2 * np.sin(phase), 270 + 1.5 * np.cos(phase)]
38
- base[t, 14] = [138 - 2 * np.sin(phase), 270 + 1.5 * np.cos(phase)]
39
- base[t, 15] = [96 + 2 * np.sin(phase), 320 + 1.2 * np.cos(phase)]
40
- base[t, 16] = [144 - 2 * np.sin(phase), 320 + 1.2 * np.cos(phase)]
41
- kp = np.concatenate([base, np.ones((frames, 17, 1), dtype=np.float64)], axis=2)
42
- kp[..., :2] += rng.normal(0, 0.15, kp[..., :2].shape)
43
- return base, kp
44
-
45
-
46
- def swap_observations(kp, groups, start, end):
47
- out = kp.copy()
48
- for t in range(start, end + 1):
49
- for left, right in groups:
50
- out[t, [left, right]] = out[t, [right, left]]
51
- return out
52
-
53
-
54
- def assert_corrected(result, projected, observed, start, end, groups):
55
- assignments = result["assignments"]
56
- for t in range(projected.shape[0]):
57
- for left, right in groups:
58
- expected = right if start <= t <= end else left
59
- assert assignments[t, left] == expected
60
- expected = left if start <= t <= end else right
61
- assert assignments[t, right] == expected
62
- corrected = np.take_along_axis(observed[..., :2], assignments[:, :, None], axis=1)
63
- raw_error = np.linalg.norm(observed[..., :2] - projected, axis=2)[:, list(BODY_INDICES)].mean()
64
- corrected_error = np.linalg.norm(corrected - projected, axis=2)[:, list(BODY_INDICES)].mean()
65
- # The detector noise remains after assignment correction; the swap error
66
- # should nevertheless be removed rather than requiring a denoiser here.
67
- assert corrected_error < raw_error * 0.4
68
-
69
-
70
- def test_one_three_seven_frame_swap_runs_are_recovered():
71
- projected, clean = synthetic_clip()
72
- # Use separate clips so each expected state is unambiguous.
73
- for length, state_groups in ((1, ARM_PAIRS), (3, LEG_PAIRS), (7, ARM_PAIRS)):
74
- start = 20
75
- observed = swap_observations(clean, state_groups, start, start + length - 1)
76
- result = solve_lr_viterbi(observed, projected, deltaPx=2.0)
77
- assert_corrected(result, projected, observed, start, start + length - 1, state_groups)
78
- expected_name = "arms_swap" if state_groups is ARM_PAIRS else "legs_swap"
79
- assert all(result["state_names"][s] == expected_name for s in result["path"][start : start + length])
80
-
81
-
82
- def test_iid_noise_has_no_switches():
83
- projected, clean = synthetic_clip(seed=21)
84
- rng = np.random.default_rng(99)
85
- noisy = clean.copy()
86
- noisy[..., :2] += rng.normal(0, 1.0, noisy[..., :2].shape)
87
- result = solve_lr_viterbi(noisy, projected, deltaPx=3.0)
88
- assert result["switches"] == 0
89
- assert np.all(result["path"] == STATE_NAMES.index("identity"))
90
-
91
-
92
- def test_slow_180_degree_turn_is_not_a_swap():
93
- projected, _ = synthetic_clip(frames=80)
94
- fixture = next((parent / "test/fixtures/heading-orient.json" for parent in Path(__file__).resolve().parents if (parent / "test/fixtures/heading-orient.json").exists()), None)
95
- if fixture is not None:
96
- raw = json.loads(fixture.read_text())["items"]["gt/turn"]
97
- angles = []
98
- for x, y, z in raw:
99
- theta = np.linalg.norm([x, y, z])
100
- qw = np.cos(theta / 2)
101
- qy = (y / theta) * np.sin(theta / 2) if theta > 1e-12 else y / 2
102
- angles.append(2 * np.arctan2(qy, qw))
103
- angles = np.unwrap(np.asarray(angles))
104
- projected = np.resize(projected, (len(angles), 17, 2))
105
- else:
106
- # The box runner receives only tools/track/py, so retain a deterministic
107
- # equivalent when the repository fixture is not uploaded.
108
- angles = np.linspace(0, np.pi, projected.shape[0])
109
- turned = projected.copy()
110
- for t, angle in enumerate(angles):
111
- c, s = np.cos(angle), np.sin(angle)
112
- xy = projected[t] - [125, 210]
113
- turned[t] = xy @ np.array([[c, s], [-s, c]]) + [125, 210]
114
- kp = np.concatenate([turned, np.ones((turned.shape[0], 17, 1))], axis=2)
115
- kp[..., :2] += np.random.default_rng(123).normal(0, 0.2, kp[..., :2].shape)
116
- result = solve_lr_viterbi(kp, turned, deltaPx=2.0)
117
- assert result["switches"] == 0
118
- assert np.all(result["path"] == STATE_NAMES.index("identity"))
119
-
120
-
121
- def test_malformed_and_no_evidence_inputs_are_safe():
122
- projected = np.zeros((4, 17, 2), dtype=np.float64)
123
- kp = np.zeros((4, 17, 3), dtype=np.float64)
124
- result = solve_lr_viterbi(kp, projected, deltaPx=2.0)
125
- assert np.all(result["path"] == 0)
126
- assert np.all(result["ambiguous"])
127
- assert np.all(result["margins"] == 0)
128
-
129
- nan_kp = kp.copy()
130
- nan_kp[..., :2] = np.nan
131
- nan_result = solve_lr_viterbi(nan_kp, projected, deltaPx=2.0)
132
- assert np.all(nan_result["path"] == 0)
133
- empty = solve_lr_viterbi(np.empty((0, 17, 3)), np.empty((0, 17, 2)), deltaPx=2.0)
134
- assert empty["path"].size == 0
135
-
136
-
137
- def test_permutations_are_restricted_and_deterministic():
138
- assert np.array_equal(state_permutation("identity"), np.arange(17))
139
- assert state_permutation("arms_swap")[11] == 11
140
- assert state_permutation("legs_swap")[5] == 5
141
- assert state_permutation("full_swap")[1] == 2
142
- assert len(set(tuple(state_permutation(name)) for name in STATE_NAMES)) == 4
143
-
144
-
145
- def test_visibility_is_anatomical_not_permuted_with_detector_labels():
146
- projected = np.zeros((1, 17, 2), dtype=np.float64)
147
- observed = np.zeros((1, 17, 3), dtype=np.float64)
148
- visibility = np.zeros((1, 17), dtype=bool)
149
- # Only anatomical left shoulder is visible. Detector left is wrong by 100px;
150
- # detector right is exactly at the visible anatomical projection.
151
- projected[0, 5] = [0, 0]
152
- observed[0, 5] = [100, 0, 1]
153
- observed[0, 6] = [0, 0, 1]
154
- visibility[0, 5] = True
155
- paths = []
156
- for hidden_right_x in (0, 10000):
157
- projected[0, 6] = [hidden_right_x, 0]
158
- result = solve_lr_viterbi(observed, projected, visibility=visibility, deltaPx=2.0)
159
- paths.append(int(result["path"][0]))
160
- assert int(np.argmin(result["emission"][0])) == STATE_NAMES.index("arms_swap")
161
- assert paths == [STATE_NAMES.index("arms_swap")] * 2
162
-
163
-
164
- def test_hysteresis_keeps_clear_swap_when_unrelated_states_tie():
165
- projected = np.zeros((5, 17, 2), dtype=np.float64)
166
- observed = np.zeros((5, 17, 3), dtype=np.float64)
167
- for left, right in ARM_PAIRS:
168
- projected[:, right, 0] = 100
169
- observed[:, left, 2] = observed[:, right, 2] = 1
170
- observed[:, :, :2] = projected
171
- for left, right in ARM_PAIRS:
172
- observed[2, [left, right]] = observed[2, [right, left]]
173
- result = solve_lr_viterbi(observed, projected, deltaPx=2.0, hysteresis=0.1)
174
- assert result["raw_path"][2] in (STATE_NAMES.index("full_swap"), STATE_NAMES.index("arms_swap"))
175
- assert result["assignments"][2, 5] == 6
176
- assert result["assignments"][2, 6] == 5
177
- assert result["margins"][2] > 0.1
178
-
179
-
180
- def test_eval_rejects_missing_truth_assignment_reference(tmp_path, monkeypatch):
181
- item = {"set": "gt", "name": "walk", "variant": "shaded", "dir": str(tmp_path), "source": "none"}
182
- assert truth_reference_error({}, "gt/walk") is not None
183
- row = evaluate_item(item, {}, tmp_path, 2.0)
184
- assert row["status"] == "missing-truth-reference"
185
-
186
- summary = tmp_path / "summary.json"
187
- approved = tmp_path / "approved.json"
188
- out = tmp_path / "result.json"
189
- summary.write_text(json.dumps({"deltaPx": 2.0}))
190
- approved.write_text(json.dumps({"items": [item]}))
191
- monkeypatch.setattr(sys, "argv", ["eval_lr.py", "--summary", str(summary), "--approved", str(approved), "--obs-root", str(tmp_path), "--out", str(out)])
192
- assert eval_main() == 1
193
- result = json.loads(out.read_text())
194
- assert result["pass"] is False
195
- assert result["items"][0]["status"] == "missing-truth-reference"