cozyclay 2.1.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (583) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +12 -30
  3. package/bin/agent/agent-routes.mjs +40 -113
  4. package/bin/agent/agent-runner.mjs +9 -10
  5. package/bin/agent/cliproxy-provider.mjs +135 -0
  6. package/bin/agent/credential-store.mjs +30 -7
  7. package/bin/agent/pi-tools.mjs +4 -1
  8. package/bin/agent/provider-keys.mjs +32 -0
  9. package/bin/agent/provider-login.mjs +110 -0
  10. package/bin/agent/provider-routes.mjs +99 -0
  11. package/bin/agent/providers.mjs +19 -97
  12. package/bin/agent/script-tool.mjs +17 -4
  13. package/bin/agent/studio-motion-reference.mjs +23 -0
  14. package/bin/agent/studio-prompt.mjs +1 -1
  15. package/bin/agent/studio-rebase-text.mjs +32 -0
  16. package/bin/agent/studio-rebase.mjs +26 -39
  17. package/bin/agent/studio-tools.mjs +9 -28
  18. package/bin/agent/tool-arguments.mjs +49 -0
  19. package/bin/agent/turn-frames.mjs +66 -0
  20. package/bin/codex-auth.mjs +9 -2
  21. package/bin/cozyclay.mjs +10 -116
  22. package/bin/live/cli.mjs +22 -6
  23. package/bin/mcp-runtime.mjs +2 -12
  24. package/bin/package-signature.mjs +0 -4
  25. package/dist/app/index.html +7 -6
  26. package/dist/assets/EXRLoader-CvVIvGVE.js +1 -0
  27. package/dist/assets/analytics-C9sUnndd.js +1 -0
  28. package/dist/assets/app-CCh--1Hc.js +5087 -0
  29. package/dist/assets/app-f-fkV5uP.css +1 -0
  30. package/dist/assets/compile-worker-CkxiD6lH.js +5 -0
  31. package/dist/assets/fflate.module-KbGj5-H6.js +5 -0
  32. package/dist/assets/{first-shot-handoff-BlT33CGf.js → first-shot-handoff-CC4xe4HM.js} +1 -1
  33. package/dist/assets/{landing-BSesrCpL.js → landing-DjHD8u7k.js} +1 -1
  34. package/dist/assets/scene-asset-sync-Hx_FzdOH.js +20 -0
  35. package/dist/assets/scene-asset-sync-_S8NQl5B.css +1 -0
  36. package/dist/assets/workflow-C4yKH1p4.js +179 -0
  37. package/dist/cozyclay-package.json +1 -1
  38. package/dist/index.html +3 -3
  39. package/dist/llms.txt +14 -0
  40. package/dist/sw.js +3 -6
  41. package/dist/workflow/index.html +5 -4
  42. package/mcp/LIVE-PROTOCOL.md +7 -47
  43. package/mcp/README.md +16 -26
  44. package/mcp/live-hub.mjs +6 -10
  45. package/mcp/package.json +1 -3
  46. package/mcp/tool-handlers.mjs +4 -168
  47. package/package.json +3 -22
  48. package/src/App.jsx +174 -2639
  49. package/src/agent-progress.js +79 -0
  50. package/src/agent-turn-mark.js +30 -0
  51. package/src/analytics.js +1 -19
  52. package/src/app-context.js +5 -0
  53. package/src/app-stage.jsx +2 -34
  54. package/src/asset-pane.jsx +6 -5
  55. package/src/camera-block.js +21 -0
  56. package/src/camera-direction.js +120 -0
  57. package/src/character-kind.js +3 -9
  58. package/src/command-bus.js +8 -6
  59. package/src/commands/cast.js +5 -34
  60. package/src/commands/elements/character.js +1 -1
  61. package/src/commands/elements/motion.js +1 -6
  62. package/src/commands/elements/object.js +6 -2
  63. package/src/commands/elements/shot.js +4 -1
  64. package/src/commands/elements.js +38 -14
  65. package/src/commands/index.js +3 -1
  66. package/src/commands/kits.js +37 -0
  67. package/src/commands/motion-timing.js +57 -0
  68. package/src/commands/motion.js +9 -152
  69. package/src/commands/motion3-compile.js +65 -0
  70. package/src/commands/motion3.js +207 -0
  71. package/src/commands/objects.js +6 -4
  72. package/src/commands/shot.js +49 -11
  73. package/src/commands/stage.js +14 -1
  74. package/src/commands/view.js +4 -1
  75. package/src/document-mirrors.js +42 -0
  76. package/src/document-store.js +4 -0
  77. package/src/domains/cast.js +19 -194
  78. package/src/domains/motion/motion-range-pin-rules.js +1 -1
  79. package/src/domains/motion/take-source.js +51 -0
  80. package/src/domains/motion.js +60 -1838
  81. package/src/domains/scenes.js +12 -55
  82. package/src/domains/shots.js +2 -2
  83. package/src/domains/stage.js +4 -2
  84. package/src/draft-ghost.js +46 -0
  85. package/src/draft-ghost.jsx +129 -0
  86. package/src/dualview.jsx +4 -3
  87. package/src/effects/EffectsLayer.jsx +112 -0
  88. package/src/effects/effect-meshes.js +232 -0
  89. package/src/effects/effect-record.js +103 -0
  90. package/src/effects/emitter.js +59 -0
  91. package/src/effects/simulate.js +261 -0
  92. package/src/effects/state.js +59 -0
  93. package/src/execution-telemetry.js +2 -3
  94. package/src/first-success-guide.jsx +5 -5
  95. package/src/ghost-set.js +257 -0
  96. package/src/held-object-edit.jsx +113 -0
  97. package/src/held-object-pose.js +175 -0
  98. package/src/hierarchy-model.js +1 -1
  99. package/src/hierarchy-panel.jsx +1 -1
  100. package/src/imported-mesh.jsx +103 -0
  101. package/src/kits/held.js +84 -0
  102. package/src/kits/index.js +70 -0
  103. package/src/kits/smoking.js +57 -0
  104. package/src/light-blockers.js +93 -0
  105. package/src/motion/beat-board-model.js +84 -0
  106. package/src/motion/beat-board.css +124 -0
  107. package/src/motion/beat-board.jsx +59 -0
  108. package/src/motion/center-of-mass.js +66 -0
  109. package/src/motion/fix-collisions.js +850 -0
  110. package/src/motion/physics-review.js +118 -0
  111. package/src/{ardy → motion}/physics-support.js +1 -76
  112. package/src/motion/rig-snapshot.js +22 -0
  113. package/src/motion/take-segments.jsx +140 -0
  114. package/src/{ardy → motion}/timeline-coordinates.js +0 -21
  115. package/src/{ardy → motion}/timeline.css +1 -2
  116. package/src/{ardy → motion}/timeline.jsx +48 -419
  117. package/src/motion/waypoints.js +76 -0
  118. package/src/motion3/beats.js +83 -0
  119. package/src/motion3/body.js +158 -0
  120. package/src/motion3/bones.js +47 -0
  121. package/src/motion3/carried.js +47 -0
  122. package/src/motion3/compile-job.js +75 -0
  123. package/src/motion3/compile-worker.js +10 -0
  124. package/src/motion3/compiler/anchors.js +105 -0
  125. package/src/motion3/compiler/arm-orient.js +128 -0
  126. package/src/motion3/compiler/channels.js +125 -0
  127. package/src/motion3/compiler/compile.js +205 -0
  128. package/src/motion3/compiler/gait-rhythm.js +97 -0
  129. package/src/motion3/compiler/grip.js +171 -0
  130. package/src/motion3/compiler/head-steady.js +48 -0
  131. package/src/motion3/compiler/hold.js +134 -0
  132. package/src/motion3/compiler/idle.js +113 -0
  133. package/src/motion3/compiler/limbs.js +170 -0
  134. package/src/motion3/compiler/math.js +72 -0
  135. package/src/motion3/compiler/pose.js +162 -0
  136. package/src/motion3/compiler/quat.js +49 -0
  137. package/src/motion3/compiler/skeleton.js +74 -0
  138. package/src/motion3/compiler/validate-common.js +32 -0
  139. package/src/motion3/compiler/validate-goals.js +118 -0
  140. package/src/motion3/compiler/validate-objects.js +143 -0
  141. package/src/motion3/compiler/validate-parts.js +116 -0
  142. package/src/motion3/compiler/validate.js +122 -0
  143. package/src/motion3/compiler/vocab.js +73 -0
  144. package/src/motion3/compiler/walk-pelvis.js +275 -0
  145. package/src/motion3/compiler/walk.js +207 -0
  146. package/src/motion3/compiler/wrist-clamp.js +32 -0
  147. package/src/motion3/delta.js +47 -0
  148. package/src/motion3/describe.js +174 -0
  149. package/src/motion3/docs.js +27 -0
  150. package/src/motion3/edit-carry.js +21 -0
  151. package/src/motion3/fix/ease.js +16 -0
  152. package/src/motion3/fix/ground.js +37 -0
  153. package/src/motion3/fix/index.js +52 -0
  154. package/src/motion3/fix/push.js +71 -0
  155. package/src/motion3/fix/reach.js +46 -0
  156. package/src/motion3/fix/retime.js +79 -0
  157. package/src/motion3/fix/self.js +211 -0
  158. package/src/motion3/fix/slide.js +70 -0
  159. package/src/motion3/fix/turn.js +51 -0
  160. package/src/motion3/follow.js +39 -0
  161. package/src/motion3/free-motion.js +173 -0
  162. package/src/motion3/grip-choice.js +130 -0
  163. package/src/motion3/hold-plan.js +157 -0
  164. package/src/motion3/json-patch.js +77 -0
  165. package/src/motion3/key-aim.js +105 -0
  166. package/src/motion3/key-edit.js +82 -0
  167. package/src/motion3/lenient.js +127 -0
  168. package/src/motion3/object-keys.js +236 -0
  169. package/src/motion3/object-pose.js +182 -0
  170. package/src/motion3/object-tracks.js +12 -0
  171. package/src/motion3/pipeline.js +102 -0
  172. package/src/motion3/playback.js +51 -0
  173. package/src/motion3/probes.js +91 -0
  174. package/src/motion3/qa/body-checks.js +34 -0
  175. package/src/motion3/qa/check-names.js +14 -0
  176. package/src/motion3/qa/checks.js +121 -0
  177. package/src/motion3/qa/contact-checks.js +51 -0
  178. package/src/motion3/qa/explain.js +80 -0
  179. package/src/motion3/qa/fmt.js +5 -0
  180. package/src/motion3/qa/foot-checks.js +72 -0
  181. package/src/motion3/qa/geometry.js +77 -0
  182. package/src/motion3/qa/goals.js +191 -0
  183. package/src/motion3/qa/ground.js +60 -0
  184. package/src/motion3/qa/hints-contact.js +179 -0
  185. package/src/motion3/qa/hints-goal.js +160 -0
  186. package/src/motion3/qa/hints-knee.js +24 -0
  187. package/src/motion3/qa/hints-motion.js +209 -0
  188. package/src/motion3/qa/hints-object.js +91 -0
  189. package/src/motion3/qa/index.js +42 -0
  190. package/src/motion3/qa/key-checks.js +82 -0
  191. package/src/motion3/qa/near.js +96 -0
  192. package/src/motion3/qa/object-checks.js +135 -0
  193. package/src/motion3/qa/patch.js +161 -0
  194. package/src/motion3/qa/severity.js +38 -0
  195. package/src/motion3/qa/spec-checks.js +65 -0
  196. package/src/motion3/qa/style.js +218 -0
  197. package/src/motion3/qa/wrist-checks.js +69 -0
  198. package/src/motion3/qa/wrist-obs.js +60 -0
  199. package/src/motion3/refs.js +120 -0
  200. package/src/motion3/repair-tune.js +121 -0
  201. package/src/motion3/repair.js +83 -0
  202. package/src/motion3/report.js +67 -0
  203. package/src/motion3/rigs/index.js +14 -0
  204. package/src/motion3/rigs/x-bot-tpose-body.json +1 -0
  205. package/src/motion3/rigs/x-bot-tpose.json +1 -0
  206. package/src/motion3/rigs/y-bot-tpose-body.json +1 -0
  207. package/src/motion3/rigs/y-bot-tpose.json +1 -0
  208. package/src/motion3/scene.js +60 -0
  209. package/src/motion3/schema/brief.js +89 -0
  210. package/src/motion3/schema/fields.js +44 -0
  211. package/src/motion3/schema/spec-examples.js +46 -0
  212. package/src/motion3/schema/topics.js +111 -0
  213. package/src/motion3/schema/types.js +147 -0
  214. package/src/motion3/search.js +117 -0
  215. package/src/motion3/second-hand.js +112 -0
  216. package/src/motion3/skills/basic.js +82 -0
  217. package/src/motion3/skills/book.js +76 -0
  218. package/src/motion3/skills/car.js +145 -0
  219. package/src/motion3/skills/frames-auto.js +29 -0
  220. package/src/motion3/skills/index.js +83 -0
  221. package/src/motion3/skills/info.js +65 -0
  222. package/src/motion3/skills/smoke-kit.js +109 -0
  223. package/src/motion3/skills/smoke.js +160 -0
  224. package/src/motion3/spec-store.js +59 -0
  225. package/src/motion3/take-edit.js +155 -0
  226. package/src/motion3/take.js +63 -0
  227. package/src/motion3/timeline.js +127 -0
  228. package/src/motion3/views.js +121 -0
  229. package/src/motion3/walk-plan.js +119 -0
  230. package/src/object-catalog.jsx +7 -1
  231. package/src/object-colors.js +98 -0
  232. package/src/object-gizmo-parts.js +115 -0
  233. package/src/object-gizmo.jsx +37 -122
  234. package/src/object-glow.js +139 -0
  235. package/src/object-hidden.js +33 -0
  236. package/src/object-look.js +196 -0
  237. package/src/object-size.js +10 -0
  238. package/src/object-travel.js +71 -6
  239. package/src/panels/CameraPanel.jsx +7 -7
  240. package/src/panels/HdrEnvironmentControls.jsx +59 -0
  241. package/src/panels/LightPanel.jsx +25 -4
  242. package/src/panels/ObjectLookPanel.jsx +88 -0
  243. package/src/panels/PoseImagePanel.jsx +1 -1
  244. package/src/panels/PosePanel.jsx +2 -17
  245. package/src/panels/RigControlPanel.jsx +5 -53
  246. package/src/panels/SubjectBox.jsx +4 -1
  247. package/src/panels/details.css +14 -0
  248. package/src/posestudio.jsx +4 -27
  249. package/src/project-browser.jsx +3 -4
  250. package/src/project-resources.js +18 -31
  251. package/src/project.js +13 -104
  252. package/src/props.jsx +25 -280
  253. package/src/rail-dot-drag.js +23 -0
  254. package/src/render-passes.js +10 -0
  255. package/src/resource-status.jsx +1 -0
  256. package/src/result-modal.jsx +4 -19
  257. package/src/room.jsx +20 -12
  258. package/src/scene-assets.js +8 -6
  259. package/src/scene-light-place.js +72 -0
  260. package/src/scene-lights.jsx +148 -0
  261. package/src/scene-objects.js +31 -139
  262. package/src/scenes.js +15 -105
  263. package/src/semantic-edit.js +2 -5
  264. package/src/set-pieces.jsx +183 -0
  265. package/src/settings-menu.jsx +3 -11
  266. package/src/shell/BottomDock.jsx +11 -53
  267. package/src/shell/CompositionGuideButton.jsx +20 -0
  268. package/src/shell/DetailsSlot.jsx +16 -200
  269. package/src/shell/GhostsMenuGroup.jsx +74 -0
  270. package/src/shell/LibrarySlot.jsx +1 -3
  271. package/src/shell/MenuBar.jsx +3 -3
  272. package/src/shell/PreferencesDialog.jsx +15 -264
  273. package/src/shell/PreferencesSlot.jsx +2 -8
  274. package/src/shell/ShotPreviewHeader.jsx +17 -0
  275. package/src/shell/TopBar.jsx +5 -83
  276. package/src/shell/ViewportToolbar.jsx +23 -93
  277. package/src/shell/agent-glass.css +19 -6
  278. package/src/shell/details-selection.js +42 -0
  279. package/src/shell/ghosts-menu.css +22 -0
  280. package/src/shell/glass-regions.css +29 -3
  281. package/src/shell/preferences.css +6 -1
  282. package/src/shell/viewport.css +12 -0
  283. package/src/shot-authoring.js +4 -7
  284. package/src/soft-shadows.js +37 -0
  285. package/src/stage-attach-frames.js +12 -12
  286. package/src/stage-canvas-rigs.jsx +3 -3
  287. package/src/stage-capture.jsx +42 -8
  288. package/src/stage-characters.jsx +22 -216
  289. package/src/stage-editor-gizmos.jsx +6 -6
  290. package/src/stage-environment.js +93 -0
  291. package/src/stage-hdr-builtins.js +107 -0
  292. package/src/stage-hdr-image.js +116 -0
  293. package/src/stage-hdr-library.js +70 -0
  294. package/src/stage-hdr-map.js +94 -0
  295. package/src/stage-hdr.js +130 -0
  296. package/src/stage-lighting.js +149 -0
  297. package/src/stage-occlusion.js +210 -0
  298. package/src/stage-sky.jsx +159 -0
  299. package/src/stage-startup-defaults.js +5 -9
  300. package/src/studio-actions.js +12 -14
  301. package/src/studio-agent-commands.js +46 -16
  302. package/src/studio-agent-motion.js +9 -9
  303. package/src/studio-agent-protocol.js +46 -73
  304. package/src/studio-app-binding.js +29 -45
  305. package/src/studio-asset-list.js +17 -0
  306. package/src/studio-contact-sheet.js +37 -5
  307. package/src/studio-elements.js +27 -7
  308. package/src/studio-entity-bounds.js +18 -0
  309. package/src/studio-error-codes.js +8 -0
  310. package/src/studio-export-framing.js +152 -0
  311. package/src/studio-lighting-check.js +103 -0
  312. package/src/studio-motion-visual.js +150 -0
  313. package/src/studio-schema-errors.js +80 -0
  314. package/src/studio-support-gap.js +74 -0
  315. package/src/styles.css +1 -19
  316. package/src/timeline-defaults.js +5 -0
  317. package/src/timeline-extent.js +8 -20
  318. package/src/tutorial-walk.js +17 -0
  319. package/src/ui-scrub.js +1 -1
  320. package/src/use-shot-camera-tracks.js +12 -0
  321. package/src/view-ghosts.js +97 -0
  322. package/src/view-ghosts.jsx +49 -0
  323. package/src/workflow/AgentPanel.jsx +47 -166
  324. package/src/workflow/AgentProgressCard.jsx +107 -0
  325. package/src/workflow/AgentReplyText.jsx +37 -0
  326. package/src/workflow/AgentTurnSteps.jsx +84 -0
  327. package/src/workflow/LoginPanel.jsx +140 -0
  328. package/src/workflow/WorkflowBuilder.jsx +7 -23
  329. package/src/workflow/agent-client.js +54 -51
  330. package/src/workflow/agent-panel.css +71 -1
  331. package/src/workflow/agent-progress-card.css +167 -0
  332. package/src/workflow/agent-progress-view.js +47 -0
  333. package/src/workflow/agent-turn-steps.css +113 -0
  334. package/src/workflow/agent-turn.js +191 -0
  335. package/src/workflow/login-client.js +34 -0
  336. package/src/workflow/motion-input.js +18 -31
  337. package/src/workflow/node-schema.js +1 -1
  338. package/src/workflow/reply-markdown.js +69 -0
  339. package/src/workflow/scene-asset-sync.js +8 -8
  340. package/tools/agent-session-stats.mjs +61 -0
  341. package/tools/dev-full.mjs +5 -56
  342. package/tools/eslint-baseline.json +12 -13
  343. package/tools/headless-studio.mjs +85 -0
  344. package/tools/knip-baseline.json +0 -179
  345. package/tools/loc-baseline.json +33 -83
  346. package/tools/motion3/extract-body.mjs +103 -0
  347. package/tools/motion3/extract-rig.mjs +85 -0
  348. package/tools/process-supervisor.mjs +0 -88
  349. package/tools/run-tests.mjs +9 -124
  350. package/bin/agent/motion-runtime.mjs +0 -76
  351. package/dist/ardy/cskel27-rest.json +0 -557
  352. package/dist/assets/analytics-B41FmPYI.js +0 -1
  353. package/dist/assets/app-Do-Og5Pu.css +0 -1
  354. package/dist/assets/app-DwP-tl2x.js +0 -4920
  355. package/dist/assets/config-Bx7pYsD9.js +0 -1
  356. package/dist/assets/config-DeoFTYzb.css +0 -1
  357. package/dist/assets/demo-Dt0qZfS7.js +0 -1
  358. package/dist/assets/scene-asset-sync-BCWDJB1d.js +0 -19
  359. package/dist/assets/scene-asset-sync-DjJ_cJ2R.css +0 -1
  360. package/dist/assets/ticket-jqMhqL88.js +0 -1
  361. package/dist/assets/workflow-Br-K7Srs.js +0 -179
  362. package/dist/demo/walk-then-stop.npz +0 -0
  363. package/mcp/ardy-prompts.mjs +0 -218
  364. package/src/ardy/auto-fix-panel.css +0 -108
  365. package/src/ardy/auto-physics.js +0 -1194
  366. package/src/ardy/client.js +0 -153
  367. package/src/ardy/convert.js +0 -313
  368. package/src/ardy/cskel27-neutral.js +0 -39
  369. package/src/ardy/cskel27.js +0 -77
  370. package/src/ardy/export.js +0 -165
  371. package/src/ardy/fix-collisions.js +0 -2257
  372. package/src/ardy/foot-lock.js +0 -230
  373. package/src/ardy/motion-calibration.js +0 -118
  374. package/src/ardy/motion-edit.js +0 -213
  375. package/src/ardy/motion-url.js +0 -55
  376. package/src/ardy/mp4-duration.js +0 -102
  377. package/src/ardy/npz.js +0 -595
  378. package/src/ardy/physics-panel.css +0 -21
  379. package/src/ardy/physics-panel.jsx +0 -89
  380. package/src/ardy/physics-refine.js +0 -111
  381. package/src/ardy/physics-review.js +0 -567
  382. package/src/ardy/physics-temporal.js +0 -54
  383. package/src/ardy/platform-fit-panel.css +0 -64
  384. package/src/ardy/platform-fit-panel.jsx +0 -41
  385. package/src/ardy/platform-fit.js +0 -326
  386. package/src/ardy/playback.js +0 -711
  387. package/src/ardy/pose-pin.js +0 -97
  388. package/src/ardy/prompt-clips.js +0 -30
  389. package/src/ardy/retime.js +0 -211
  390. package/src/ardy/root-drop.js +0 -369
  391. package/src/ardy/timeline-resize.js +0 -13
  392. package/src/ardy/to-cskel27.js +0 -225
  393. package/src/ardy/trim.js +0 -33
  394. package/src/ardy/waypoints.js +0 -369
  395. package/src/ardy-bridge-contract.js +0 -281
  396. package/src/domains/motion/motion-video-capture.js +0 -664
  397. package/src/i2v-motion-client.js +0 -77
  398. package/src/i2v-motion-studio.jsx +0 -180
  399. package/src/line-edit.js +0 -1348
  400. package/src/motion/generation.js +0 -105
  401. package/src/motion-readiness-ui.jsx +0 -67
  402. package/src/motion-readiness.js +0 -40
  403. package/src/motion-resources.js +0 -177
  404. package/src/motion-store.js +0 -65
  405. package/src/motion-trail.js +0 -601
  406. package/src/multimodel-ingest.js +0 -394
  407. package/src/panels/PromptBlocksPanel.jsx +0 -444
  408. package/src/panels/VideoCapturePanel.jsx +0 -181
  409. package/src/pose-extract/browser.js +0 -42
  410. package/src/pose-extract/detector.js +0 -100
  411. package/src/pose-extract/fit.js +0 -227
  412. package/src/pose-extract/image-frame.js +0 -51
  413. package/src/pose-extract/index.js +0 -58
  414. package/src/pose-extract/keyframes.js +0 -74
  415. package/src/pose-extract/landmarks.js +0 -162
  416. package/src/pose-extract/mirror.js +0 -153
  417. package/src/pose-extract/take.js +0 -131
  418. package/src/pose-extract/video-frames.js +0 -91
  419. package/src/take-recipe.js +0 -165
  420. package/src/trail-key-conflicts.js +0 -39
  421. package/src/trail-pick.js +0 -59
  422. package/src/use-i2v-capture-modal.js +0 -15
  423. package/tools/ardy/BRIDGE.md +0 -37
  424. package/tools/ardy/GVHMR-NPZ.md +0 -68
  425. package/tools/ardy/README.md +0 -124
  426. package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
  427. package/tools/ardy/artifacts.mjs +0 -34
  428. package/tools/ardy/bridge.mjs +0 -1732
  429. package/tools/ardy/bvh-cskel27.mjs +0 -1265
  430. package/tools/ardy/cclay_constrained_generate.py +0 -561
  431. package/tools/ardy/cclay_gvhmr_worker.py +0 -264
  432. package/tools/ardy/cclay_motion_edit.py +0 -510
  433. package/tools/ardy/cclay_sequence_generate.py +0 -705
  434. package/tools/ardy/cclay_worker.py +0 -304
  435. package/tools/ardy/dump-npz.py +0 -205
  436. package/tools/ardy/extract-rest.mjs +0 -299
  437. package/tools/ardy/extract.mjs +0 -507
  438. package/tools/ardy/footage.mjs +0 -467
  439. package/tools/ardy/gvhmr-floor.mjs +0 -102
  440. package/tools/ardy/gvhmr-to-cskel27.mjs +0 -21
  441. package/tools/ardy/gvhmr_fastpath.py +0 -168
  442. package/tools/ardy/gvhmr_trajectory.py +0 -240
  443. package/tools/ardy/merge-text-encoder.py +0 -160
  444. package/tools/ardy/mocap-metrics.mjs +0 -200
  445. package/tools/ardy/mocap-quality-gate.mjs +0 -110
  446. package/tools/ardy/motion-stabilize.mjs +0 -348
  447. package/tools/ardy/npz.mjs +0 -414
  448. package/tools/ardy/pose-to-npz.mjs +0 -106
  449. package/tools/ardy/prompt-limits.mjs +0 -5
  450. package/tools/ardy/run-edit-on-box.sh +0 -73
  451. package/tools/ardy/run-local.mjs +0 -233
  452. package/tools/ardy/run-on-box.sh +0 -593
  453. package/tools/ardy/run-sequence-on-box.sh +0 -177
  454. package/tools/ardy/runners/gvhmr-worker.mjs +0 -206
  455. package/tools/ardy/runners/index.mjs +0 -18
  456. package/tools/ardy/runners/local.mjs +0 -455
  457. package/tools/ardy/runners/proc.mjs +0 -125
  458. package/tools/ardy/runners/remote.mjs +0 -252
  459. package/tools/ardy/setup-local.mjs +0 -189
  460. package/tools/ardy/setup-text-encoder-on-box.sh +0 -93
  461. package/tools/ardy/setup-text-encoder.py +0 -306
  462. package/tools/ardy/smpl-cskel27.mjs +0 -178
  463. package/tools/ardy/visual-qa.mjs +0 -196
  464. package/tools/ardy/vq-car.mjs +0 -52
  465. package/tools/bench/EXP3.md +0 -61
  466. package/tools/bench/cclay_bench_extract_incam.py +0 -125
  467. package/tools/bench/cclay_bench_extract_obs.py +0 -204
  468. package/tools/bench/cclay_bench_runner.py +0 -42
  469. package/tools/bench/cube-contact.mjs +0 -162
  470. package/tools/bench/exp3.mjs +0 -152
  471. package/tools/bench/extract-bench-lib.mjs +0 -216
  472. package/tools/bench/extract-bench.mjs +0 -302
  473. package/tools/bench/fal-generate.mjs +0 -88
  474. package/tools/bench/fit/README.md +0 -189
  475. package/tools/bench/fit/camera.mjs +0 -59
  476. package/tools/bench/fit/contact.mjs +0 -419
  477. package/tools/bench/fit/footlock.mjs +0 -234
  478. package/tools/bench/fit/motion.mjs +0 -85
  479. package/tools/bench/fit/pin.mjs +0 -27
  480. package/tools/bench/fit/remote.mjs +0 -105
  481. package/tools/bench/fit-bench.mjs +0 -126
  482. package/tools/bench/fit-sanity.mjs +0 -45
  483. package/tools/bench/metrics.mjs +0 -313
  484. package/tools/bench/obs/depth.mjs +0 -258
  485. package/tools/bench/obs/extrinsics.mjs +0 -175
  486. package/tools/bench/obs/fit_mannequin_betas.py +0 -340
  487. package/tools/bench/obs/ground.mjs +0 -294
  488. package/tools/bench/obs/heading.mjs +0 -151
  489. package/tools/bench/obs/ladder.mjs +0 -555
  490. package/tools/bench/obs/mannequin-betas.json +0 -124
  491. package/tools/bench/obs/remote.mjs +0 -83
  492. package/tools/bench/obs/rest_joints.py +0 -52
  493. package/tools/bench/obs/ybot-targets.mjs +0 -49
  494. package/tools/bench/obs-bench.mjs +0 -520
  495. package/tools/bench/score.mjs +0 -343
  496. package/tools/bench/summarize.mjs +0 -86
  497. package/tools/demo-worker/README.md +0 -137
  498. package/tools/demo-worker/api-client.mjs +0 -233
  499. package/tools/demo-worker/generate.mjs +0 -301
  500. package/tools/demo-worker/index.mjs +0 -466
  501. package/tools/demo-worker/sign-core.mjs +0 -112
  502. package/tools/demo-worker/sign.mjs +0 -161
  503. package/tools/gt-render/browser.mjs +0 -159
  504. package/tools/gt-render/camera-math.mjs +0 -276
  505. package/tools/gt-render/page.mjs +0 -267
  506. package/tools/gt-render/render.mjs +0 -514
  507. package/tools/gt-render/scene-box.mjs +0 -31
  508. package/tools/gt-render/take-transform.mjs +0 -87
  509. package/tools/kimodo/compare-seam.mjs +0 -108
  510. package/tools/kimodo/constraints.mjs +0 -244
  511. package/tools/kimodo/edit.mjs +0 -259
  512. package/tools/kimodo/effector-constraints.mjs +0 -200
  513. package/tools/kimodo/generate.mjs +0 -646
  514. package/tools/kimodo/local-output.mjs +0 -265
  515. package/tools/kimodo/measure-pose.mjs +0 -65
  516. package/tools/kimodo/measure-preserve.mjs +0 -899
  517. package/tools/kimodo/measure-waypoints.mjs +0 -55
  518. package/tools/kimodo/mlx-generate.py +0 -111
  519. package/tools/kimodo/pose-constraints.mjs +0 -248
  520. package/tools/kimodo/preserve-mask.mjs +0 -543
  521. package/tools/kimodo/read-npz.mjs +0 -189
  522. package/tools/kimodo/run-edit-on-box.mjs +0 -155
  523. package/tools/kimodo/run-sequence-on-box.mjs +0 -178
  524. package/tools/kimodo/runner.mjs +0 -284
  525. package/tools/kimodo/seam-batch.mjs +0 -77
  526. package/tools/kimodo/setup-local.mjs +0 -30
  527. package/tools/kimodo/setup-on-box.sh +0 -339
  528. package/tools/kimodo/soma77-to-cskel27.mjs +0 -305
  529. package/tools/morphgs/assets/gen_truth.py +0 -39
  530. package/tools/morphgs/assets/mesh_ori_rig.txt +0 -27
  531. package/tools/morphgs/assets/playback-check.mjs +0 -30
  532. package/tools/morphgs/demo-gate.sh +0 -26
  533. package/tools/morphgs/fbx2morphgs.mjs +0 -68
  534. package/tools/morphgs/morphgs-to-cskel27.mjs +0 -105
  535. package/tools/morphgs/patches/preprocess_src-none-mode.patch +0 -76
  536. package/tools/morphgs/setup-on-cluster.sh +0 -126
  537. package/tools/projflow/driver.py +0 -1069
  538. package/tools/projflow/generate.mjs +0 -805
  539. package/tools/projflow/hml22-to-cskel27.mjs +0 -735
  540. package/tools/projflow/line-edit-job.mjs +0 -547
  541. package/tools/projflow/replay.mjs +0 -452
  542. package/tools/projflow/runner.mjs +0 -269
  543. package/tools/projflow/service.mjs +0 -742
  544. package/tools/track/DESIGN.md +0 -513
  545. package/tools/track/backfill-provenance.mjs +0 -116
  546. package/tools/track/budget.mjs +0 -8
  547. package/tools/track/check-rig.mjs +0 -209
  548. package/tools/track/diagnostics.schema.json +0 -60
  549. package/tools/track/export-rig.mjs +0 -256
  550. package/tools/track/fallback.mjs +0 -7
  551. package/tools/track/fk-parity-fixture.mjs +0 -161
  552. package/tools/track/gate.mjs +0 -349
  553. package/tools/track/masks.mjs +0 -318
  554. package/tools/track/metrics.mjs +0 -201
  555. package/tools/track/publish-obs.mjs +0 -108
  556. package/tools/track/py/check_env.py +0 -61
  557. package/tools/track/py/eval_lr.py +0 -229
  558. package/tools/track/py/lr_viterbi.py +0 -294
  559. package/tools/track/py/masks.py +0 -405
  560. package/tools/track/py/objective.py +0 -382
  561. package/tools/track/py/rig.py +0 -508
  562. package/tools/track/py/scene.py +0 -326
  563. package/tools/track/py/test_joint_indices.py +0 -123
  564. package/tools/track/py/test_lr_viterbi.py +0 -195
  565. package/tools/track/py/test_masks.py +0 -229
  566. package/tools/track/py/test_rig.py +0 -288
  567. package/tools/track/py/test_scene.py +0 -354
  568. package/tools/track/py/test_track.py +0 -328
  569. package/tools/track/py/track.py +0 -411
  570. package/tools/track/remote.mjs +0 -140
  571. package/tools/track/rig-dump.mjs +0 -227
  572. package/tools/track/run-box-tests.mjs +0 -14
  573. package/tools/track/run-box.mjs +0 -168
  574. package/tools/track/setup-box.sh +0 -59
  575. package/tools/track/study-2d.mjs +0 -728
  576. /package/src/{ardy → motion}/collision-blockers.js +0 -0
  577. /package/src/{ardy → motion}/ground.js +0 -0
  578. /package/src/{ardy → motion}/ik-drag.js +0 -0
  579. /package/src/{ardy → motion}/ik-key-json.js +0 -0
  580. /package/src/{ardy → motion}/ik.js +0 -0
  581. /package/src/{ardy → motion}/physics-surface.js +0 -0
  582. /package/src/{ardy → motion}/playback-clock.js +0 -0
  583. /package/src/{ardy → motion}/range-pin.js +0 -0
@@ -1,52 +0,0 @@
1
- import { mkdirSync, writeFileSync } from "node:fs";
2
- const OUT = "/tmp/visual-qa-car"; mkdirSync(OUT, { recursive: true });
3
- const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
4
- async function newTab() {
5
- const res = await fetch("http://127.0.0.1:9222/json/new?about:blank", { method: "PUT" });
6
- const target = await res.json();
7
- const ws = new WebSocket(target.webSocketDebuggerUrl);
8
- await new Promise((res2, rej) => { ws.onopen = res2; ws.onerror = rej; });
9
- let id = 0; const pending = new Map();
10
- const errors = [];
11
- ws.onmessage = (event) => {
12
- const msg = JSON.parse(event.data);
13
- if (msg.id && pending.has(msg.id)) {
14
- const { resolve, reject } = pending.get(msg.id); pending.delete(msg.id);
15
- if (msg.error) reject(new Error(msg.error.message)); else resolve(msg.result);
16
- } else if (msg.method === "Runtime.exceptionThrown") {
17
- errors.push(msg.params.exceptionDetails.text + " " + (msg.params.exceptionDetails.exception?.description ?? ""));
18
- }
19
- };
20
- const send = (method, params = {}) => new Promise((resolve, reject) => {
21
- const msgId = ++id; pending.set(msgId, { resolve, reject });
22
- ws.send(JSON.stringify({ id: msgId, method, params }));
23
- });
24
- return { ws, send, errors };
25
- }
26
- async function evaluate(tab, expression) {
27
- const res = await tab.send("Runtime.evaluate", { expression, awaitPromise: true, returnByValue: true });
28
- if (res.exceptionDetails) throw new Error(res.exceptionDetails.exception?.description || res.exceptionDetails.text);
29
- return res.result.value;
30
- }
31
- async function shot(tab, name) {
32
- const res = await tab.send("Page.captureScreenshot", { format: "png" });
33
- writeFileSync(`${OUT}/${name}.png`, Buffer.from(res.data, "base64"));
34
- console.log(`saved ${OUT}/${name}.png`);
35
- }
36
- const tab = await newTab();
37
- await tab.send("Page.enable"); await tab.send("Runtime.enable");
38
- await tab.send("Network.enable");
39
- await tab.send("Network.setCacheDisabled", { cacheDisabled: true });
40
- await tab.send("Page.navigate", { url: "http://127.0.0.1:5180/app/?v=" + Date.now() });
41
- await sleep(7000);
42
- await shot(tab, "car-default-view");
43
- // wide preset for a broader read of the set
44
- await evaluate(tab, `(() => {
45
- const wide = [...document.querySelectorAll("button")].find(b => /^wide$/i.test(b.textContent.trim()));
46
- wide?.click(); return true;
47
- })()`);
48
- await sleep(900);
49
- await shot(tab, "car-wide-view");
50
- if (tab.errors.length) console.log("--- exceptions ---\n" + tab.errors.join("\n"));
51
- tab.ws.close();
52
- console.log("done");
@@ -1,61 +0,0 @@
1
- # Cube-contact validation (#454)
2
-
3
- Run from this checkout. Requires Node, ffmpeg/ffprobe, Chrome, and installed npm dependencies. Live extraction additionally requires the existing GVHMR installation over SSH. Never run two GPU extractions concurrently.
4
-
5
- ```sh
6
- E=/Users/yun/ccFalToMocap/evidence/exp3
7
- node tools/bench/exp3.mjs --root "$E" --stage gt
8
- node tools/bench/exp3.mjs --root "$E" --stage gt-path --host yun@ubuntu-baremetal
9
- node tools/bench/exp3.mjs --root "$E" --stage fal
10
- node tools/bench/exp3.mjs --root "$E" --stage summary
11
- ```
12
-
13
- Defaults are Vite 5194, CDP 9234, and `--variants shaded,skin`. `--motions sit` selects a scenario; `--variants skin` selects only the grey/YOLO path. `--stage all` runs all stages serially. Inputs: `gt-motions/{bump,handon,sit,stepup}.npz` and `gen-log*.json` containing the action prompts. A failed stage exits nonzero; it never substitutes a guessed motion or passes a failed extraction into fitting. Completed extraction/scoring artifacts can be reused; use a fresh evidence directory after changing inputs or derivation code (the driver is not a content-addressed cache).
14
-
15
- ## Renderer
16
-
17
- ```sh
18
- node tools/gt-render/render.mjs --out /tmp/example --port 5194 --cdp-port 9234 \
19
- --azimuth 30 --elevation 5 --f-mm 35 --keep-frames \
20
- --scene-box '{"x":0,"z":1,"rot":0,"sx":0.5,"sy":0.4,"sz":0.5}' motion.npz
21
- ```
22
-
23
- `scaleX/scaleY/scaleZ` aliases are accepted. Studio scale/position bounds are validated at the CLI boundary (sizes 0.1..100 m), and yaw is normalized. Sizes are metres; the library cube is 1 m on each axis. Base is on Y=0, footprint centred on X/Z, rotation is yaw degrees about +Y. The single QA-hook line exposes only the existing Studio place and update operations. A scene prop's committed scale is checked before capture; clamped unsupported values fail instead of recording inaccurate dimensions. `scene.json` is written at the motion root and beside each `camera.json`. Rotated boxes record centre/half-extents/yaw and corners; axis-aligned boxes also record min/max. The existing `--box` contact scorer and F5 accept **axis-aligned boxes only**; this experiment derives yaw-zero boxes.
24
-
25
- The auto-camera includes both character support over the full clip and all box corners. Masks redraw only the character magenta: the neutral cube remains an occluder but is not included in the character mask. `score.mjs` now reads `scene.json` and places the same visible box in prediction/GT mask renders, including calibrated placements. Without a scene file its old behavior is unchanged.
26
-
27
- `--export-vertices --no-video` exports `vertices.f32` (little-endian float32, frame/vertex/XYZ order) and `vertices.json` plus the usual joints/masks. Vertices are world-space CPU-skinned points, not native NPZ joint estimates. `<variant>/contact-sheet.png` shows up to four sampled poses with the eight cube corners/twelve edges projected using the saved camera. `contact-sheet.json` identifies frames.
28
-
29
- ## Derivation and interpretation
30
-
31
- Every span is 0.5 s at the Studio's 24 fps. Sit chooses the lowest sustained hips after the first third; hand-on chooses the lowest sustained mean wrist height in the latter half that permits a skin-safe palm-height table in front of the body (the absolute lowest span can have hands beside the thighs); step-up uses the terminal raised foot; bump uses maximal skin reach along the dominant horizontal travel axis (not necessarily a torso impact). A finite grid of footprint translations around the intended contact is considered, with 0.5 m seat, 0.9 m table, 0.4 m step. For horizontal supports, the top equals the **lowest actual skin vertex within that footprint across the span**. Thus a witness touches exactly and no sampled skin penetrates in that span; vertices lower than the intended contact are never silently discarded. Anatomically poor candidates are reported as warnings, not asserted to be valid motion. A static box cannot force every frame of an imperfect motion to touch. `contact-baseline.json` reports maximum gap as well as minimum gap and penetration; its pass criterion checks every declared contact frame (gap <=3 cm, penetration <=2 cm).
32
-
33
- `scenario.json` preserves witness, target height, span, box, prompt, method and uncertainties. Inspect the contact sheet; a small distance alone does not certify sitting, stepping, or palm support. Thin triangle intersections without an enclosed vertex are outside the existing skinned-vertex metric.
34
-
35
- GT path runs T2 prod/prod+fmm on shaded and native yolo-vitpose/yolo-vitpose+fmm on skin, fits F0..F5 with the scenario box and matching `--base-condition`, and runs `score.mjs --box` for each step plus GT-as-pred. `gt-path/summary.md` has a variant column (48 rows). Fits, scores and GT-self checks are stored under `gt-path/{fit,score,gt-self}/<variant>/<scenario>/`. It uses RAW box contact (scene safety must not benefit from evaluation-only alignment), aligned root-relative MPJPE, aligned last-frame mean joint error, and aligned IoU. F4 uses only native NPZ endpoint poses, not intermediate GT; rig retargeting/anchoring means these pins are not exact rendered-rig endpoints. F5 is skeletal collision resolution, not skinned-contact optimization.
36
-
37
- ## fal
38
-
39
- ```sh
40
- node tools/bench/fal-generate.mjs --scenario "$E/gt/sit" --out "$E/fal/clips" --variant skin --runs 3 --dry-run
41
- ```
42
-
43
- Live key defaults to `~/ccFalToMocap/.fal-key`, or `--key <file>`. Missing key automatically produces dry-run request records and A/B stills; it never waits for the key. The key is not saved or logged. A and B are first/last decoded GT frames of `--variant shaded|skin` (default shaded), cube included. Clip names include scenario, variant and independent run number. The experiment driver plans 4 scenarios x 2 variants x 3 runs = 24 clips. Requests import the repo's `buildH3LockedPrompt` contract and use `minimax/h3-max-turbo/image-to-video`, 480P, 5 s, disabled prompt expansion. No undocumented seed field is sent: three independent requests, explicitly recorded. The queue polling has no overall deadline, logs every status, and bounds individual HTTP requests. Each clip saves request ID, request metadata, all queue responses, MP4, and provider-reported cost fields. Absent cost is unknown, not zero. Mock tests validate the client contract, not live provider availability.
44
-
45
- `--stage fal` generates then extracts and fits available clips. It runs prod/prod+fmm for shaded and yolo-vitpose/yolo-vitpose+fmm for skin, supplying true F0/F1 baselines and detector-matched in-camera evidence. Video is normalized to the A-still's 832x480 and 24 fps without temporal stretching. A changed aspect ratio (>1%) fails; unchanged framing is an explicit unverified H3 assumption. F4 pins the original A/B GT poses to the fal clip endpoints; F5 receives the box. **No intermediate fal motion GT exists**, so no MPJPE/trajectory-GT claims are made. Each F step is evaluated for first/last world joint error against A/B, whole-clip skin distance/penetration (contact timing unknown), and same-camera unaligned mask IoU.
46
-
47
- Palette segmentation is a coloured-pixel proxy: max(R,G,B)-min(R,G,B) >=35; HSV saturation >=0.25; value >=0.18, on decoded RGB bytes. Neutral cube/floor/shadows are excluded; saturated hallucinations are included and desaturated body pixels can be lost. It is not semantic segmentation. Threshold boundary tests are deterministic.
48
-
49
- For skin clips, segmentation is **background difference**, not colour: compare each decoded RGB frame against the GT's `plate.png` (same fixed A-still camera, cube present, character hidden). A pixel is foreground if any channel changes by >=30/255. Moving shadows, lighting drift, compression and generated set changes can contaminate this proxy; no semantic or camera-drift correction is claimed. Shaded and skin IoUs use different proxies and should not be compared as identical measures.
50
-
51
- `qa-pack/<scenario>-<variant>-<run>.mp4`: fal | F5 Studio render with cube | 50% blend, same camera/time. `qa-pack/ratings.csv` has empty human rating columns (`prompt_adherence_0_2,contact_natural_0_2,usable_without_fix_YN,notes`), and resumed runs preserve existing ratings. No pack video is fabricated when generation is pending.
52
-
53
- ## Validation
54
-
55
- ```sh
56
- node test/verify-cube-contact.mjs
57
- npm run build
58
- node tools/run-tests.mjs
59
- ```
60
-
61
- Pure tests cover support witness/nonpenetration, sustained spans, all scenario derivations, rotated box coordinates, independent Three camera projection, segmentation thresholds, request fields, and mocked queue completion/errors. No fixed sleeps or live network calls occur in these tests.
@@ -1,125 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Issue #432: add camera evidence around the UNMODIFIED production runner.
3
-
4
- python cclay_bench_extract_incam.py <runner.py> <video> <out.npz>
5
- --static-cam --f-mm 35 --out-root /tmp/cclay-fit-.../cache
6
- --detector palette --keypoints hybrid --smooth-sigma 3
7
-
8
- Import-only reuse: runner.main, FastRuntime and trajectory_job. The two repo
9
- worker modules are deployed alongside this file, never into the GVHMR tree.
10
- No GT poses/joints are inputs. The output contains the runner's members plus:
11
- incam_raw_{global_orient,transl,body_pose,betas}: SMPL-X model parameters
12
- incam_global_orient / incam_pelvis: production-smoothed camera-space root
13
- ayfz_to_camera[T,4,4]: per-frame pelvis/orientation relation, column vectors
14
- ay_to_ayfz[4,4]: exact runner normalization including floor/heading offset
15
- K_fullimg[T,3,3], ay_global_orient, ay_transl: raw prediction diagnostics
16
-
17
- SMPL model transl is NOT its regressed pelvis. We evaluate the same SMPL-X ->
18
- SMPL mesh regressor as production to get camera pelvis, then smooth with the
19
- runner's own function. ayfz_to_camera is framewise, not a claim that noisy
20
- monocular camera and integrated world trajectories agree on one static SE(3).
21
- The JS fit uses one initial registration to preserve F1's trajectory.
22
- """
23
- import importlib.util
24
- from pathlib import Path
25
- import sys
26
-
27
-
28
- def main():
29
- runner_path = Path(sys.argv[1]).resolve()
30
- arguments = sys.argv[2:]
31
- direct = "--bench-direct" in arguments
32
- if direct:
33
- arguments.remove("--bench-direct")
34
- if "--static-cam" not in arguments or "--f-mm" not in arguments:
35
- raise ValueError("bench camera evidence requires --static-cam and --f-mm")
36
- sys.path.insert(0, str(runner_path.parent))
37
- spec = importlib.util.spec_from_file_location("cozyclay_fit_runner", runner_path)
38
- runner = importlib.util.module_from_spec(spec)
39
- spec.loader.exec_module(runner)
40
- import numpy as np
41
- import torch
42
-
43
- capture = {}
44
- detach = runner.detach_to_cpu
45
- normalize = runner.compute_T_ayfz2ay
46
-
47
- def save_prediction(value):
48
- result = detach(value)
49
- capture["prediction"] = result
50
- return result
51
-
52
- def save_normalization(joints, *args, **kwargs):
53
- result = normalize(joints, *args, **kwargs)
54
- capture["heading"] = result.detach().cpu()
55
- return result
56
-
57
- runner.detach_to_cpu = save_prediction
58
- runner.compute_T_ayfz2ay = save_normalization
59
- sys.argv = [str(runner_path), *arguments]
60
- video, output = Path(arguments[0]), Path(arguments[1])
61
- root = Path(arguments[arguments.index("--out-root") + 1])
62
- sigma = float(arguments[arguments.index("--smooth-sigma") + 1]) if "--smooth-sigma" in arguments else 1.2
63
- # Match the T2 condition's execution path as well as its detector flags.
64
- # YOLO/direct must NOT gain production's trajectory correction or palette
65
- # fast path merely because this launcher also captures camera evidence.
66
- if direct:
67
- runner.main()
68
- else:
69
- from gvhmr_fastpath import FastRuntime
70
- from gvhmr_trajectory import trajectory_job
71
- runtime = FastRuntime(runner, enabled=True)
72
- with runtime.job(), trajectory_job(runner, video, root):
73
- runner.main()
74
- pred = capture["prediction"]
75
- cp, gp = pred["smpl_params_incam"], pred["smpl_params_global"]
76
- with np.load(output) as archive:
77
- arrays = {key: archive[key] for key in archive.files}
78
-
79
- # Re-evaluate regressed pelvis in BOTH frames. The SMPL parameter transl
80
- # omits the shaped rest-pelvis offset; using it directly shifts placement.
81
- with torch.no_grad():
82
- model = runner.make_smplx("supermotion").cuda()
83
- mapping = torch.load("hmr4d/utils/body_model/smplx2smpl_sparse.pt").cuda()
84
- regressor = torch.load("hmr4d/utils/body_model/smpl_neutral_J_regressor.pt").cuda()
85
-
86
- def pelvis(params):
87
- mesh = model(**runner.to_cuda(params)).vertices
88
- verts = torch.stack([torch.matmul(mapping, v) for v in mesh])
89
- return torch.einsum("v,tvc->tc", regressor[0], verts).cpu()
90
-
91
- camera_pelvis = pelvis(cp)
92
- world_pelvis = pelvis(gp)
93
- del model, mapping, regressor
94
- runner._release_gpu()
95
- # The prediction packs 21 axis-angle vectors into 63 scalars; the
96
- # runner smoother requires a final axis of 3, as in its own main().
97
- orient, _, camera_pelvis = runner.smooth_motion_params(
98
- cp["global_orient"], cp["body_pose"].reshape(-1, 21, 3), camera_pelvis, sigma=sigma)
99
- # Reconstruct normalization translation from production's unsmoothed
100
- # mesh-regressed pelvis. Its first root is saved in legacy positions.
101
- heading = capture["heading"][0]
102
- rotation = heading[:3, :3]
103
- norm = heading.clone()
104
- norm[:3, 3] = torch.from_numpy(arrays["positions"][0, 0]) - rotation @ world_pelvis[0]
105
- camera_r = runner.axis_angle_to_matrix(orient)
106
- ayfz_r = runner.axis_angle_to_matrix(torch.from_numpy(arrays["smpl_global_orient"]))
107
- r = camera_r @ ayfz_r.transpose(-1, -2)
108
- relation = torch.eye(4).repeat(len(orient), 1, 1)
109
- relation[:, :3, :3] = r
110
- relation[:, :3, 3] = camera_pelvis - (r @ torch.from_numpy(arrays["smpl_transl"])[..., None])[..., 0]
111
-
112
- c32 = lambda value: np.ascontiguousarray(np.asarray(value, dtype=np.float32))
113
- arrays.update({"incam_raw_" + key: c32(value) for key, value in cp.items()})
114
- arrays.update(incam_global_orient=c32(orient), incam_pelvis=c32(camera_pelvis),
115
- ayfz_to_camera=c32(relation), ay_to_ayfz=c32(norm),
116
- ay_global_orient=c32(gp["global_orient"]), ay_transl=c32(gp["transl"]),
117
- K_fullimg=c32(pred["K_fullimg"]))
118
- if not all(np.isfinite(value).all() for value in arrays.values()):
119
- raise ValueError("nonfinite camera evidence")
120
- np.savez(output, **arrays)
121
- print(f"[fit] wrote camera parameters and transforms: {output}", flush=True)
122
-
123
-
124
- if __name__ == "__main__":
125
- main()
@@ -1,204 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Extract the OBS NPZ contract from one GVHMR forward pass.
3
-
4
- Units and frames: image coordinates are pixels; translations and joints are
5
- metres. ``incam_*`` uses OpenCV camera coordinates (+X right, +Y down, +Z
6
- forward). ``global_*`` uses GVHMR gravity-view ``ay`` coordinates (+Y up).
7
- Joints are the first 22 SMPL joints obtained by SMPL-X FK/regression using the
8
- runner's ``make_smplx('supermotion')`` body model. K is float64 and is copied
9
- exactly from --K-json.
10
- """
11
- import argparse
12
- import importlib.util
13
- import json
14
- import sys
15
- from pathlib import Path
16
-
17
- import numpy as np
18
-
19
-
20
- def load_runner(path):
21
- path = Path(path).resolve()
22
- sys.path.insert(0, str(path.parent))
23
- spec = importlib.util.spec_from_file_location("cclay_obs_runner", path)
24
- runner = importlib.util.module_from_spec(spec)
25
- spec.loader.exec_module(runner)
26
- return runner
27
-
28
-
29
- def fk22(runner, params, model, sparse, regressor):
30
- import torch
31
- # Pipeline params are batched (1, T, ...); SMPL-X expects (T, ...), the
32
- # same per-frame layout the runner feeds it via pred["smpl_params_global"].
33
- flat = {key: (value[0] if value.dim() >= 2 and value.shape[0] == 1 else value) for key, value in params.items()}
34
- with torch.no_grad():
35
- body = model(**runner.to_cuda(flat))
36
- verts = torch.stack([torch.matmul(sparse, v) for v in body.vertices])
37
- joints = torch.einsum("jv,tvi->tji", regressor, verts)
38
- return joints[:, :22]
39
-
40
-
41
- def parameterized_pp(outputs, endecoder, cp_thr, clamp):
42
- """Exact pp_static_joint_cam, exposing its two correction limits."""
43
- import torch
44
- from hmr4d.utils.geo_transform import apply_T_on_points, transform_mat
45
- from hmr4d.utils.net_utils import gaussian_smooth
46
- from pytorch3d.transforms import axis_angle_to_matrix
47
-
48
- incam = outputs["pred_smpl_params_incam"].copy()
49
- global_params = outputs["pred_smpl_params_global"]
50
- logits = outputs["static_conf_logits"].clone()[:, :-1]
51
- joint_ids = [7, 10, 8, 11, 20, 21]
52
- batch, length = incam["transl"].shape[:2]
53
- assert batch == 1
54
-
55
- pred_w = endecoder.fk_v2(**global_params)
56
- incam["transl"] = gaussian_smooth(incam["transl"], sigma=5, dim=-2)
57
- pred_c = endecoder.fk_v2(**incam)
58
- r_gv = axis_angle_to_matrix(global_params["global_orient"][:, 0])
59
- r_c = axis_angle_to_matrix(incam["global_orient"][:, 0])
60
- r_c2w = r_gv @ r_c.mT
61
- t_c2w = pred_w[:, 0, 0] - torch.einsum("bij,bj->bi", r_c2w, pred_c[:, 0, 0])
62
- t_c2w = transform_mat(r_c2w, t_c2w)
63
- pred_c_in_w = apply_T_on_points(pred_c, t_c2w[:, None])
64
-
65
- post_transl = global_params["transl"].clone()
66
- post_joints = pred_w.clone()
67
- threshold = torch.as_tensor([cp_thr] * 3, device=post_joints.device, dtype=post_joints.dtype)
68
- for i in range(1, length):
69
- diff = post_joints[:, i, 0] - pred_c_in_w[:, i, 0]
70
- diff = diff * ~((diff > -threshold) * (diff < threshold))
71
- diff = torch.clamp(diff, -clamp, clamp)
72
- post_transl[:, i:] -= diff
73
- post_joints[:, i:] -= diff[:, None, None]
74
-
75
- static = logits.sigmoid() > 0.8
76
- count = static.sum(-1, keepdim=True).clamp_min(1)
77
- disp = (post_joints[:, 1:, joint_ids] - post_joints[:, :-1, joint_ids])
78
- disp = (disp * static[..., None]).sum(-2) / count
79
- disp[:, :, 1] = 0
80
- for i in range(1, length):
81
- post_transl[:, i:] -= disp[:, [i - 1]]
82
- post_joints[:, i:] -= disp[:, [i - 1], None]
83
- ground_y = post_joints[..., 1].flatten(-2).min(dim=-1)[0]
84
- post_transl[..., 1] -= ground_y
85
- return post_transl
86
-
87
-
88
- def as_np(value, dtype=np.float32):
89
- if hasattr(value, "detach"):
90
- value = value.detach().cpu().numpy()
91
- return np.ascontiguousarray(value, dtype=dtype)
92
-
93
-
94
- def main():
95
- ap = argparse.ArgumentParser()
96
- ap.add_argument("runner")
97
- ap.add_argument("video")
98
- ap.add_argument("output")
99
- ap.add_argument("--out-root", default="outputs/cclay-obs")
100
- ap.add_argument("--K-json", required=True)
101
- ap.add_argument("--detector", choices=("palette", "yolo"), default="palette")
102
- ap.add_argument("--keypoints", choices=("vitpose", "hybrid"), default="vitpose")
103
- ap.add_argument("--betas-json")
104
- ap.add_argument("--pp-thr", type=float, default=0.0)
105
- ap.add_argument("--pp-clamp", type=float, default=1.0)
106
- ap.add_argument("--check-pp", action="store_true")
107
- args = ap.parse_args()
108
-
109
- runner = load_runner(args.runner)
110
- import torch
111
- from hmr4d.model.gvhmr.utils.postprocess import pp_static_joint_cam, process_ik
112
- from hmr4d.utils.geo.hmr_cam import normalize_kp2d
113
-
114
- with open(args.K_json, encoding="utf-8") as stream:
115
- K = np.asarray(json.load(stream), dtype=np.float64)
116
- if K.shape != (3, 3):
117
- raise ValueError("--K-json must contain a 3x3 array")
118
-
119
- video = Path(args.video)
120
- length, width, height = runner.get_video_lwh(str(video))
121
- cfg = runner.build_cfg(video, True, None, Path(args.out_root))
122
- data = runner.preprocess(cfg, video, args.detector, args.keypoints)
123
- data["K_fullimg"] = torch.as_tensor(K, dtype=data["K_fullimg"].dtype,
124
- device=data["K_fullimg"].device).repeat(length, 1, 1)
125
- model = runner.hydra.utils.instantiate(cfg.model, _recursive_=False)
126
- model.load_pretrained_model(cfg.ckpt_path)
127
- model = model.eval().cuda()
128
- batch = {
129
- "length": data["length"][None],
130
- "obs": normalize_kp2d(data["kp2d"], data["bbx_xys"])[None],
131
- "bbx_xys": data["bbx_xys"][None], "K_fullimg": data["K_fullimg"][None],
132
- "cam_angvel": data["cam_angvel"][None], "f_imgseq": data["f_imgseq"][None],
133
- }
134
- batch = {key: value.cuda() for key, value in batch.items()}
135
- with torch.no_grad():
136
- raw = model.pipeline.forward(batch, train=False, postproc=False, static_cam=True)
137
-
138
- pred = {key: {name: value.clone() for name, value in raw[key].items()}
139
- for key in ("pred_smpl_params_incam", "pred_smpl_params_global")}
140
- pred["static_conf_logits"] = raw["static_conf_logits"].clone()
141
- if args.betas_json:
142
- with open(args.betas_json, encoding="utf-8") as stream:
143
- loaded = json.load(stream)
144
- # Accept a bare list or tools/bench/obs/mannequin-betas.json ({"betas": [...], ...}).
145
- betas_used = np.asarray(loaded["betas"] if isinstance(loaded, dict) else loaded, dtype=np.float32)
146
- if betas_used.shape != (10,):
147
- raise ValueError("--betas-json must contain 10 floats")
148
- betas = torch.as_tensor(betas_used, device=pred["pred_smpl_params_incam"]["betas"].device,
149
- dtype=pred["pred_smpl_params_incam"]["betas"].dtype)
150
- for params in (pred["pred_smpl_params_incam"], pred["pred_smpl_params_global"]):
151
- params["betas"] = betas[None, None].expand_as(params["betas"])
152
- else:
153
- betas_used = as_np(pred["pred_smpl_params_incam"]["betas"][0].mean(0))
154
-
155
- default_transl = pp_static_joint_cam(pred, model.pipeline.endecoder)
156
- relaxed_transl = parameterized_pp(pred, model.pipeline.endecoder, args.pp_thr, args.pp_clamp)
157
- if args.check_pp:
158
- exact = parameterized_pp(pred, model.pipeline.endecoder, 0.25, 0.02)
159
- pp_diff = float((exact - default_transl).abs().max().item())
160
- print(f"pp_default vs GVHMR postproc max abs diff: {pp_diff:.9g}", flush=True)
161
- if pp_diff > 1e-5:
162
- raise AssertionError(f"pp_default mismatch: {pp_diff}")
163
-
164
- variants = {}
165
- for name, transl in (("pp_default", default_transl), ("pp_relaxed", relaxed_transl)):
166
- params = {key: value.clone() for key, value in pred["pred_smpl_params_global"].items()}
167
- params["transl"] = transl
168
- ik_input = {"pred_smpl_params_global": params,
169
- "pred_smpl_params_incam": pred["pred_smpl_params_incam"],
170
- "static_conf_logits": pred["static_conf_logits"]}
171
- params["body_pose"] = process_ik(ik_input, model.pipeline.endecoder)
172
- variants[name] = params
173
-
174
- smplx = runner.make_smplx("supermotion").cuda()
175
- sparse = torch.load("hmr4d/utils/body_model/smplx2smpl_sparse.pt").cuda()
176
- regressor = torch.load("hmr4d/utils/body_model/smpl_neutral_J_regressor.pt").cuda()
177
- incam = pred["pred_smpl_params_incam"]
178
- incam_joints = fk22(runner, incam, smplx, sparse, regressor)
179
- output = {
180
- "fps": np.int32(runner.video_fps(video)), "K": K,
181
- "bbx_xys": as_np(data["bbx_xys"]), "kp2d": as_np(data["kp2d"]),
182
- "static_conf_logits": as_np(pred["static_conf_logits"][0]),
183
- "betas_pred": as_np(raw["pred_smpl_params_incam"]["betas"][0]),
184
- "betas_used": as_np(betas_used),
185
- "incam_global_orient": as_np(incam["global_orient"][0]),
186
- "incam_body_pose": as_np(incam["body_pose"][0].reshape(length, 21, 3)),
187
- "incam_transl": as_np(incam["transl"][0]), "incam_joints": as_np(incam_joints),
188
- "incam_pelvis": as_np(incam_joints[:, 0]),
189
- }
190
- for name, params in variants.items():
191
- joints = fk22(runner, params, smplx, sparse, regressor)
192
- output.update({f"global_{name}_global_orient": as_np(params["global_orient"][0]),
193
- f"global_{name}_body_pose": as_np(params["body_pose"][0].reshape(length, 21, 3)),
194
- f"global_{name}_transl": as_np(params["transl"][0]),
195
- f"global_{name}_joints": as_np(joints)})
196
- if not all(np.isfinite(value).all() for value in output.values()):
197
- raise ValueError("nonfinite OBS output")
198
- Path(args.output).parent.mkdir(parents=True, exist_ok=True)
199
- np.savez(args.output, **output)
200
- print(f"wrote OBS NPZ: {args.output} fields={len(output)}", flush=True)
201
-
202
-
203
- if __name__ == "__main__":
204
- main()
@@ -1,42 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Bench launcher for cclay_gvhmr_extract.py (issue #430), YOLO runs only.
3
-
4
- python cclay_bench_runner.py <runner.py> <runner args...>
5
-
6
- Runs the runner's own main() with exactly the given argv, in the same way
7
- tools/ardy/cclay_gvhmr_worker.py loads it, and adds one observation: the
8
- runner logs how many frames its YOLO track detected only on the raw-detection
9
- fallback, so the tracker path gets a "[bench] yolo track: N/M frames" line
10
- here. The wrapped method's inputs and return value are passed through
11
- untouched; the runner's decisions do not change.
12
- """
13
- import importlib.util
14
- from pathlib import Path
15
- import sys
16
-
17
-
18
- def main():
19
- runner_path = Path(sys.argv[1]).resolve()
20
- sys.path.insert(0, str(runner_path.parent))
21
- from hmr4d.utils.preproc.tracker import Tracker
22
-
23
- original = Tracker.sort_track_length
24
-
25
- def counted(track_history, video_path):
26
- result = original(track_history, video_path)
27
- id_to_frame_ids, _, id_sorted = result
28
- if id_sorted:
29
- print(f"[bench] yolo track: {len(id_to_frame_ids[id_sorted[0]])}/{len(track_history)} frames",
30
- file=sys.stderr, flush=True)
31
- return result
32
-
33
- Tracker.sort_track_length = staticmethod(counted)
34
- spec = importlib.util.spec_from_file_location("cozyclay_gvhmr_bench", runner_path)
35
- runner = importlib.util.module_from_spec(spec)
36
- sys.argv = [str(runner_path), *sys.argv[2:]]
37
- spec.loader.exec_module(runner)
38
- runner.main()
39
-
40
-
41
- if __name__ == "__main__":
42
- main()
@@ -1,162 +0,0 @@
1
- import { sceneBoxRecord, projectBox, BOX_EDGES } from "../gt-render/scene-box.mjs";
2
-
3
- /** Lowest sustained window, never a one-frame outlier. Ties choose the later span. */
4
- export function lowestWindow(values, length, start = 0) {
5
- if (!values.length || !values.every(Number.isFinite) || length < 1 || length > values.length - start) throw new Error("invalid sustained window");
6
- let best = start, score = Infinity;
7
- for (let i = start; i <= values.length - length; i++) {
8
- const mean = values.slice(i, i + length).reduce((a, b) => a + b, 0) / length;
9
- if (mean <= score) { score = mean; best = i; }
10
- }
11
- return Array.from({ length }, (_, i) => best + i);
12
- }
13
- const mean = a => a.reduce((s, v) => s + v, 0) / a.length;
14
- const xyz = (v, i) => [v[i], v[i + 1], v[i + 2]];
15
-
16
- /** Axis-aligned horizontal support: the highest nonpenetrating top beneath
17
- * ALL sampled skin in this footprint. No joint radius or guessed skin offset.
18
- * Returns the witness vertex, making exact contact independently checkable. */
19
- export function supportTop(frames, vertices, { x, z, sx, sz }) {
20
- let height = Infinity, witness = null;
21
- for (const frame of frames) {
22
- const v = vertices(frame);
23
- for (let i = 0; i < v.length; i += 3) {
24
- if (Math.abs(v[i] - x) <= sx / 2 && Math.abs(v[i + 2] - z) <= sz / 2 && v[i + 1] < height) {
25
- height = v[i + 1]; witness = { frame, vertex: i / 3, point: xyz(v, i) };
26
- }
27
- }
28
- }
29
- return { height, witness };
30
- }
31
-
32
- export function deriveBox(scenario, joints, vertices) {
33
- const index = name => { const i = joints.joints.findIndex(j => j.name === name); if (i < 0) throw new Error(`missing joint ${name}`); return i; };
34
- const at = (f, name) => joints.world[f][index(name)];
35
- const n = joints.frames, length = Math.min(n, Math.round(joints.fps / 2));
36
- const heights = name => joints.world.map((_, f) => at(f, name)[1]);
37
- const warnings = [];
38
- let frames, box, witness, targetHeight;
39
- if (scenario === "bump") {
40
- const first = at(0, "Hips"), travel = joints.world.map((_, f) => at(f, "Hips").map((v, a) => v - first[a]));
41
- const extent = a => Math.max(...travel.map(v => Math.abs(v[a])));
42
- const axis = extent(0) > extent(2) ? 0 : 2;
43
- const far = travel.reduce((a, b) => Math.abs(b[axis]) > Math.abs(a[axis]) ? b : a), sign = Math.sign(far[axis]) || 1;
44
- let reach = -Infinity, peak = 0;
45
- for (let f = 0; f < n; f++) {
46
- const v = vertices(f);
47
- for (let i = 0; i < v.length; i += 3) if (v[i + 1] > 0.15 && v[i + 1] < 1.5 && sign * v[i + axis] > reach) {
48
- reach = sign * v[i + axis]; peak = f; witness = { frame: f, vertex: i / 3, point: xyz(v, i) };
49
- }
50
- }
51
- frames = Array.from({ length: Math.min(length, n) }, (_, i) => Math.max(0, Math.min(n - length, peak - Math.floor(length / 2))) + i);
52
- const centre = at(peak, "Hips").slice(), side = axis === 0 ? 2 : 0;
53
- centre[axis] = sign * (reach + 0.2);
54
- box = { x: centre[0], z: centre[2], rot: 0, sx: axis === 0 ? 0.4 : 1.5, sy: 1.5, sz: axis === 2 ? 0.4 : 1.5 };
55
- // Centre width on the actual contact witness, not a drifting pelvis.
56
- box[side === 0 ? "x" : "z"] = witness.point[side];
57
- warnings.push("Bump frame is maximal skin reach along the dominant travel axis; this may be an extended hand rather than torso impact.");
58
- } else {
59
- let centres;
60
- if (scenario === "sit") {
61
- frames = lowestWindow(heights("Hips"), length, Math.floor(n / 3));
62
- centres = frames.map(f => at(f, "Hips"));
63
- targetHeight = mean(centres.map(p => p[1])) - 0.12;
64
- if (Math.max(...heights("Hips")) - mean(centres.map(p => p[1])) < 0.15) warnings.push("No clear seated pelvis drop; using the lowest available sustained span.");
65
- } else if (scenario === "handon") {
66
- const travel = [at(n - 1, "Hips")[0] - at(0, "Hips")[0], at(n - 1, "Hips")[2] - at(0, "Hips")[2]];
67
- const norm = Math.hypot(...travel), forward = norm > 1e-6 ? travel.map(v => v / norm) : [0, 1];
68
- const windows = Array.from({ length: n - length - Math.floor(n / 2) + 1 }, (_, i) => {
69
- const span = Array.from({ length }, (_, k) => Math.floor(n / 2) + i + k);
70
- const palms = span.flatMap(f => [at(f, "LeftHand"), at(f, "RightHand")]);
71
- return { span, palms, height: mean(palms.map(p => p[1])) };
72
- }).sort((a, b) => a.height - b.height);
73
- // The lowest hand span can still be beside the thighs. Select the
74
- // lowest span that permits a palm-height table IN FRONT, not a box
75
- // shifted sideways to touch a leg. Keep the failed-motion caveat.
76
- for (const { span, palms } of windows) {
77
- let best = Infinity;
78
- const target = Math.min(...palms.map(p => p[1])) - 0.025;
79
- for (let step = 14; step <= 36; step++) {
80
- const distance = step * 0.025;
81
- const candidate = { x: mean(palms.map(p => p[0])) + forward[0] * distance, z: mean(palms.map(p => p[2])) + forward[1] * distance, sx: 0.9, sz: 0.9 };
82
- const support = supportTop(span, vertices, candidate);
83
- if (support.height < Math.max(0.1, target - 0.1) || support.height > target + 0.1) continue;
84
- const loss = Math.abs(support.height - target) + 0.02 * distance;
85
- if (loss < best) { best = loss; box = { ...candidate, sy: support.height, rot: 0 }; witness = support.witness; frames = span; centres = palms; targetHeight = target; }
86
- }
87
- if (box) break;
88
- }
89
- if (!box) throw new Error("handon: no skin-safe palm-height table in front of the body in any sustained latter-half span");
90
- warnings.push("The absolute lowest hands are beside the thighs, not in a table-support pose. Selected the lowest sustained span permitting a skin-safe palm-height table in front. Both-palms natural contact is not established.");
91
- } else if (scenario === "stepup") {
92
- frames = Array.from({ length }, (_, i) => n - length + i);
93
- const foot = mean(frames.map(f => at(f, "LeftFoot")[1])) > mean(frames.map(f => at(f, "RightFoot")[1])) ? "LeftFoot" : "RightFoot";
94
- centres = frames.map(f => at(f, foot));
95
- targetHeight = Math.min(...centres.map(p => p[1])) - 0.04;
96
- if (targetHeight < 0.12) warnings.push("No clearly raised terminal stance foot; best available terminal span used, not a fabricated 0.3 m step.");
97
- } else throw new Error(`unknown scenario ${scenario}`);
98
- if (!box) {
99
- const x = mean(centres.map(p => p[0])), z = mean(centres.map(p => p[2]));
100
- const size = scenario === "sit" ? 0.5 : 0.4;
101
- let best = Infinity;
102
- // A footprint enclosing the feet is rejected by its near-floor
103
- // support top, not repaired by ignoring those vertices.
104
- const offsets = [-0.3, -0.2, -0.1, 0, 0.1, 0.2, 0.3];
105
- for (const dx of offsets) for (const dz of offsets) {
106
- const candidate = { x: x + dx, z: z + dz, sx: size, sz: size };
107
- const support = supportTop(frames, vertices, candidate);
108
- if (!Number.isFinite(support.height) || support.height < 0.1 || support.height > targetHeight + 0.1) continue;
109
- const loss = Math.abs(support.height - targetHeight) + 0.3 * Math.hypot(dx, dz);
110
- if (loss < best) { best = loss; box = { ...candidate, sy: support.height, rot: 0 }; witness = support.witness; }
111
- }
112
- if (!box) throw new Error(`${scenario}: no Studio-supported (>=0.1 m) support footprint near the intended contact`);
113
- }
114
- if (scenario === "stepup" && box.sy > 0.45) warnings.push(`Kimodo's terminal sole is ${box.sy.toFixed(3)} m high; retained the motion-derived height rather than inventing a 0.3-0.4 m step.`);
115
- if (Math.abs(box.sy - targetHeight) > 0.1) warnings.push(`Skin-safe top ${box.sy.toFixed(3)} m differs from anatomical target ${targetHeight.toFixed(3)} m: motion/contact is not a clean instance of the requested action.`);
116
- }
117
- return { scenario, box, scene: sceneBoxRecord(box), contactFrames: frames, contactSeconds: [frames[0] / joints.fps, frames.at(-1) / joints.fps], witness, targetHeight, warnings, method: "Static box from rendered skin in a declared 0.5 s contact span; no intermediate GT is used in fitting. A support top equals the lowest skin vertex within its footprint across that span." };
118
- }
119
-
120
- /** Chroma-only proxy: RGB range >= 35, HSV S >= .25, V >= .18;
121
- * includes all saturated palette colours, excludes neutral cube/floor/shadows.
122
- * It is NOT semantic segmentation and includes similarly coloured hallucinations. */
123
- export const PALETTE_THRESHOLDS = Object.freeze({ minRange: 35, minSaturation: 0.25, minValue: 0.18 });
124
- export function paletteMask(rgb, thresholds = PALETTE_THRESHOLDS) {
125
- if (rgb.length % 3) throw new Error("RGB byte count must be divisible by three");
126
- const mask = Buffer.alloc(rgb.length / 3);
127
- for (let p = 0; p < mask.length; p++) {
128
- const r = rgb[p * 3], g = rgb[p * 3 + 1], b = rgb[p * 3 + 2], hi = Math.max(r, g, b), lo = Math.min(r, g, b);
129
- mask[p] = hi - lo >= thresholds.minRange && (hi - lo) / hi >= thresholds.minSaturation && hi / 255 >= thresholds.minValue ? 255 : 0;
130
- }
131
- return mask;
132
- }
133
-
134
- /** Grey-body proxy against the same camera's empty Studio plate (cube stays).
135
- * Any RGB channel changing by >=30 counts as foreground. Lighting drift,
136
- * moving shadows and generated background changes are intentionally NOT
137
- * corrected: they are uncertainties of this proxy, not motion ground truth. */
138
- export function backgroundDifferenceMask(rgb, plate, threshold = 30) {
139
- if (!plate.length || plate.length % 3 || rgb.length % plate.length || !Number.isFinite(threshold) || threshold <= 0 || threshold > 255) throw new Error("invalid RGB background-difference inputs");
140
- const mask = Buffer.alloc(rgb.length / 3);
141
- for (let p = 0; p < mask.length; p++) {
142
- const i = p * 3, q = i % plate.length;
143
- mask[p] = Math.max(Math.abs(rgb[i] - plate[q]), Math.abs(rgb[i + 1] - plate[q + 1]), Math.abs(rgb[i + 2] - plate[q + 2])) >= threshold ? 255 : 0;
144
- }
145
- return mask;
146
- }
147
-
148
- /** Draw all 8 projected corners and 12 edges directly into RGB (no image dependency). */
149
- export function drawProjectedBox(rgb, camera, box) {
150
- const out = Buffer.from(rgb), points = projectBox(box, camera), { width, height } = camera;
151
- const dot = (x, y, radius) => { for (let dx = -radius; dx <= radius; dx++) for (let dy = -radius; dy <= radius; dy++) {
152
- const u = Math.round(x) + dx, v = Math.round(y) + dy;
153
- if (u >= 0 && u < width && v >= 0 && v < height) { const p = (v * width + u) * 3; out[p] = 255; out[p + 1] = 25; out[p + 2] = 25; }
154
- } };
155
- for (const [a, b] of BOX_EDGES) {
156
- const p = points[a], q = points[b]; if (p[2] <= 0 || q[2] <= 0) continue;
157
- const steps = Math.ceil(Math.max(Math.abs(p[0] - q[0]), Math.abs(p[1] - q[1])));
158
- for (let i = 0; i <= steps; i++) { const t = steps ? i / steps : 0; dot(p[0] + (q[0] - p[0]) * t, p[1] + (q[1] - p[1]) * t, 0); }
159
- }
160
- for (const [u, v, d] of points) if (d > 0) dot(u, v, 3);
161
- return out;
162
- }