cozyclay 2.1.1 → 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 (577) hide show
  1. package/CHANGELOG.md +60 -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 +3 -3
  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-msLI71g1.js → first-shot-handoff-CC4xe4HM.js} +1 -1
  33. package/dist/assets/{landing-ChUvB_zL.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/sw.js +3 -6
  40. package/dist/workflow/index.html +5 -4
  41. package/mcp/LIVE-PROTOCOL.md +7 -47
  42. package/mcp/README.md +16 -26
  43. package/mcp/live-hub.mjs +6 -10
  44. package/mcp/package.json +1 -3
  45. package/mcp/tool-handlers.mjs +4 -168
  46. package/package.json +3 -22
  47. package/src/App.jsx +150 -2624
  48. package/src/agent-progress.js +79 -0
  49. package/src/agent-turn-mark.js +30 -0
  50. package/src/analytics.js +1 -19
  51. package/src/app-context.js +5 -0
  52. package/src/app-stage.jsx +2 -34
  53. package/src/asset-pane.jsx +6 -5
  54. package/src/camera-block.js +21 -0
  55. package/src/camera-direction.js +120 -0
  56. package/src/character-kind.js +3 -9
  57. package/src/command-bus.js +8 -6
  58. package/src/commands/cast.js +5 -34
  59. package/src/commands/elements/character.js +1 -1
  60. package/src/commands/elements/motion.js +1 -6
  61. package/src/commands/elements/object.js +6 -2
  62. package/src/commands/elements/shot.js +4 -1
  63. package/src/commands/elements.js +38 -14
  64. package/src/commands/index.js +3 -1
  65. package/src/commands/kits.js +37 -0
  66. package/src/commands/motion-timing.js +57 -0
  67. package/src/commands/motion.js +9 -152
  68. package/src/commands/motion3-compile.js +65 -0
  69. package/src/commands/motion3.js +207 -0
  70. package/src/commands/objects.js +6 -4
  71. package/src/commands/shot.js +49 -11
  72. package/src/commands/stage.js +14 -1
  73. package/src/commands/view.js +4 -1
  74. package/src/document-mirrors.js +42 -0
  75. package/src/document-store.js +4 -0
  76. package/src/domains/cast.js +19 -194
  77. package/src/domains/motion/motion-range-pin-rules.js +1 -1
  78. package/src/domains/motion/take-source.js +51 -0
  79. package/src/domains/motion.js +60 -1838
  80. package/src/domains/scenes.js +12 -55
  81. package/src/domains/shots.js +2 -2
  82. package/src/domains/stage.js +4 -2
  83. package/src/draft-ghost.js +46 -0
  84. package/src/draft-ghost.jsx +129 -0
  85. package/src/dualview.jsx +4 -3
  86. package/src/effects/EffectsLayer.jsx +112 -0
  87. package/src/effects/effect-meshes.js +232 -0
  88. package/src/effects/effect-record.js +103 -0
  89. package/src/effects/emitter.js +59 -0
  90. package/src/effects/simulate.js +261 -0
  91. package/src/effects/state.js +59 -0
  92. package/src/execution-telemetry.js +2 -3
  93. package/src/first-success-guide.jsx +5 -5
  94. package/src/ghost-set.js +257 -0
  95. package/src/held-object-edit.jsx +113 -0
  96. package/src/held-object-pose.js +175 -0
  97. package/src/hierarchy-model.js +1 -1
  98. package/src/hierarchy-panel.jsx +1 -1
  99. package/src/imported-mesh.jsx +103 -0
  100. package/src/kits/held.js +84 -0
  101. package/src/kits/index.js +70 -0
  102. package/src/kits/smoking.js +57 -0
  103. package/src/light-blockers.js +93 -0
  104. package/src/motion/beat-board-model.js +84 -0
  105. package/src/motion/beat-board.css +124 -0
  106. package/src/motion/beat-board.jsx +59 -0
  107. package/src/motion/center-of-mass.js +66 -0
  108. package/src/motion/fix-collisions.js +850 -0
  109. package/src/motion/physics-review.js +118 -0
  110. package/src/{ardy → motion}/physics-support.js +1 -76
  111. package/src/motion/rig-snapshot.js +22 -0
  112. package/src/motion/take-segments.jsx +140 -0
  113. package/src/{ardy → motion}/timeline-coordinates.js +0 -21
  114. package/src/{ardy → motion}/timeline.css +1 -2
  115. package/src/{ardy → motion}/timeline.jsx +42 -413
  116. package/src/motion/waypoints.js +76 -0
  117. package/src/motion3/beats.js +83 -0
  118. package/src/motion3/body.js +158 -0
  119. package/src/motion3/bones.js +47 -0
  120. package/src/motion3/carried.js +47 -0
  121. package/src/motion3/compile-job.js +75 -0
  122. package/src/motion3/compile-worker.js +10 -0
  123. package/src/motion3/compiler/anchors.js +105 -0
  124. package/src/motion3/compiler/arm-orient.js +128 -0
  125. package/src/motion3/compiler/channels.js +125 -0
  126. package/src/motion3/compiler/compile.js +205 -0
  127. package/src/motion3/compiler/gait-rhythm.js +97 -0
  128. package/src/motion3/compiler/grip.js +171 -0
  129. package/src/motion3/compiler/head-steady.js +48 -0
  130. package/src/motion3/compiler/hold.js +134 -0
  131. package/src/motion3/compiler/idle.js +113 -0
  132. package/src/motion3/compiler/limbs.js +170 -0
  133. package/src/motion3/compiler/math.js +72 -0
  134. package/src/motion3/compiler/pose.js +162 -0
  135. package/src/motion3/compiler/quat.js +49 -0
  136. package/src/motion3/compiler/skeleton.js +74 -0
  137. package/src/motion3/compiler/validate-common.js +32 -0
  138. package/src/motion3/compiler/validate-goals.js +118 -0
  139. package/src/motion3/compiler/validate-objects.js +143 -0
  140. package/src/motion3/compiler/validate-parts.js +116 -0
  141. package/src/motion3/compiler/validate.js +122 -0
  142. package/src/motion3/compiler/vocab.js +73 -0
  143. package/src/motion3/compiler/walk-pelvis.js +275 -0
  144. package/src/motion3/compiler/walk.js +207 -0
  145. package/src/motion3/compiler/wrist-clamp.js +32 -0
  146. package/src/motion3/delta.js +47 -0
  147. package/src/motion3/describe.js +174 -0
  148. package/src/motion3/docs.js +27 -0
  149. package/src/motion3/edit-carry.js +21 -0
  150. package/src/motion3/fix/ease.js +16 -0
  151. package/src/motion3/fix/ground.js +37 -0
  152. package/src/motion3/fix/index.js +52 -0
  153. package/src/motion3/fix/push.js +71 -0
  154. package/src/motion3/fix/reach.js +46 -0
  155. package/src/motion3/fix/retime.js +79 -0
  156. package/src/motion3/fix/self.js +211 -0
  157. package/src/motion3/fix/slide.js +70 -0
  158. package/src/motion3/fix/turn.js +51 -0
  159. package/src/motion3/follow.js +39 -0
  160. package/src/motion3/free-motion.js +173 -0
  161. package/src/motion3/grip-choice.js +130 -0
  162. package/src/motion3/hold-plan.js +157 -0
  163. package/src/motion3/json-patch.js +77 -0
  164. package/src/motion3/key-aim.js +105 -0
  165. package/src/motion3/key-edit.js +82 -0
  166. package/src/motion3/lenient.js +127 -0
  167. package/src/motion3/object-keys.js +236 -0
  168. package/src/motion3/object-pose.js +182 -0
  169. package/src/motion3/object-tracks.js +12 -0
  170. package/src/motion3/pipeline.js +102 -0
  171. package/src/motion3/playback.js +51 -0
  172. package/src/motion3/probes.js +91 -0
  173. package/src/motion3/qa/body-checks.js +34 -0
  174. package/src/motion3/qa/check-names.js +14 -0
  175. package/src/motion3/qa/checks.js +121 -0
  176. package/src/motion3/qa/contact-checks.js +51 -0
  177. package/src/motion3/qa/explain.js +80 -0
  178. package/src/motion3/qa/fmt.js +5 -0
  179. package/src/motion3/qa/foot-checks.js +72 -0
  180. package/src/motion3/qa/geometry.js +77 -0
  181. package/src/motion3/qa/goals.js +191 -0
  182. package/src/motion3/qa/ground.js +60 -0
  183. package/src/motion3/qa/hints-contact.js +179 -0
  184. package/src/motion3/qa/hints-goal.js +160 -0
  185. package/src/motion3/qa/hints-knee.js +24 -0
  186. package/src/motion3/qa/hints-motion.js +209 -0
  187. package/src/motion3/qa/hints-object.js +91 -0
  188. package/src/motion3/qa/index.js +42 -0
  189. package/src/motion3/qa/key-checks.js +82 -0
  190. package/src/motion3/qa/near.js +96 -0
  191. package/src/motion3/qa/object-checks.js +135 -0
  192. package/src/motion3/qa/patch.js +161 -0
  193. package/src/motion3/qa/severity.js +38 -0
  194. package/src/motion3/qa/spec-checks.js +65 -0
  195. package/src/motion3/qa/style.js +218 -0
  196. package/src/motion3/qa/wrist-checks.js +69 -0
  197. package/src/motion3/qa/wrist-obs.js +60 -0
  198. package/src/motion3/refs.js +120 -0
  199. package/src/motion3/repair-tune.js +121 -0
  200. package/src/motion3/repair.js +83 -0
  201. package/src/motion3/report.js +67 -0
  202. package/src/motion3/rigs/index.js +14 -0
  203. package/src/motion3/rigs/x-bot-tpose-body.json +1 -0
  204. package/src/motion3/rigs/x-bot-tpose.json +1 -0
  205. package/src/motion3/rigs/y-bot-tpose-body.json +1 -0
  206. package/src/motion3/rigs/y-bot-tpose.json +1 -0
  207. package/src/motion3/scene.js +60 -0
  208. package/src/motion3/schema/brief.js +89 -0
  209. package/src/motion3/schema/fields.js +44 -0
  210. package/src/motion3/schema/spec-examples.js +46 -0
  211. package/src/motion3/schema/topics.js +111 -0
  212. package/src/motion3/schema/types.js +147 -0
  213. package/src/motion3/search.js +117 -0
  214. package/src/motion3/second-hand.js +112 -0
  215. package/src/motion3/skills/basic.js +82 -0
  216. package/src/motion3/skills/book.js +76 -0
  217. package/src/motion3/skills/car.js +145 -0
  218. package/src/motion3/skills/frames-auto.js +29 -0
  219. package/src/motion3/skills/index.js +83 -0
  220. package/src/motion3/skills/info.js +65 -0
  221. package/src/motion3/skills/smoke-kit.js +109 -0
  222. package/src/motion3/skills/smoke.js +160 -0
  223. package/src/motion3/spec-store.js +59 -0
  224. package/src/motion3/take-edit.js +155 -0
  225. package/src/motion3/take.js +63 -0
  226. package/src/motion3/timeline.js +127 -0
  227. package/src/motion3/views.js +121 -0
  228. package/src/motion3/walk-plan.js +119 -0
  229. package/src/object-catalog.jsx +7 -1
  230. package/src/object-colors.js +98 -0
  231. package/src/object-gizmo-parts.js +115 -0
  232. package/src/object-gizmo.jsx +37 -122
  233. package/src/object-glow.js +139 -0
  234. package/src/object-hidden.js +33 -0
  235. package/src/object-look.js +196 -0
  236. package/src/object-size.js +10 -0
  237. package/src/object-travel.js +71 -6
  238. package/src/panels/CameraPanel.jsx +7 -7
  239. package/src/panels/HdrEnvironmentControls.jsx +59 -0
  240. package/src/panels/LightPanel.jsx +25 -4
  241. package/src/panels/ObjectLookPanel.jsx +88 -0
  242. package/src/panels/PosePanel.jsx +2 -17
  243. package/src/panels/RigControlPanel.jsx +5 -53
  244. package/src/panels/SubjectBox.jsx +4 -1
  245. package/src/panels/details.css +14 -0
  246. package/src/posestudio.jsx +4 -27
  247. package/src/project-browser.jsx +3 -4
  248. package/src/project-resources.js +18 -31
  249. package/src/project.js +13 -104
  250. package/src/props.jsx +25 -280
  251. package/src/rail-dot-drag.js +23 -0
  252. package/src/render-passes.js +10 -0
  253. package/src/resource-status.jsx +1 -0
  254. package/src/result-modal.jsx +4 -19
  255. package/src/room.jsx +20 -12
  256. package/src/scene-assets.js +8 -6
  257. package/src/scene-light-place.js +72 -0
  258. package/src/scene-lights.jsx +148 -0
  259. package/src/scene-objects.js +31 -139
  260. package/src/scenes.js +15 -105
  261. package/src/semantic-edit.js +2 -5
  262. package/src/set-pieces.jsx +183 -0
  263. package/src/settings-menu.jsx +3 -11
  264. package/src/shell/BottomDock.jsx +9 -51
  265. package/src/shell/DetailsSlot.jsx +16 -200
  266. package/src/shell/GhostsMenuGroup.jsx +74 -0
  267. package/src/shell/LibrarySlot.jsx +1 -3
  268. package/src/shell/MenuBar.jsx +3 -3
  269. package/src/shell/PreferencesDialog.jsx +15 -264
  270. package/src/shell/PreferencesSlot.jsx +2 -8
  271. package/src/shell/TopBar.jsx +5 -84
  272. package/src/shell/ViewportToolbar.jsx +10 -70
  273. package/src/shell/agent-glass.css +19 -6
  274. package/src/shell/details-selection.js +42 -0
  275. package/src/shell/ghosts-menu.css +22 -0
  276. package/src/shell/glass-regions.css +29 -3
  277. package/src/shell/preferences.css +6 -1
  278. package/src/shot-authoring.js +4 -7
  279. package/src/soft-shadows.js +37 -0
  280. package/src/stage-attach-frames.js +12 -12
  281. package/src/stage-canvas-rigs.jsx +3 -3
  282. package/src/stage-capture.jsx +42 -8
  283. package/src/stage-characters.jsx +22 -216
  284. package/src/stage-editor-gizmos.jsx +6 -6
  285. package/src/stage-environment.js +93 -0
  286. package/src/stage-hdr-builtins.js +107 -0
  287. package/src/stage-hdr-image.js +116 -0
  288. package/src/stage-hdr-library.js +70 -0
  289. package/src/stage-hdr-map.js +94 -0
  290. package/src/stage-hdr.js +130 -0
  291. package/src/stage-lighting.js +149 -0
  292. package/src/stage-occlusion.js +210 -0
  293. package/src/stage-sky.jsx +159 -0
  294. package/src/stage-startup-defaults.js +5 -9
  295. package/src/studio-actions.js +12 -14
  296. package/src/studio-agent-commands.js +19 -16
  297. package/src/studio-agent-motion.js +9 -9
  298. package/src/studio-agent-protocol.js +41 -71
  299. package/src/studio-app-binding.js +25 -45
  300. package/src/studio-asset-list.js +17 -0
  301. package/src/studio-contact-sheet.js +37 -5
  302. package/src/studio-elements.js +27 -7
  303. package/src/studio-entity-bounds.js +18 -0
  304. package/src/studio-error-codes.js +8 -0
  305. package/src/studio-export-framing.js +152 -0
  306. package/src/studio-lighting-check.js +103 -0
  307. package/src/studio-motion-visual.js +150 -0
  308. package/src/studio-schema-errors.js +80 -0
  309. package/src/studio-support-gap.js +74 -0
  310. package/src/styles.css +1 -19
  311. package/src/timeline-defaults.js +5 -0
  312. package/src/timeline-extent.js +8 -20
  313. package/src/tutorial-walk.js +17 -0
  314. package/src/ui-scrub.js +1 -1
  315. package/src/use-shot-camera-tracks.js +12 -0
  316. package/src/view-ghosts.js +97 -0
  317. package/src/view-ghosts.jsx +49 -0
  318. package/src/workflow/AgentPanel.jsx +47 -166
  319. package/src/workflow/AgentProgressCard.jsx +107 -0
  320. package/src/workflow/AgentReplyText.jsx +37 -0
  321. package/src/workflow/AgentTurnSteps.jsx +84 -0
  322. package/src/workflow/LoginPanel.jsx +140 -0
  323. package/src/workflow/WorkflowBuilder.jsx +7 -23
  324. package/src/workflow/agent-client.js +54 -51
  325. package/src/workflow/agent-panel.css +71 -1
  326. package/src/workflow/agent-progress-card.css +167 -0
  327. package/src/workflow/agent-progress-view.js +47 -0
  328. package/src/workflow/agent-turn-steps.css +113 -0
  329. package/src/workflow/agent-turn.js +191 -0
  330. package/src/workflow/login-client.js +34 -0
  331. package/src/workflow/motion-input.js +18 -31
  332. package/src/workflow/node-schema.js +1 -1
  333. package/src/workflow/reply-markdown.js +69 -0
  334. package/src/workflow/scene-asset-sync.js +8 -8
  335. package/tools/agent-session-stats.mjs +61 -0
  336. package/tools/dev-full.mjs +5 -56
  337. package/tools/eslint-baseline.json +12 -13
  338. package/tools/knip-baseline.json +0 -179
  339. package/tools/loc-baseline.json +31 -81
  340. package/tools/motion3/extract-body.mjs +103 -0
  341. package/tools/motion3/extract-rig.mjs +85 -0
  342. package/tools/process-supervisor.mjs +0 -88
  343. package/tools/run-tests.mjs +8 -124
  344. package/bin/agent/motion-runtime.mjs +0 -76
  345. package/dist/ardy/cskel27-rest.json +0 -557
  346. package/dist/assets/analytics-C9pMpW7s.js +0 -1
  347. package/dist/assets/app-B_2XB9rw.css +0 -1
  348. package/dist/assets/app-DAuvKPP8.js +0 -4920
  349. package/dist/assets/config-Bx7pYsD9.js +0 -1
  350. package/dist/assets/config-DeoFTYzb.css +0 -1
  351. package/dist/assets/demo-CvKuBgy5.js +0 -1
  352. package/dist/assets/scene-asset-sync-DKkpjKBV.js +0 -19
  353. package/dist/assets/scene-asset-sync-DjJ_cJ2R.css +0 -1
  354. package/dist/assets/ticket-BPk2Ni95.js +0 -1
  355. package/dist/assets/workflow-jJRBR94Z.js +0 -179
  356. package/dist/demo/walk-then-stop.npz +0 -0
  357. package/mcp/ardy-prompts.mjs +0 -218
  358. package/src/ardy/auto-fix-panel.css +0 -108
  359. package/src/ardy/auto-physics.js +0 -1194
  360. package/src/ardy/client.js +0 -153
  361. package/src/ardy/convert.js +0 -313
  362. package/src/ardy/cskel27-neutral.js +0 -39
  363. package/src/ardy/cskel27.js +0 -77
  364. package/src/ardy/export.js +0 -165
  365. package/src/ardy/fix-collisions.js +0 -2257
  366. package/src/ardy/foot-lock.js +0 -230
  367. package/src/ardy/motion-calibration.js +0 -118
  368. package/src/ardy/motion-edit.js +0 -213
  369. package/src/ardy/motion-url.js +0 -55
  370. package/src/ardy/mp4-duration.js +0 -102
  371. package/src/ardy/npz.js +0 -595
  372. package/src/ardy/physics-panel.css +0 -21
  373. package/src/ardy/physics-panel.jsx +0 -89
  374. package/src/ardy/physics-refine.js +0 -111
  375. package/src/ardy/physics-review.js +0 -567
  376. package/src/ardy/physics-temporal.js +0 -54
  377. package/src/ardy/platform-fit-panel.css +0 -64
  378. package/src/ardy/platform-fit-panel.jsx +0 -41
  379. package/src/ardy/platform-fit.js +0 -326
  380. package/src/ardy/playback.js +0 -711
  381. package/src/ardy/pose-pin.js +0 -97
  382. package/src/ardy/prompt-clips.js +0 -30
  383. package/src/ardy/retime.js +0 -211
  384. package/src/ardy/root-drop.js +0 -369
  385. package/src/ardy/timeline-resize.js +0 -13
  386. package/src/ardy/to-cskel27.js +0 -225
  387. package/src/ardy/trim.js +0 -33
  388. package/src/ardy/waypoints.js +0 -369
  389. package/src/ardy-bridge-contract.js +0 -281
  390. package/src/domains/motion/motion-video-capture.js +0 -664
  391. package/src/i2v-motion-client.js +0 -77
  392. package/src/i2v-motion-studio.jsx +0 -180
  393. package/src/line-edit.js +0 -1348
  394. package/src/motion/generation.js +0 -105
  395. package/src/motion-readiness-ui.jsx +0 -67
  396. package/src/motion-readiness.js +0 -40
  397. package/src/motion-resources.js +0 -177
  398. package/src/motion-store.js +0 -65
  399. package/src/motion-trail.js +0 -601
  400. package/src/multimodel-ingest.js +0 -394
  401. package/src/panels/PromptBlocksPanel.jsx +0 -444
  402. package/src/panels/VideoCapturePanel.jsx +0 -181
  403. package/src/pose-extract/browser.js +0 -42
  404. package/src/pose-extract/detector.js +0 -100
  405. package/src/pose-extract/fit.js +0 -227
  406. package/src/pose-extract/image-frame.js +0 -51
  407. package/src/pose-extract/index.js +0 -58
  408. package/src/pose-extract/keyframes.js +0 -74
  409. package/src/pose-extract/landmarks.js +0 -162
  410. package/src/pose-extract/mirror.js +0 -153
  411. package/src/pose-extract/take.js +0 -131
  412. package/src/pose-extract/video-frames.js +0 -91
  413. package/src/take-recipe.js +0 -165
  414. package/src/trail-key-conflicts.js +0 -39
  415. package/src/trail-pick.js +0 -59
  416. package/src/use-i2v-capture-modal.js +0 -15
  417. package/tools/ardy/BRIDGE.md +0 -37
  418. package/tools/ardy/GVHMR-NPZ.md +0 -68
  419. package/tools/ardy/README.md +0 -124
  420. package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
  421. package/tools/ardy/artifacts.mjs +0 -34
  422. package/tools/ardy/bridge.mjs +0 -1732
  423. package/tools/ardy/bvh-cskel27.mjs +0 -1265
  424. package/tools/ardy/cclay_constrained_generate.py +0 -561
  425. package/tools/ardy/cclay_gvhmr_worker.py +0 -264
  426. package/tools/ardy/cclay_motion_edit.py +0 -510
  427. package/tools/ardy/cclay_sequence_generate.py +0 -705
  428. package/tools/ardy/cclay_worker.py +0 -304
  429. package/tools/ardy/dump-npz.py +0 -205
  430. package/tools/ardy/extract-rest.mjs +0 -299
  431. package/tools/ardy/extract.mjs +0 -507
  432. package/tools/ardy/footage.mjs +0 -467
  433. package/tools/ardy/gvhmr-floor.mjs +0 -102
  434. package/tools/ardy/gvhmr-to-cskel27.mjs +0 -21
  435. package/tools/ardy/gvhmr_fastpath.py +0 -168
  436. package/tools/ardy/gvhmr_trajectory.py +0 -240
  437. package/tools/ardy/merge-text-encoder.py +0 -160
  438. package/tools/ardy/mocap-metrics.mjs +0 -200
  439. package/tools/ardy/mocap-quality-gate.mjs +0 -110
  440. package/tools/ardy/motion-stabilize.mjs +0 -348
  441. package/tools/ardy/npz.mjs +0 -414
  442. package/tools/ardy/pose-to-npz.mjs +0 -106
  443. package/tools/ardy/prompt-limits.mjs +0 -5
  444. package/tools/ardy/run-edit-on-box.sh +0 -73
  445. package/tools/ardy/run-local.mjs +0 -233
  446. package/tools/ardy/run-on-box.sh +0 -593
  447. package/tools/ardy/run-sequence-on-box.sh +0 -177
  448. package/tools/ardy/runners/gvhmr-worker.mjs +0 -206
  449. package/tools/ardy/runners/index.mjs +0 -18
  450. package/tools/ardy/runners/local.mjs +0 -455
  451. package/tools/ardy/runners/proc.mjs +0 -125
  452. package/tools/ardy/runners/remote.mjs +0 -252
  453. package/tools/ardy/setup-local.mjs +0 -189
  454. package/tools/ardy/setup-text-encoder-on-box.sh +0 -93
  455. package/tools/ardy/setup-text-encoder.py +0 -306
  456. package/tools/ardy/smpl-cskel27.mjs +0 -178
  457. package/tools/ardy/visual-qa.mjs +0 -196
  458. package/tools/ardy/vq-car.mjs +0 -52
  459. package/tools/bench/EXP3.md +0 -61
  460. package/tools/bench/cclay_bench_extract_incam.py +0 -125
  461. package/tools/bench/cclay_bench_extract_obs.py +0 -204
  462. package/tools/bench/cclay_bench_runner.py +0 -42
  463. package/tools/bench/cube-contact.mjs +0 -162
  464. package/tools/bench/exp3.mjs +0 -152
  465. package/tools/bench/extract-bench-lib.mjs +0 -216
  466. package/tools/bench/extract-bench.mjs +0 -302
  467. package/tools/bench/fal-generate.mjs +0 -88
  468. package/tools/bench/fit/README.md +0 -189
  469. package/tools/bench/fit/camera.mjs +0 -59
  470. package/tools/bench/fit/contact.mjs +0 -419
  471. package/tools/bench/fit/footlock.mjs +0 -234
  472. package/tools/bench/fit/motion.mjs +0 -85
  473. package/tools/bench/fit/pin.mjs +0 -27
  474. package/tools/bench/fit/remote.mjs +0 -105
  475. package/tools/bench/fit-bench.mjs +0 -126
  476. package/tools/bench/fit-sanity.mjs +0 -45
  477. package/tools/bench/metrics.mjs +0 -313
  478. package/tools/bench/obs/depth.mjs +0 -258
  479. package/tools/bench/obs/extrinsics.mjs +0 -175
  480. package/tools/bench/obs/fit_mannequin_betas.py +0 -340
  481. package/tools/bench/obs/ground.mjs +0 -294
  482. package/tools/bench/obs/heading.mjs +0 -151
  483. package/tools/bench/obs/ladder.mjs +0 -555
  484. package/tools/bench/obs/mannequin-betas.json +0 -124
  485. package/tools/bench/obs/remote.mjs +0 -83
  486. package/tools/bench/obs/rest_joints.py +0 -52
  487. package/tools/bench/obs/ybot-targets.mjs +0 -49
  488. package/tools/bench/obs-bench.mjs +0 -520
  489. package/tools/bench/score.mjs +0 -343
  490. package/tools/bench/summarize.mjs +0 -86
  491. package/tools/demo-worker/README.md +0 -137
  492. package/tools/demo-worker/api-client.mjs +0 -233
  493. package/tools/demo-worker/generate.mjs +0 -301
  494. package/tools/demo-worker/index.mjs +0 -466
  495. package/tools/demo-worker/sign-core.mjs +0 -112
  496. package/tools/demo-worker/sign.mjs +0 -161
  497. package/tools/gt-render/browser.mjs +0 -182
  498. package/tools/gt-render/camera-math.mjs +0 -276
  499. package/tools/gt-render/page.mjs +0 -267
  500. package/tools/gt-render/render.mjs +0 -514
  501. package/tools/gt-render/scene-box.mjs +0 -31
  502. package/tools/gt-render/take-transform.mjs +0 -87
  503. package/tools/kimodo/compare-seam.mjs +0 -108
  504. package/tools/kimodo/constraints.mjs +0 -244
  505. package/tools/kimodo/edit.mjs +0 -259
  506. package/tools/kimodo/effector-constraints.mjs +0 -200
  507. package/tools/kimodo/generate.mjs +0 -646
  508. package/tools/kimodo/local-output.mjs +0 -265
  509. package/tools/kimodo/measure-pose.mjs +0 -65
  510. package/tools/kimodo/measure-preserve.mjs +0 -899
  511. package/tools/kimodo/measure-waypoints.mjs +0 -55
  512. package/tools/kimodo/mlx-generate.py +0 -111
  513. package/tools/kimodo/pose-constraints.mjs +0 -248
  514. package/tools/kimodo/preserve-mask.mjs +0 -543
  515. package/tools/kimodo/read-npz.mjs +0 -189
  516. package/tools/kimodo/run-edit-on-box.mjs +0 -155
  517. package/tools/kimodo/run-sequence-on-box.mjs +0 -178
  518. package/tools/kimodo/runner.mjs +0 -284
  519. package/tools/kimodo/seam-batch.mjs +0 -77
  520. package/tools/kimodo/setup-local.mjs +0 -30
  521. package/tools/kimodo/setup-on-box.sh +0 -339
  522. package/tools/kimodo/soma77-to-cskel27.mjs +0 -305
  523. package/tools/morphgs/assets/gen_truth.py +0 -39
  524. package/tools/morphgs/assets/mesh_ori_rig.txt +0 -27
  525. package/tools/morphgs/assets/playback-check.mjs +0 -30
  526. package/tools/morphgs/demo-gate.sh +0 -26
  527. package/tools/morphgs/fbx2morphgs.mjs +0 -68
  528. package/tools/morphgs/morphgs-to-cskel27.mjs +0 -105
  529. package/tools/morphgs/patches/preprocess_src-none-mode.patch +0 -76
  530. package/tools/morphgs/setup-on-cluster.sh +0 -126
  531. package/tools/projflow/driver.py +0 -1069
  532. package/tools/projflow/generate.mjs +0 -805
  533. package/tools/projflow/hml22-to-cskel27.mjs +0 -735
  534. package/tools/projflow/line-edit-job.mjs +0 -547
  535. package/tools/projflow/replay.mjs +0 -452
  536. package/tools/projflow/runner.mjs +0 -269
  537. package/tools/projflow/service.mjs +0 -742
  538. package/tools/track/DESIGN.md +0 -513
  539. package/tools/track/backfill-provenance.mjs +0 -116
  540. package/tools/track/budget.mjs +0 -8
  541. package/tools/track/check-rig.mjs +0 -209
  542. package/tools/track/diagnostics.schema.json +0 -60
  543. package/tools/track/export-rig.mjs +0 -256
  544. package/tools/track/fallback.mjs +0 -7
  545. package/tools/track/fk-parity-fixture.mjs +0 -161
  546. package/tools/track/gate.mjs +0 -349
  547. package/tools/track/masks.mjs +0 -318
  548. package/tools/track/metrics.mjs +0 -201
  549. package/tools/track/publish-obs.mjs +0 -108
  550. package/tools/track/py/check_env.py +0 -61
  551. package/tools/track/py/eval_lr.py +0 -229
  552. package/tools/track/py/lr_viterbi.py +0 -294
  553. package/tools/track/py/masks.py +0 -405
  554. package/tools/track/py/objective.py +0 -382
  555. package/tools/track/py/rig.py +0 -508
  556. package/tools/track/py/scene.py +0 -326
  557. package/tools/track/py/test_joint_indices.py +0 -123
  558. package/tools/track/py/test_lr_viterbi.py +0 -195
  559. package/tools/track/py/test_masks.py +0 -229
  560. package/tools/track/py/test_rig.py +0 -288
  561. package/tools/track/py/test_scene.py +0 -354
  562. package/tools/track/py/test_track.py +0 -328
  563. package/tools/track/py/track.py +0 -411
  564. package/tools/track/remote.mjs +0 -140
  565. package/tools/track/rig-dump.mjs +0 -227
  566. package/tools/track/run-box-tests.mjs +0 -14
  567. package/tools/track/run-box.mjs +0 -168
  568. package/tools/track/setup-box.sh +0 -59
  569. package/tools/track/study-2d.mjs +0 -728
  570. /package/src/{ardy → motion}/collision-blockers.js +0 -0
  571. /package/src/{ardy → motion}/ground.js +0 -0
  572. /package/src/{ardy → motion}/ik-drag.js +0 -0
  573. /package/src/{ardy → motion}/ik-key-json.js +0 -0
  574. /package/src/{ardy → motion}/ik.js +0 -0
  575. /package/src/{ardy → motion}/physics-surface.js +0 -0
  576. /package/src/{ardy → motion}/playback-clock.js +0 -0
  577. /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 }));