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,467 +0,0 @@
1
- /**
2
- * footage.mjs — the bridge's footage-fetch side: platform pages (YouTube,
3
- * Vimeo, …) serve a player, not a file, and block cross-origin reads, so the
4
- * BROWSER can never ingest them. This process can: yt-dlp downloads the
5
- * video stream, ffmpeg locks it to a constant rate (platform video is often
6
- * VFR — frame numbers would drift against seconds) no higher than extraction
7
- * can use, and the result is served back over loopback where the ordinary
8
- * ingest takes over.
9
- *
10
- * Same safety posture as motion delivery: served paths come only from an
11
- * allowlist this process populated, a client disconnect kills the child
12
- * process group, and every failure is a NAMED reason the UI can act on.
13
- */
14
-
15
- import { spawn } from "node:child_process";
16
- import { randomBytes } from "node:crypto";
17
- import { createReadStream, existsSync, readdirSync, rmSync, statSync } from "node:fs";
18
- import { dirname, join, sep } from "node:path";
19
- import { fileURLToPath } from "node:url";
20
- import { killGroup, track } from "./runners/proc.mjs";
21
- import { createPrivateArtifactDir, removePrivateArtifactDir } from "./artifacts.mjs";
22
-
23
- const HERE = dirname(fileURLToPath(import.meta.url));
24
- const FOOTAGE_DIR = join(HERE, "out", "footage");
25
- // Normalisation locks the clip to a CONSTANT rate and keeps the source's own
26
- // rate up to the extraction ceiling below: platform video is often VFR (frame
27
- // numbers drift against seconds), but resampling 30 → 24 would drop every 5th
28
- // frame and turn smooth motion into a 4-step-1-jump judder that extraction
29
- // reads as real velocity. The take is retimed to the 24 fps timeline AFTER
30
- // extraction, in rotation space.
31
- const FOOTAGE_FPS_FALLBACK = 30;
32
- const FOOTAGE_FPS_MIN = 10;
33
- const FOOTAGE_FPS_MAX = 60;
34
- // The ceiling extraction is allowed to see. SAM-3D-Body solves every frame
35
- // INDEPENDENTLY and errs a little each time, so twice the rate feeds twice the
36
- // estimation noise per second while the smoothing windows behind it are
37
- // counted in FRAMES and therefore span half as much time. Measured on two
38
- // takes of the same session: a 60 fps / 1632-frame extract trembled 3.36 and
39
- // 3.54 cm at the feet, a 30 fps / 270-frame one 0.60 and 0.72 cm — five times
40
- // the wobble, plus double the GPU cost, for frames a 24 fps timeline can
41
- // never show. Both intake paths clamp here; this is the single edit that
42
- // moves the ceiling.
43
- export const EXTRACT_FPS_MAX = 30;
44
- const FOOTAGE_MAX_S = 900; // 15 min: previs reference, not an archive mirror
45
- const FOOTAGE_MAX_HEIGHT = 1080;
46
- const FOOTAGE_ALLOWLIST_MAX = 16; // newest downloads only; evicted ids 404
47
- const FOOTAGE_ID = /^[0-9]+-[0-9a-f]{6}$/;
48
- // Prefer the Homebrew binaries over whatever shadows them on PATH: a stale
49
- // pip-installed yt-dlp is the classic cause of "The page needs to be
50
- // reloaded" refusals from YouTube, and it sorts FIRST in a default PATH.
51
- function firstExisting(override, candidates, fallback) {
52
- if (override) return override;
53
- for (const candidate of candidates) if (existsSync(candidate)) return candidate;
54
- return fallback;
55
- }
56
- const YTDLP = firstExisting(process.env.CCLAY_YTDLP, ["/opt/homebrew/bin/yt-dlp", "/usr/local/bin/yt-dlp"], "yt-dlp");
57
- const FFMPEG = firstExisting(process.env.CCLAY_FFMPEG, ["/opt/homebrew/bin/ffmpeg", "/usr/local/bin/ffmpeg"], "ffmpeg");
58
- const FFPROBE = firstExisting(process.env.CCLAY_FFPROBE, ["/opt/homebrew/bin/ffprobe", "/usr/local/bin/ffprobe"], "ffprobe");
59
- // The default (web) client serves full resolution but hits YouTube's
60
- // "confirm you're not a bot" wall on music/mix content; the android client
61
- // always passes but is capped around 360p. So every yt-dlp call goes web
62
- // first and falls back to android ONLY on that specific refusal — quality
63
- // when possible, availability when not.
64
- const YTDLP_BASE = ["--no-playlist", "--no-warnings"];
65
- const YTDLP_BOTFIX = ["--extractor-args", "youtube:player_client=android"];
66
- // Two ways YouTube refuses the web client: the bot wall up front, or a 403
67
- // on the media URLs partway through a download. Both mean "switch client".
68
- const WEB_CLIENT_REFUSED = /confirm you.re not a bot|HTTP Error 403/i;
69
-
70
- const footageAllowlist = new Map(); // id -> absolute mp4 path under FOOTAGE_DIR
71
-
72
- /** `[download] 42.3% of ...` -> 0.423; null for any other line. */
73
- export function parseDownloadRatio(line) {
74
- const match = /\[download\]\s+([\d.]+)%/.exec(line);
75
- if (!match) return null;
76
- const percent = Number(match[1]);
77
- if (!Number.isFinite(percent)) return null;
78
- return Math.min(1, Math.max(0, percent / 100));
79
- }
80
-
81
- /** ffmpeg's `... time=00:00:12.34 ...` against the clip length -> 0..1. */
82
- export function parseEncodeRatio(line, durationS) {
83
- if (!(durationS > 0)) return null;
84
- const match = /time=(\d+):(\d\d):(\d\d(?:\.\d+)?)/.exec(line);
85
- if (!match) return null;
86
- const seconds = Number(match[1]) * 3600 + Number(match[2]) * 60 + Number(match[3]);
87
- if (!Number.isFinite(seconds)) return null;
88
- return Math.min(1, Math.max(0, seconds / durationS));
89
- }
90
-
91
- /** yt-dlp's probe line `"123.45\t29.97\tSome title"` -> { durationS, fps,
92
- * title }. A missing/absurd rate falls back to 30 and is clamped to a sane
93
- * band — the ffmpeg pass hard-locks whatever rate is returned here. */
94
- export function parseProbeLine(line) {
95
- const parts = line.split("\t");
96
- if (parts.length < 3) return null;
97
- const durationS = Number(parts[0]);
98
- const rawFps = Number(parts[1]);
99
- const title = parts.slice(2).join("\t").trim();
100
- if (!Number.isFinite(durationS) || durationS <= 0) return null;
101
- const fps = Number.isFinite(rawFps) && rawFps > 0
102
- ? Math.min(FOOTAGE_FPS_MAX, Math.max(FOOTAGE_FPS_MIN, Math.round(rawFps)))
103
- : FOOTAGE_FPS_FALLBACK;
104
- return { durationS, fps, title };
105
- }
106
-
107
- /**
108
- * The rate a source of `sourceFps` may be fed to extraction at. Never
109
- * upsamples — 24 stays 24, 25 stays 25 — because inventing frames invents
110
- * motion. Above the ceiling it prefers an INTEGER divisor of the source
111
- * (60→30, 50→25, 48→24, 120→30): dropping every n-th frame keeps the
112
- * surviving frames exactly as they were shot, while landing on a foreign
113
- * rate has to duplicate frames at uneven intervals and reads as judder. A
114
- * rate with no divisor that lands in range (35, 45, …) takes the ceiling
115
- * itself, and an unreadable rate takes it too — the ceiling is the safe
116
- * answer, never the source's word.
117
- */
118
- export function capExtractFps(sourceFps) {
119
- const fps = Math.round(Number(sourceFps));
120
- if (!Number.isFinite(fps) || fps <= 0) return EXTRACT_FPS_MAX;
121
- if (fps <= EXTRACT_FPS_MAX) return fps;
122
- const divisor = Math.ceil(fps / EXTRACT_FPS_MAX);
123
- return fps % divisor === 0 ? fps / divisor : EXTRACT_FPS_MAX;
124
- }
125
-
126
- /**
127
- * The one ffmpeg pass both intake paths use: a CONSTANT `fps`, no audio, a
128
- * faststart mp4. Nothing here touches presentation timestamps as a whole —
129
- * `fps=N` only drops or repeats frames, so the clip comes out the same
130
- * length and the same speed it went in.
131
- */
132
- export function normalizeArgs(inputPath, outputPath, fps) {
133
- return [
134
- "-y", "-i", inputPath,
135
- "-vf", `fps=${fps}`,
136
- "-an",
137
- "-c:v", "libx264", "-preset", "veryfast", "-crf", "20",
138
- "-movflags", "+faststart",
139
- outputPath,
140
- ];
141
- }
142
-
143
- /** ffprobe's `key=num/den` block -> { fps, durationS }, either field null when
144
- * the file would not say. avg_frame_rate is frames ÷ length over the whole
145
- * stream, which is what extraction actually meets; r_frame_rate is only the
146
- * container's base tick and reads as 90000 on some VFR muxes, so it is the
147
- * fallback, not the answer. */
148
- export function parseProbeStreamInfo(text) {
149
- const source = typeof text === "string" ? text : "";
150
- const ratio = (key) => {
151
- const match = new RegExp(`^${key}=([\\d.]+)(?:/([\\d.]+))?$`, "m").exec(source);
152
- if (!match) return null;
153
- const value = match[2] === undefined ? Number(match[1]) : Number(match[1]) / Number(match[2]);
154
- return Number.isFinite(value) && value > 0 ? value : null;
155
- };
156
- return { fps: ratio("avg_frame_rate") ?? ratio("r_frame_rate"), durationS: ratio("duration") };
157
- }
158
-
159
- function runCollect(command, args, { children, timeoutMs, onLine }) {
160
- return new Promise((resolvePromise, reject) => {
161
- const child = spawn(command, args, { detached: true, stdio: ["ignore", "pipe", "pipe"] });
162
- children.add(child);
163
- track(child);
164
- let out = "";
165
- let err = "";
166
- let buffered = "";
167
- const timer = timeoutMs
168
- ? setTimeout(() => {
169
- killGroup(child);
170
- reject(new Error("footage-timeout"));
171
- }, timeoutMs)
172
- : null;
173
- const feed = (text) => {
174
- buffered += text;
175
- // yt-dlp redraws progress with \r, ffmpeg logs stats to stderr the
176
- // same way; both count as line breaks for the progress parsers.
177
- const lines = buffered.split(/[\r\n]/);
178
- buffered = lines.pop() ?? "";
179
- for (const line of lines) if (line) onLine?.(line);
180
- };
181
- child.stdout.on("data", (chunk) => {
182
- out += chunk;
183
- feed(String(chunk));
184
- });
185
- child.stderr.on("data", (chunk) => {
186
- err += chunk;
187
- feed(String(chunk));
188
- });
189
- child.on("error", (error) => {
190
- if (timer) clearTimeout(timer);
191
- children.delete(child);
192
- reject(new Error(`spawn ${command}: ${error.message}`));
193
- });
194
- child.on("close", (code) => {
195
- if (timer) clearTimeout(timer);
196
- children.delete(child);
197
- if (buffered) onLine?.(buffered);
198
- if (code === 0) resolvePromise({ out, err });
199
- else reject(new Error(err.split("\n").filter(Boolean).pop() || `${command} exited ${code}`));
200
- });
201
- });
202
- }
203
-
204
- /** yt-dlp with the web client, retried via the android client only when
205
- * YouTube's bot wall is the stated reason for the failure. */
206
- async function runYtdlp(args, options) {
207
- try {
208
- return await runCollect(YTDLP, [...YTDLP_BASE, ...args], options);
209
- } catch (error) {
210
- if (!WEB_CLIENT_REFUSED.test(error?.message ?? "")) throw error;
211
- console.error("[bridge] youtube refused the web client; retrying with the android client (≤360p)");
212
- return runCollect(YTDLP, [...YTDLP_BASE, ...YTDLP_BOTFIX, ...args], options);
213
- }
214
- }
215
-
216
- function cleanup(paths) {
217
- for (const path of paths) {
218
- try {
219
- rmSync(path, { force: true });
220
- } catch {
221
- /* best effort */
222
- }
223
- }
224
- }
225
-
226
- /**
227
- * Bring a local video down to EXTRACT_FPS_MAX, if it is over it. Resolves to
228
- * `{ path, fps, capped }`: `path` is the INPUT untouched whenever the source
229
- * is already at or under the ceiling, so a bridge download — normalized at
230
- * this same ceiling on the way in — is never re-encoded a second time for
231
- * nothing. `fps` is what the file at `path` now runs at, i.e. what extraction
232
- * will see; it is null only when ffprobe would not name a rate, in which case
233
- * the clip goes through as it is rather than be refused over a probe hiccup.
234
- * The ffmpeg failure itself throws, for the caller to name.
235
- */
236
- export async function conformToExtractFps(inputPath, outputPath, { children = new Set(), timeoutMs = 600000, onCap, onProgress } = {}) {
237
- let probed = { fps: null, durationS: null };
238
- try {
239
- const { out } = await runCollect(
240
- FFPROBE,
241
- [
242
- "-v", "error", "-select_streams", "v:0",
243
- "-show_entries", "stream=avg_frame_rate,r_frame_rate",
244
- "-show_entries", "format=duration",
245
- "-of", "default=noprint_wrappers=1",
246
- inputPath,
247
- ],
248
- { children, timeoutMs: 60000 }
249
- );
250
- probed = parseProbeStreamInfo(out);
251
- } catch (err) {
252
- console.error(`[bridge] fps probe failed for ${inputPath}: ${err.message}`);
253
- }
254
- if (probed.fps === null) return { path: inputPath, fps: null, capped: false };
255
- const targetFps = capExtractFps(probed.fps);
256
- if (targetFps >= Math.round(probed.fps)) return { path: inputPath, fps: targetFps, capped: false };
257
- onCap?.(targetFps);
258
- await runCollect(FFMPEG, normalizeArgs(inputPath, outputPath, targetFps), {
259
- children,
260
- timeoutMs,
261
- onLine: (line) => {
262
- const ratio = parseEncodeRatio(line, probed.durationS ?? 0);
263
- if (ratio !== null) onProgress?.(ratio);
264
- },
265
- });
266
- return { path: outputPath, fps: targetFps, capped: true };
267
- }
268
-
269
- /**
270
- * POST /ardy/footage — body {url}, answer ndjson:
271
- * {event:"status", message} stage announcements
272
- * {event:"progress", stage, ratio} download / normalize movement
273
- * {event:"done", url, title, durationS, fps}
274
- * {event:"error", message} message is a NAMED reason
275
- */
276
- export async function handleFootage(req, res, readBody) {
277
- let body;
278
- try {
279
- body = JSON.parse(await readBody(req));
280
- } catch (err) {
281
- res.writeHead(400, { "Content-Type": "application/json" });
282
- res.end(`${JSON.stringify({ ok: false, reason: err.message })}\n`);
283
- return;
284
- }
285
- const url = typeof body?.url === "string" ? body.url.trim() : "";
286
- if (!/^https?:\/\/\S{1,2000}$/.test(url)) {
287
- res.writeHead(400, { "Content-Type": "application/json" });
288
- res.end(`${JSON.stringify({ ok: false, reason: "footage-url-invalid" })}\n`);
289
- return;
290
- }
291
-
292
- res.writeHead(200, { "Content-Type": "application/x-ndjson", "Cache-Control": "no-store" });
293
- const send = (obj) => {
294
- if (res.writableEnded) return;
295
- try {
296
- res.write(`${JSON.stringify(obj)}\n`);
297
- } catch {
298
- /* socket gone */
299
- }
300
- };
301
- let cleanupArtifacts = () => {};
302
- const fail = (message) => {
303
- send({ event: "error", message });
304
- res.end();
305
- cleanupArtifacts();
306
- };
307
-
308
- const children = new Set();
309
- res.on("close", () => {
310
- if (!res.writableEnded) {
311
- console.error(`[bridge] client disconnected mid-footage; killing ${children.size} child group(s)`);
312
- for (const child of children) killGroup(child);
313
- children.clear();
314
- cleanupArtifacts();
315
- }
316
- });
317
-
318
- const stamp = `${Date.now()}-${randomBytes(3).toString("hex")}`;
319
- const artifactDir = createPrivateArtifactDir(FOOTAGE_DIR, "footage");
320
- const rawPrefix = "raw";
321
- const outPath = join(artifactDir, "normalized.mp4");
322
- cleanupArtifacts = () => removePrivateArtifactDir(artifactDir);
323
-
324
- // 1) probe: refuse lives (no duration) and long videos before any bytes.
325
- send({ event: "status", message: "probing" });
326
- let probe;
327
- try {
328
- const { out } = await runYtdlp(
329
- ["--print", "%(duration)s\t%(fps)s\t%(title)s", url],
330
- { children, timeoutMs: 60000 }
331
- );
332
- probe = parseProbeLine(out.split("\n").find((line) => line.includes("\t")) ?? "");
333
- } catch (err) {
334
- console.error(`[bridge] footage probe failed: ${err.message}`);
335
- fail("footage-probe-failed");
336
- return;
337
- }
338
- if (!probe) {
339
- fail("footage-live-unsupported");
340
- return;
341
- }
342
- if (probe.durationS > FOOTAGE_MAX_S) {
343
- fail("footage-too-long");
344
- return;
345
- }
346
-
347
- // 2) download the video stream only (audio never reaches the ingest).
348
- send({ event: "status", message: "downloading" });
349
- try {
350
- await runYtdlp(
351
- [
352
- "--newline",
353
- "-f", `bv*[height<=${FOOTAGE_MAX_HEIGHT}]/b[height<=${FOOTAGE_MAX_HEIGHT}]/b`,
354
- "-o", join(artifactDir, `${rawPrefix}.%(ext)s`),
355
- url,
356
- ],
357
- {
358
- children,
359
- timeoutMs: 10 * 60 * 1000,
360
- onLine: (line) => {
361
- const ratio = parseDownloadRatio(line);
362
- if (ratio !== null) send({ event: "progress", stage: "download", ratio });
363
- },
364
- }
365
- );
366
- } catch (err) {
367
- console.error(`[bridge] footage download failed: ${err.message}`);
368
- cleanupArtifacts();
369
- fail("footage-download-failed");
370
- return;
371
- }
372
- const rawName = readdirSync(artifactDir).find((f) => f.startsWith(rawPrefix));
373
- if (!rawName) {
374
- fail("footage-download-failed");
375
- return;
376
- }
377
- const rawPath = join(artifactDir, rawName);
378
-
379
- // 3) lock to a CONSTANT rate at the source's own fps, capped at the rate
380
- // extraction can use: VFR platform video would otherwise make "frame 385"
381
- // a different instant than the timeline believes, and resampling to a
382
- // foreign rate would judder the motion. Everything downstream — the done
383
- // event below, the browser probe, the retime — is told THIS number, so the
384
- // clip's declared rate is the rate SAM will actually meet.
385
- const targetFps = capExtractFps(probe.fps);
386
- send({ event: "status", message: "normalizing" });
387
- try {
388
- await runCollect(
389
- FFMPEG,
390
- normalizeArgs(rawPath, outPath, targetFps),
391
- {
392
- children,
393
- timeoutMs: 10 * 60 * 1000,
394
- onLine: (line) => {
395
- const ratio = parseEncodeRatio(line, probe.durationS);
396
- if (ratio !== null) send({ event: "progress", stage: "normalize", ratio });
397
- },
398
- }
399
- );
400
- } catch (err) {
401
- console.error(`[bridge] footage normalize failed: ${err.message}`);
402
- cleanupArtifacts();
403
- fail("footage-normalize-failed");
404
- return;
405
- }
406
- cleanup([rawPath]);
407
-
408
- footageAllowlist.set(stamp, outPath);
409
- if (footageAllowlist.size > FOOTAGE_ALLOWLIST_MAX) {
410
- const oldest = footageAllowlist.keys().next().value;
411
- removePrivateArtifactDir(dirname(footageAllowlist.get(oldest)));
412
- footageAllowlist.delete(oldest);
413
- }
414
- send({
415
- event: "done",
416
- url: `/ardy/footage/${stamp}.mp4`,
417
- footage: stamp,
418
- title: probe.title,
419
- durationS: probe.durationS,
420
- fps: targetFps,
421
- });
422
- res.end();
423
- }
424
-
425
- /** Absolute path for an allowlisted footage id, or null — the extract route
426
- * reuses a bridge-downloaded clip without a second trip through the browser. */
427
- export function footagePath(id) {
428
- if (typeof id !== "string" || !FOOTAGE_ID.test(id)) return null;
429
- return footageAllowlist.get(id) ?? null;
430
- }
431
-
432
- /** GET /ardy/footage/<id>.mp4 — allowlist only, exactly like motion delivery. */
433
- export function serveFootage(req, res, pathname) {
434
- const sendJson = (status, obj) => {
435
- res.writeHead(status, { "Content-Type": "application/json", "Cache-Control": "no-store" });
436
- res.end(`${JSON.stringify(obj)}\n`);
437
- };
438
- const match = /^\/ardy\/footage\/([^/]+)\.mp4$/.exec(pathname);
439
- if (!match || !FOOTAGE_ID.test(match[1]) || !footageAllowlist.has(match[1])) {
440
- sendJson(404, { ok: false, reason: `unknown or expired footage "${match?.[1] ?? pathname}"` });
441
- return 404;
442
- }
443
- const absPath = footageAllowlist.get(match[1]);
444
- if (!absPath.startsWith(`${FOOTAGE_DIR}${sep}`)) {
445
- sendJson(404, { ok: false, reason: "footage path is outside the footage directory" });
446
- return 404;
447
- }
448
- let size;
449
- try {
450
- size = statSync(absPath).size;
451
- } catch {
452
- sendJson(404, { ok: false, reason: "footage is no longer on disk" });
453
- return 404;
454
- }
455
- res.writeHead(200, {
456
- "Content-Type": "video/mp4",
457
- "Content-Length": size,
458
- "Cache-Control": "no-store",
459
- });
460
- createReadStream(absPath)
461
- .on("error", (err) => {
462
- console.error(`[bridge] error streaming ${absPath}: ${err.message}`);
463
- res.destroy();
464
- })
465
- .pipe(res);
466
- return 200;
467
- }
@@ -1,102 +0,0 @@
1
- /** Root-only floor safety for an accepted GVHMR descent correction.
2
- * Measures both shipped character skins with the same playback transforms
3
- * as Studio. It never runs AutoPhysics, writes IK keys or changes rotations.
4
- */
5
- import { existsSync, readFileSync } from "node:fs";
6
- import { fileURLToPath } from "node:url";
7
- import { FBXLoader } from "three/examples/jsm/loaders/FBXLoader.js";
8
- import { applyMotionFrame } from "../../src/ardy/playback.js";
9
- import { createSurfaceSampler } from "../../src/ardy/physics-surface.js";
10
-
11
- const SITES = [
12
- { id: "leftFoot", bone: "LeftFoot", kind: "foot", match: /Left(Foot|Toe)/ },
13
- { id: "rightFoot", bone: "RightFoot", kind: "foot", match: /Right(Foot|Toe)/ },
14
- { id: "leftHand", bone: "LeftHand", kind: "hand", match: /LeftHand/ },
15
- { id: "rightHand", bone: "RightHand", kind: "hand", match: /RightHand/ },
16
- ];
17
- function shiftFrame(motion, f, delta) {
18
- motion.rootPos[f * 3 + 1] += delta;
19
- for (let j = 0; j < 27; j++) motion.posedJoints[(f * 27 + j) * 3 + 1] += delta;
20
- }
21
- function loadRig(model) {
22
- const path = ["public", "dist"].map(dir => fileURLToPath(new URL(`../../${dir}/models/${model}.fbx`, import.meta.url))).find(existsSync);
23
- if (!path) throw new Error(`extract-trajectory-floor-model-missing: ${model}`);
24
- const bytes = readFileSync(path);
25
- const rig = new FBXLoader().parse(bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength), "");
26
- rig.scale.setScalar(.01); rig.updateMatrixWorld(true);
27
- return rig;
28
- }
29
-
30
- export function guardTrajectoryFloor(motion, events = []) {
31
- if (!events.length) return { motion, diagnostics: { status: "not-needed", changedFrames: 0 } };
32
- const began = performance.now();
33
- // Export/retarget grounding uses skeletal foot points, not skin thickness.
34
- // Once this pass owns a corrected take, protect its whole surface timeline
35
- // (including the initial standing frames), not only the final descent.
36
- const start = 0;
37
- const descentStart = Math.max(0, Math.min(...events.map(e => e.start)));
38
- const lifts = new Float64Array(motion.frames);
39
- const models = ["x-bot-tpose", "y-bot-tpose"];
40
- const measurements = [];
41
- for (const model of models) {
42
- const rig = loadRig(model), sample = createSurfaceSampler(rig, SITES);
43
- applyMotionFrame(rig, motion, start);
44
- const y = sample().pelvis.position.y;
45
- const probe = { ...motion, rootPos: motion.rootPos.slice(), posedJoints: motion.posedJoints.slice() };
46
- shiftFrame(probe, start, 1); applyMotionFrame(rig, probe, start);
47
- const scale = sample().pelvis.position.y - y;
48
- if (!(scale > .5 && scale < 2)) throw new Error("extract-trajectory-floor-invalid-scale");
49
- const floor = new Float64Array(motion.frames);
50
- for (let f = start; f < motion.frames; f++) {
51
- applyMotionFrame(rig, motion, f);
52
- floor[f] = Math.min(...Object.values(sample()).map(p => p.floor));
53
- lifts[f] = Math.max(lifts[f], (0.002 - floor[f]) / scale, 0);
54
- }
55
- measurements.push({ model, scale, floor });
56
- // These are private measurement rigs, not cached live user characters.
57
- rig.traverse(o => { o.geometry?.dispose(); if (Array.isArray(o.material)) o.material.forEach(m => m.dispose()); else o.material?.dispose(); });
58
- }
59
- // A camera-scaled endpoint is not yet calibrated to the target skin.
60
- // Adjust its landing datum from measured contact clearance, distributing
61
- // that change over the observed descent instead of abruptly lifting the
62
- // body at impact. Only raise an endpoint that penetrates; never assume a
63
- // stationary landing on an elevated object must be lowered to the floor.
64
- const datum = new Float64Array(motion.frames);
65
- const median = values => { const a = [...values].sort((x, y) => x - y); return a[Math.floor(a.length / 2)]; };
66
- for (const event of events) {
67
- if (event.endpointSource !== "observed-plateau") continue;
68
- const { start: from, landing, anchor } = event;
69
- const lift = median(lifts.slice(landing, anchor + 1));
70
- if (lift > .5) throw new Error("extract-trajectory-endpoint-clearance-too-large");
71
- const firstY = motion.rootPos[from * 3 + 1];
72
- const lastY = median(Array.from({ length: anchor - landing + 1 }, (_, i) => motion.rootPos[(landing + i) * 3 + 1]));
73
- for (let f = from; f < motion.frames; f++) {
74
- const progress = f >= landing ? 1 : Math.max(0, Math.min(1, (firstY - motion.rootPos[f * 3 + 1]) / Math.max(.01, firstY - lastY)));
75
- datum[f] = Math.max(datum[f], lift * progress);
76
- }
77
- }
78
- const residual = lifts.map((v, f) => Math.max(0, v - datum[f]));
79
- if (Math.max(...residual) > .25) throw new Error("extract-trajectory-floor-correction-too-large");
80
- // A conservative smooth upper envelope cannot reintroduce penetration.
81
- const radius = Math.max(1, Math.round(motion.fps * .1));
82
- // Once landed, use each pose's actual clearance instead of keeping the
83
- // median datum as a permanent cushion (which can itself leave a hover).
84
- const desired = lifts.slice();
85
- for (const event of events) if (event.endpointSource === "observed-plateau") {
86
- for (let f = event.start; f < event.landing; f++) desired[f] = Math.max(desired[f], datum[f]);
87
- }
88
- const safe = desired.slice();
89
- for (let f = start; f < motion.frames; f++) for (let j = Math.max(start, f - radius); j <= Math.min(motion.frames - 1, f + radius); j++) {
90
- const t = Math.abs(f - j) / (radius + 1), fade = 1 - t * t * (3 - 2 * t);
91
- safe[f] = Math.max(safe[f], desired[j] * fade);
92
- }
93
- const corrected = { ...motion, rootPos: motion.rootPos.slice(), posedJoints: motion.posedJoints.slice() };
94
- for (let f = start; f < motion.frames; f++) if (safe[f]) shiftFrame(corrected, f, safe[f]);
95
- const diagnostics = { status: "verified", start, descentStart, changedFrames: [...safe].filter(v => v > 1e-6).length,
96
- maxLiftM: Math.max(...safe), endpointDatumLiftM: Math.max(...datum), seconds: (performance.now() - began) / 1000,
97
- models: measurements.map(({ model, scale, floor }) => ({ model,
98
- minimumBeforeM: Math.min(...floor.slice(start)),
99
- minimumAfterM: Math.min(...floor.slice(start).map((v, i) => v + safe[start + i] * scale)),
100
- })), };
101
- return { motion: corrected, diagnostics };
102
- }
@@ -1,21 +0,0 @@
1
- #!/usr/bin/env node
2
- import { readNpz } from "../kimodo/read-npz.mjs";
3
- import { motionArraysToNpzMembers, writeNpz } from "./npz.mjs";
4
- import { smplToCskel27Motion } from "./smpl-cskel27.mjs";
5
-
6
- const args = process.argv.slice(2);
7
- const input = args[0], output = args[1];
8
- let boneScale = "derive";
9
- for (let i = 2; i < args.length; i += 1) {
10
- if (args[i] !== "--bone-scale" || i + 1 >= args.length) { console.error("usage: node tools/ardy/gvhmr-to-cskel27.mjs <gvhmr.npz> <motion.npz> [--bone-scale derive|number]"); process.exit(2); }
11
- const value = args[++i];
12
- if (value !== "derive") { boneScale = Number(value); if (!(Number.isFinite(boneScale) && boneScale > 0)) { console.error("--bone-scale must be derive or a positive number"); process.exit(2); } }
13
- }
14
- if (!input || !output) {
15
- console.error("usage: node tools/ardy/gvhmr-to-cskel27.mjs <gvhmr.npz> <motion.npz> [--bone-scale derive|number]");
16
- process.exit(2);
17
- }
18
-
19
- const motion = smplToCskel27Motion(readNpz(input), { boneScale });
20
- writeNpz(output, motionArraysToNpzMembers(motion));
21
- console.log(JSON.stringify({ output, frames: motion.frames, fps: motion.fps, boneScale }));