cozyclay 2.1.1 → 3.0.1

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