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,664 +0,0 @@
1
- import {
2
- I2V_MOTION_STILL_OUTPUT, I2V_MOTION_SHOT_ASPECT, I2V_MOTION_MIN_DURATION,
3
- waitForMotionJob, submitMotionJob, buildH3MotionPrompt,
4
- } from "../../i2v-motion-client.js";
5
- import * as THREE from "three";
6
- import { isKo, ko } from "../../locale.js";
7
- import {
8
- isPlatformPageUrl,
9
- normalizeSourceUrl,
10
- sourceLabel,
11
- requestBridgeFootage,
12
- fetchFootageBlob,
13
- probeFootage,
14
- requestBridgeExtract,
15
- } from "../../multimodel-ingest.js";
16
- import { characterScaleFor, loadMotionFromUrl } from "../../ardy/npz.js";
17
- import { autoRoofDrop, applyAutoFall } from "../../ardy/root-drop.js";
18
- import { takeAnchor, createCharacterEntry, isProxyFigure } from "../../scenes.js";
19
- import { kindRefusal } from "../../character-kind.js";
20
- import { retimeMotion } from "../../ardy/retime.js";
21
- import { createMotionEdit } from "../../ardy/motion-edit.js";
22
- import { DEFAULT_POSE } from "../../poses.js";
23
- import { startMotionRequest } from "../../analytics.js";
24
- import { MULTIMODEL_REASONS, nextCharacterId, TIMELINE_FPS } from "../../app-stage.jsx";
25
-
26
- export function createMotionVideoCapture({
27
- appContext,
28
- bridge,
29
- i2vMotion,
30
- setI2vMotion,
31
- multiModelUrl,
32
- multiModelSource,
33
- multiModelFootage,
34
- multiModelExtract,
35
- setMultiModelUrl,
36
- setMultiModelSource,
37
- setMultiModelStatus,
38
- setMultiModelStage,
39
- setMultiModelProgress,
40
- setMultiModelFootage,
41
- setMultiModelError,
42
- setMultiModelTake,
43
- setMultiModelExtract,
44
- setMultiModelExtractProgress,
45
- setMultiModelExtractError,
46
- loadMotion,
47
- authoredSupportDescriptors,
48
- applyAuthoredSupportRise,
49
- }) {
50
- /* ---------------------------- video capture ----------------------------
51
- * Footage in, takes out. The ingest downloads and decodes a source so the
52
- * timeline is sized by what was actually read; extraction then turns it
53
- * into one take per tracked performer. Take 0 belongs to the ACTIVE
54
- * character's layer, the rest are landed on their own cast members. */
55
- // A picked file is already local bytes: no download stage, straight to the
56
- // probe that produces the timeline numbers.
57
- function chooseMultiModelFile(event) {
58
- const file = event.target.files?.[0] ?? null;
59
- if (!file) return;
60
- setMultiModelUrl("");
61
- ingestFootage({ kind: "file", name: file.name, blob: file, bytes: file.size });
62
- }
63
-
64
- async function pasteMultiModelUrl() {
65
- try {
66
- const text = await navigator.clipboard.readText();
67
- if (!text.trim()) return;
68
- setMultiModelUrl(text.trim());
69
- setMultiModelStatus("idle");
70
- } catch {
71
- appContext.notify(isKo ? "클립보드를 읽지 못했어요 — 직접 붙여넣어 주세요" : "Clipboard is unavailable — paste into the field directly");
72
- }
73
- }
74
-
75
- function useMultiModelUrl() {
76
- const raw = multiModelUrl.trim();
77
- // A platform page (YouTube, Vimeo, …) is never browser-fetchable, but
78
- // the dev bridge can fetch it server-side. With the bridge up the
79
- // address goes there; without it the named refusal below stands.
80
- if (isPlatformPageUrl(raw) && bridge?.ok) {
81
- ingestPlatformFootage(raw);
82
- return;
83
- }
84
- const normalized = normalizeSourceUrl(raw);
85
- if (!normalized.ok) {
86
- setMultiModelStatus("error");
87
- setMultiModelStage("error");
88
- setMultiModelError(MULTIMODEL_REASONS[normalized.reason]?.[isKo ? 1 : 0] ?? normalized.reason);
89
- return;
90
- }
91
- ingestFootage({ kind: "url", name: sourceLabel(normalized.url), url: normalized.url });
92
- }
93
-
94
- /** Platform page → bridge download (yt-dlp + normalize) → the local
95
- * /ardy/footage/… address rides the ordinary ingest unchanged. */
96
- async function ingestPlatformFootage(pageUrl) {
97
- const run = appContext.shared.multiModelRunRef.current + 1;
98
- appContext.shared.multiModelRunRef.current = run;
99
- const live = () => appContext.shared.multiModelRunRef.current === run;
100
- setMultiModelSource({ kind: "url", name: sourceLabel(pageUrl), url: pageUrl });
101
- setMultiModelFootage(null);
102
- setMultiModelError("");
103
- setMultiModelStatus("busy");
104
- setMultiModelStage("fetching");
105
- setMultiModelProgress(null);
106
- setMultiModelTake(null);
107
- setMultiModelExtract("idle");
108
- setMultiModelExtractProgress(null);
109
- setMultiModelExtractError("");
110
- try {
111
- const footage = await requestBridgeFootage(pageUrl, {
112
- onProgress: ({ ratio }) => {
113
- if (live()) setMultiModelProgress(Number.isFinite(ratio) ? ratio : null);
114
- },
115
- });
116
- if (!live()) return;
117
- ingestFootage({ kind: "url", name: footage.title || sourceLabel(pageUrl), url: footage.url, fps: footage.fps, bridgeId: footage.footage ?? null });
118
- } catch (error) {
119
- if (!live()) return;
120
- const code = error?.message ?? String(error);
121
- setMultiModelStage("error");
122
- setMultiModelStatus("error");
123
- setMultiModelProgress(null);
124
- setMultiModelError(MULTIMODEL_REASONS[code]?.[isKo ? 1 : 0] ?? code);
125
- }
126
- }
127
-
128
- /** Download (when remote), decode, measure, then size the timeline from what
129
- * was actually read. Each run carries a token so a slow first source can
130
- * never land its numbers after a second one replaced it. */
131
- async function ingestFootage(source, commandContext = null) {
132
- const run = appContext.shared.multiModelRunRef.current + 1;
133
- appContext.shared.multiModelRunRef.current = run;
134
- const live = () => appContext.shared.multiModelRunRef.current === run;
135
- setMultiModelSource(source);
136
- setMultiModelFootage(null);
137
- setMultiModelError("");
138
- setMultiModelStatus("busy");
139
- setMultiModelProgress(null);
140
- // A take baked from the PREVIOUS clip must not read as this one's
141
- // result, so the extraction state resets with the source.
142
- setMultiModelTake(null);
143
- setMultiModelExtract("idle");
144
- setMultiModelExtractProgress(null);
145
- setMultiModelExtractError("");
146
- try {
147
- let blob = source.blob ?? null;
148
- let bytes = source.bytes ?? 0;
149
- if (!blob) {
150
- setMultiModelStage("fetching");
151
- const downloaded = await fetchFootageBlob(source.url, {
152
- onProgress: ({ ratio }) => {
153
- if (live()) setMultiModelProgress(ratio);
154
- },
155
- });
156
- if (!live()) return;
157
- blob = downloaded.blob;
158
- bytes = downloaded.bytes;
159
- }
160
- setMultiModelStage("probing");
161
- if (appContext.shared.multiModelObjectUrlRef.current) URL.revokeObjectURL(appContext.shared.multiModelObjectUrlRef.current);
162
- const objectUrl = URL.createObjectURL(blob);
163
- appContext.shared.multiModelObjectUrlRef.current = objectUrl;
164
- const probed = await probeFootage(objectUrl, {
165
- createVideo: () => document.createElement("video"),
166
- // The bridge normalized the clip and DECLARED its rate; a probe
167
- // that re-guesses it would overrule a measurement with a guess.
168
- knownFps: Number.isFinite(source.fps) ? source.fps : null,
169
- });
170
- if (!live()) return;
171
- commandContext?.check();
172
- const footage = { ...probed, bytes, objectUrl, blob, bridgeId: source.bridgeId ?? null };
173
- setMultiModelFootage(footage);
174
- setMultiModelStage("ready");
175
- setMultiModelStatus("ready");
176
- setMultiModelProgress(1);
177
- // The timeline is the point: the playhead now spans the footage that
178
- // was actually decoded, at the rate that was actually measured.
179
- appContext.shared.setTlFps(footage.fps);
180
- appContext.shared.setTlFrameCount(footage.frames);
181
- appContext.shared.setTlFrame(0);
182
- appContext.shared.setTlPlaying(false);
183
- appContext.notify((isKo, ko) => isKo
184
- ? `${source.name} 인제스트됨 — ${footage.frames}프레임 @ ${footage.fps} fps`
185
- : `Ingested ${source.name} — ${footage.frames} frames @ ${footage.fps} fps`);
186
- return footage;
187
- } catch (error) {
188
- if (commandContext && (commandContext.signal.aborted || error.code === "STALE_TARGET")) throw error;
189
- if (!live()) return;
190
- const code = error?.message ?? String(error);
191
- setMultiModelStage("error");
192
- setMultiModelStatus("error");
193
- setMultiModelProgress(null);
194
- setMultiModelError(MULTIMODEL_REASONS[code]?.[isKo ? 1 : 0] ?? code);
195
- }
196
- }
197
-
198
- /** Extract motion from the ingested footage. With the bridge up this goes
199
- * to the GPU box (GVHMR: whole-clip temporal context, real 3D body
200
- * prior — previs-grade). If the bridge is unavailable or is configured for
201
- * another backend, extraction stops with a named error. */
202
- async function extractMultiModelMotion() {
203
- if (!bridge?.ok) {
204
- setMultiModelExtract("error");
205
- setMultiModelExtractError(MULTIMODEL_REASONS["extract-bridge-required"]?.[isKo ? 1 : 0] ?? "extract-bridge-required");
206
- return;
207
- }
208
- if (bridge.extractionBackend !== "gvhmr") {
209
- setMultiModelExtract("error");
210
- setMultiModelExtractError(MULTIMODEL_REASONS["extract-backend-unsupported"]?.[isKo ? 1 : 0] ?? "extract-backend-unsupported");
211
- return;
212
- }
213
- return extractMultiModelMotionGpu();
214
- }
215
-
216
- async function extractMultiModelMotionGpu() {
217
- const footage = multiModelFootage;
218
- if (!footage || multiModelExtract === "running") return;
219
- if (isProxyFigure(appContext.shared.activeChar)) {
220
- setMultiModelExtract("error");
221
- setMultiModelExtractError(kindRefusal("mocap", isKo));
222
- return;
223
- }
224
- const run = appContext.shared.multiModelRunRef.current;
225
- const live = () => appContext.shared.multiModelRunRef.current === run;
226
- setMultiModelExtract("running");
227
- setMultiModelExtractProgress(null);
228
- setMultiModelExtractError("");
229
- setMultiModelTake(null);
230
- try {
231
- const done = await requestBridgeExtract(
232
- footage.bridgeId ? { footage: footage.bridgeId } : footage.blob,
233
- {
234
- onProgress: ({ ratio }) => {
235
- if (live()) setMultiModelExtractProgress(Number.isFinite(ratio) ? ratio : null);
236
- },
237
- }
238
- );
239
- if (!live()) return;
240
- if (done.quality && done.quality.pass === false) {
241
- const failed = Array.isArray(done.quality.checks)
242
- ? done.quality.checks.filter((check) => check && check.pass === false).map((check) => check.name).join(", ")
243
- : "quality";
244
- appContext.notify(isKo
245
- ? `모캡 품질 경고: ${failed || "검증 실패"} — 결과는 로드하지만 보정이 필요합니다`
246
- : `Mocap quality warning: ${failed || "validation failed"} — loaded for review, correction required`);
247
- }
248
- // One take per tracked performer. An older bridge sends a single
249
- // motionUrl and no list; that is the same thing with one entry.
250
- const takes = Array.isArray(done.takes) && done.takes.length
251
- ? done.takes
252
- : [{ motionUrl: done.motionUrl, personScale: done.personScale, offsetX: 0, offsetZ: 0 }];
253
- const label = multiModelSource?.name ?? "extracted take";
254
- // The cast can change while the GPU works, so the destination is read
255
- // now, once, and every take is placed against THIS character.
256
- const active = appContext.live.characters.find((entry) => entry.id === appContext.shared.activeChar.id) ?? appContext.shared.activeChar;
257
- // Take 0 arrives as an ordinary motion npz; loadMotion decodes,
258
- // retimes to the 24 fps timeline, snapshots the rig baseline and
259
- // applies the stature stored in the take itself — the body matches
260
- // the FILMED person because the file carries the measurement, not
261
- // because this handler remembered to re-apply it afterwards.
262
- const fallbackScale = Number.isFinite(takes[0].personScale) ? takes[0].personScale : done.personScale;
263
- let personScale = await loadMotion(takes[0].motionUrl ?? done.motionUrl, label, active.rot, null, active.id, null, { personScaleFallback: fallbackScale });
264
- if (!live()) return;
265
- // loadMotion reports the stature it applied — 1 for a take that
266
- // stores none, nothing at all if the load failed. A failed load is
267
- // not a finished extraction: say so with the named reason instead
268
- // of a receipt for a take nobody can play.
269
- if (!Number.isFinite(personScale)) throw new Error("extract-convert-failed");
270
- // Only a take that stores NO stature gets the fallback for an npz
271
- // that predates person_scale (or an older bridge): the response
272
- // still carries the estimate, clamped the same way, because a bad
273
- // leg estimate must never produce a giant or a gnome.
274
- const declared = Number.isFinite(takes[0].personScale) ? takes[0].personScale : done.personScale;
275
- if (personScale === 1 && Number.isFinite(declared)) {
276
- personScale = characterScaleFor(null, declared);
277
- }
278
- // The clip itself is session-only; this reference is what a save
279
- // keeps and restoreMotionRefs re-fetches on the next session.
280
- const leadRef = {
281
- url: takes[0].motionUrl ?? done.motionUrl,
282
- prompt: label,
283
- rotationDeg: active.rot,
284
- anchorX: active.x,
285
- anchorZ: active.z,
286
- };
287
- if (!appContext.storeDomain('motion')) appContext.shared.publishStudioCharacters((list) => list.map((entry) => entry.id === active.id
288
- ? { ...entry, scale: personScale, motionRef: leadRef }
289
- : entry));
290
- const placed = await deliverExtraTakes(takes.slice(1), active, label);
291
- if (!live()) return;
292
- const persons = 1 + placed;
293
- setMultiModelTake({ frames: done.frames, fps: done.fps, gpu: true, personScale, persons, trajectory: done.performance?.trajectory, segmentation: done.segmentation ?? done.performance?.segmentation ?? takes[0]?.segmentation ?? null, quality: done.quality ?? takes[0]?.quality ?? null });
294
- setMultiModelExtract("done");
295
- appContext.notify(isKo
296
- ? `GPU 모션 추출됨 — ${done.frames}프레임 @ ${done.fps} fps${persons > 1 ? ` · ${persons}명` : ""} · 인물 스케일 ×${personScale.toFixed(2)}`
297
- : `GPU motion extracted — ${done.frames} frames @ ${done.fps} fps${persons > 1 ? ` · ${persons} performers` : ""} · person scale ×${personScale.toFixed(2)}`);
298
- } catch (error) {
299
- if (!live()) return;
300
- const code = error?.message ?? String(error);
301
- setMultiModelExtract("error");
302
- setMultiModelExtractError(MULTIMODEL_REASONS[code]?.[isKo ? 1 : 0] ?? code);
303
- }
304
- }
305
-
306
- async function deliverExtraTakes(extras, active, label) {
307
- const decoded = await Promise.all(extras.map(async (take, index) => {
308
- if (typeof take?.motionUrl !== "string" || !take.motionUrl) return null;
309
- try {
310
- // Inbound boundary, exactly like every other clip: decode, then
311
- // retime onto the production clock before anything counts frames.
312
- const anchor = takeAnchor(active, take.offsetX, take.offsetZ);
313
- const clip = retimeMotion(await loadMotionFromUrl(take.motionUrl), TIMELINE_FPS);
314
- const scale = characterScaleFor(clip, take.personScale);
315
- const raised = applyAuthoredSupportRise(clip, { ...anchor, y: active.y ?? 0 }, active.rot, scale);
316
- const staging = autoRoofDrop(
317
- raised,
318
- { x: anchor.x, z: anchor.z, y: active.y ?? 0, rotationDeg: active.rot },
319
- authoredSupportDescriptors(),
320
- { worldScale: scale },
321
- );
322
- const stagedClip = staging ? applyAutoFall(raised, staging, { worldScale: scale }) : raised;
323
- return {
324
- url: take.motionUrl,
325
- prompt: `${label} · ${index + 2}`,
326
- rotationDeg: active.rot,
327
- anchor,
328
- // A second performer is a DIFFERENT body: their take carries
329
- // their own stature, and the response estimate is only the
330
- // fallback for an npz that stores none.
331
- scale: characterScaleFor(clip, take.personScale),
332
- clip: stagedClip,
333
- };
334
- } catch {
335
- return null; // one unreadable take never voids the others
336
- }
337
- }));
338
- const usable = decoded.filter(Boolean);
339
- if (!usable.length) return 0;
340
- const owned = appContext.storeDomain('motion');
341
- // Plan against the cast as it stands, so ids are decided once and the
342
- // full-take map can be seeded with them.
343
- const list = appContext.live.characters;
344
- const taken = new Set([active.id]);
345
- let idPool = list;
346
- const assignments = usable.map((take) => {
347
- // Reuse a visible cast member with no clip of its own before adding
348
- // another body to the set.
349
- const reuse = list.find((entry) => !entry.hidden && !isProxyFigure(entry) && !taken.has(entry.id) && !entry.sessionMotion && !entry.motionRef);
350
- const id = reuse ? reuse.id : nextCharacterId(idPool);
351
- if (!reuse) idPool = [...idPool, { id }];
352
- taken.add(id);
353
- return {
354
- id,
355
- spawn: !reuse,
356
- patch: {
357
- hidden: false,
358
- x: take.anchor.x,
359
- z: take.anchor.z,
360
- rot: take.rotationDeg,
361
- scale: take.scale,
362
- motionRef: {
363
- url: take.url,
364
- prompt: take.prompt,
365
- rotationDeg: take.rotationDeg,
366
- anchorX: take.anchor.x,
367
- anchorZ: take.anchor.z,
368
- },
369
- sessionMotion: {
370
- ...take.clip,
371
- url: take.url,
372
- prompt: take.prompt,
373
- anchorX: take.anchor.x,
374
- anchorZ: take.anchor.z,
375
- anchorFrame: 0,
376
- rotationDeg: take.rotationDeg,
377
- editSegments: createMotionEdit(take.clip.frames),
378
- },
379
- },
380
- };
381
- });
382
- const place = current => {
383
- let next = current;
384
- for (const { id, spawn, patch } of assignments) {
385
- next = spawn && !next.some((entry) => entry.id === id)
386
- ? [...next, { ...createCharacterEntry({ id, model: active.model, pose: DEFAULT_POSE, subject: "a person" }, next.length), ...patch }]
387
- : next.map((entry) => (entry.id === id ? { ...entry, ...patch } : entry));
388
- }
389
- return next;
390
- };
391
- if (owned) owned.runPrepared(active.id, () => {
392
- owned.castWrite(place);
393
- for (const { id, patch } of assignments) { owned.replace(id, patch.sessionMotion); owned.persistTake(patch.sessionMotion, patch.motionRef); }
394
- return { affectedIds: assignments.map(row => row.id) };
395
- });
396
- else {
397
- appContext.shared.publishStudioCharacters(place);
398
- // Their takes are trimmable the moment they become the active layer.
399
- for (const { id, patch } of assignments) appContext.shared.motionFullRef.current.set(id, patch.sessionMotion);
400
- }
401
- return assignments.length;
402
- }
403
-
404
- function captureI2vStill() {
405
- // H3 480P renders 832x480. The still is captured at exactly that canvas
406
- // (x2) regardless of the Studio's shot ratio; markI2vPose also switches
407
- // the viewport to the matching ratio so what the user framed is what
408
- // gets sent.
409
- const captured = appContext.shared.captureLiveFraming({ output: I2V_MOTION_STILL_OUTPUT });
410
- if (!captured?.dataUrl?.startsWith("data:image/")) throw new Error(ko("The shot renderer is not ready.", "렌더러가 준비되지 않았어요."));
411
- if (captured.width !== I2V_MOTION_STILL_OUTPUT.width || captured.height !== I2V_MOTION_STILL_OUTPUT.height) {
412
- throw new Error(ko(`The H3 480P reference must be captured at ${I2V_MOTION_STILL_OUTPUT.width}×${I2V_MOTION_STILL_OUTPUT.height}.`, `H3 480P 참조 캡처는 ${I2V_MOTION_STILL_OUTPUT.width}×${I2V_MOTION_STILL_OUTPUT.height}이어야 해요.`));
413
- }
414
- // H3 must see the same complete subject in both endpoints. A clipped
415
- // foot or head makes the model invent the missing geometry during the
416
- // transition, which is exactly the bad motion this flow is meant to avoid.
417
- const rig = appContext.live.state.rigs?.[appContext.shared.activeChar.id];
418
- const cam = appContext.shared.shotCamRef.current;
419
- if (!rig || !cam) throw new Error(ko("The full-body frame is not ready yet. Wait a moment and try the reference capture again.", "전신 프레임을 확인할 수 없어 참조를 캡처할 수 없어요. 잠시 후 다시 시도하세요."));
420
- rig.updateWorldMatrix(true, true);
421
- cam.updateMatrixWorld(true);
422
- const bounds = new THREE.Box3().setFromObject(rig);
423
- const corners = [
424
- new THREE.Vector3(bounds.min.x, bounds.min.y, bounds.min.z),
425
- new THREE.Vector3(bounds.min.x, bounds.min.y, bounds.max.z),
426
- new THREE.Vector3(bounds.min.x, bounds.max.y, bounds.min.z),
427
- new THREE.Vector3(bounds.min.x, bounds.max.y, bounds.max.z),
428
- new THREE.Vector3(bounds.max.x, bounds.min.y, bounds.min.z),
429
- new THREE.Vector3(bounds.max.x, bounds.min.y, bounds.max.z),
430
- new THREE.Vector3(bounds.max.x, bounds.max.y, bounds.min.z),
431
- new THREE.Vector3(bounds.max.x, bounds.max.y, bounds.max.z),
432
- ].map((corner) => corner.project(cam));
433
- const margin = 0.94;
434
- const clipped = corners.some((corner) =>
435
- corner.z < -1 || corner.z > 1 || Math.abs(corner.x) > margin || Math.abs(corner.y) > margin
436
- );
437
- if (clipped) {
438
- throw new Error(ko(
439
- "The full character is not inside the A/B reference. In the shot view, pull the camera back until the head and both feet are visible, then capture again.",
440
- "A/B 참조에 캐릭터 전신이 다 안 들어왔어요. 샷 시점에서 머리와 양발이 화면 안에 들어오도록 카메라를 뒤로 빼고 다시 캡처하세요."
441
- ));
442
- }
443
- if (!appContext.shared.i2vMotionSegmentationReady || !Array.isArray(captured.partColours) || captured.partColours.length === 0) {
444
- throw new Error(ko("Enable View → Body part colours → Shaded before capturing an A/B reference.", "A/B 참조는 View에서 부위 색상 → 음영을 켜야 캡처할 수 있어요."));
445
- }
446
- return { ...captured, framing: appContext.shared.captureCurrentFraming() };
447
- }
448
- /** Put the viewport on the Fal canvas and hand the fly controls to the
449
- * shot camera, so the user composes the A/B reference on exactly the
450
- * 832x480 frame the clip will have. Idempotent; capture does not need it. */
451
- function enterI2vFraming() {
452
- appContext.shared.runStudioAction("stage.setFilmback", { shotAspect: I2V_MOTION_SHOT_ASPECT });
453
- if (!appContext.shared.lookThroughShot) appContext.shared.enterShotLook();
454
- }
455
- function markI2vPose(slot) {
456
- try {
457
- if (slot === "b" && i2vMotion.a && framingDistance(i2vMotion.a.framing, appContext.shared.captureCurrentFraming()) > 0.001) {
458
- throw new Error(ko("The camera moved between A and B. Capture both refs with the same camera framing.", "A와 B 사이에서 카메라가 이동했어요. 같은 카메라 프레이밍으로 다시 캡처하세요."));
459
- }
460
- const still = captureI2vStill();
461
- // The capture is already on the Fal canvas; make the viewport agree so
462
- // the user sees the frame that was just sent.
463
- appContext.shared.runStudioAction("stage.setFilmback", { shotAspect: I2V_MOTION_SHOT_ASPECT });
464
- setI2vMotion((current) => ({ ...current, [slot]: still, status: "idle", error: "" }));
465
- if (slot === "a") appContext.shared.setI2vMotionCameraUnlocked(false);
466
- appContext.notify(isKo ? `포즈 ${slot.toUpperCase()} 캡처됨 · ${still.width}×${still.height}` : `Pose ${slot.toUpperCase()} captured · ${still.width}×${still.height}`);
467
- } catch (error) {
468
- setI2vMotion((current) => ({ ...current, error: error.message, status: "error" }));
469
- }
470
- }
471
- function clearI2vPose(slot) {
472
- setI2vMotion((current) => ({ ...current, [slot]: null, status: "idle", error: "", job: null }));
473
- if (slot === "a") appContext.shared.setI2vMotionCameraUnlocked(false);
474
- }
475
- function clearI2vMotion() {
476
- setI2vMotion({ a: null, b: null, job: null, status: "idle", error: "", instruction: "", promptOverride: "", duration: I2V_MOTION_MIN_DURATION, dailyRemaining: null });
477
- appContext.shared.setI2vMotionCameraUnlocked(false);
478
- }
479
- function restoreI2vCamera() {
480
- const framing = i2vMotion.a?.framing;
481
- const camera = appContext.shared.shotCamRef.current;
482
- if (!framing || !camera) return;
483
- camera.position.set(framing.pos.x, framing.pos.y, framing.pos.z);
484
- camera.rotation.order = "YXZ";
485
- camera.rotation.set(framing.pitch, framing.yaw, 0);
486
- camera.fov = framing.fovDeg;
487
- camera.updateProjectionMatrix();
488
- appContext.shared.look.current.yaw = framing.yaw;
489
- appContext.shared.look.current.pitch = framing.pitch;
490
- appContext.shared.shotCameraPosRef.current = { ...framing.pos };
491
- appContext.shared.setCameraPos({ ...framing.pos });
492
- appContext.shared.setFovDeg(framing.fovDeg);
493
- appContext.shared.setI2vMotionCameraUnlocked(false);
494
- setI2vMotion((current) => ({ ...current, error: "", status: "idle" }));
495
- appContext.notify(isKo ? "A 캡처 카메라로 복원했어요." : "Restored the camera used for A.");
496
- }
497
- function framingDistance(a, b) {
498
- if (!a || !b) return Infinity;
499
- return Math.max(
500
- Math.abs(a.pos.x - b.pos.x), Math.abs(a.pos.y - b.pos.y), Math.abs(a.pos.z - b.pos.z),
501
- Math.abs(a.yaw - b.yaw), Math.abs(a.pitch - b.pitch), Math.abs(a.fovDeg - b.fovDeg),
502
- );
503
- }
504
- /** The AI motion card's lock line: AI video motion is not enabled for this account. */
505
- function showI2vMotionLock() {
506
- setI2vMotion((current) => ({ ...current, error: ko("AI motion generation is locked during QA.", "AI 모션 생성은 QA 중 잠겨 있어요."), status: "error" }));
507
- }
508
- /** The AI motion card shows every failure itself. For motion.generateFromVideo the
509
- * answer says what happened: `{ failed }` with the reason in English, or the
510
- * finished job, the footage it was ingested as (null when ingest failed) and
511
- * the account's daily generations left. */
512
- async function generateI2vMotion(kind = "interpolate", instructionOverride = null, commandContext = null) {
513
- // One explicit request on the motion:* contract (#466). While the route is
514
- // gated, the lock itself is the demand signal. Telemetry never decides.
515
- const motionRequest = startMotionRequest({
516
- surface: !commandContext ? "fal_card" : commandContext.origin === "mcp" || commandContext.origin === "live" ? "mcp" : "agent",
517
- input_mode: kind === "interpolate" ? "a_to_b" : "still",
518
- });
519
- if (!appContext.shared.i2vMotionEnabled) {
520
- motionRequest.block("locked");
521
- showI2vMotionLock();
522
- return { failed: "AI video motion (Fal) is not enabled for this account." };
523
- }
524
- let source = i2vMotion;
525
- if (kind === "act" && !source.a) {
526
- try { source = { ...source, a: captureI2vStill() }; setI2vMotion((current) => ({ ...current, a: source.a })); }
527
- catch (error) {
528
- motionRequest.block("missing_input");
529
- setI2vMotion((current) => ({ ...current, error: error.message, status: "error" }));
530
- return { failed: "Could not capture the character's pose frame: the full body must be inside the shot frame, shaded part colours must be on (view.setPartColours { mode: \"shaded\" }), and the renderer and rig must be ready." };
531
- }
532
- }
533
- if (kind === "interpolate" && (!source.a || !source.b)) {
534
- motionRequest.block("missing_input");
535
- setI2vMotion((current) => ({ ...current, error: ko("Capture both A and B poses first.", "A와 B 포즈를 먼저 캡처하세요."), status: "error" }));
536
- return { failed: "Capture both A and B poses first." };
537
- }
538
- if (!source.a?.partColours || (kind === "interpolate" && !source.b?.partColours)) {
539
- motionRequest.block("missing_input");
540
- setI2vMotion((current) => ({ ...current, error: ko("Recapture A/B refs with shaded body-part segmentation enabled.", "색 세그멘테이션이 포함된 음영 A/B 참조를 다시 캡처하세요."), status: "error" }));
541
- return { failed: "The captured pose frame has no shaded body-part segmentation; the user must recapture it in the Fal card with shaded part colours on." };
542
- }
543
- if (kind === "interpolate" && framingDistance(source.a.framing, source.b.framing) > 0.001) {
544
- motionRequest.block("missing_input");
545
- setI2vMotion((current) => ({ ...current, error: ko("The camera changed between A and B. Capture both poses with the same camera.", "A와 B 사이에서 카메라가 바뀌었어요. 같은 카메라로 다시 캡처하세요."), status: "error" }));
546
- return { failed: "The camera changed between poses A and B; capture both with the same camera." };
547
- }
548
- // A hand-edited prompt wins verbatim; otherwise build from the description.
549
- // Interpolate now honours the description too (#380): the bare pose
550
- // difference lets the model invent the transition and produces junk.
551
- const description = instructionOverride || source.instruction || "";
552
- const prompt = source.promptOverride?.trim()
553
- ? source.promptOverride.trim()
554
- : buildH3MotionPrompt(description || (kind === "interpolate" ? "" : "Make the character perform the requested action."), { interpolate: kind === "interpolate" });
555
- setI2vMotion((current) => ({ ...current, status: "submitting", error: "", job: null }));
556
- motionRequest.pass("fal");
557
- motionRequest.start();
558
- try {
559
- const fetchImpl = commandContext ? (url, options) => fetch(url, { ...options, signal: commandContext.signal }) : undefined;
560
- const submitted = await submitMotionJob({
561
- kind,
562
- stillA: source.a?.dataUrl,
563
- stillB: source.b?.dataUrl,
564
- still: source.a?.dataUrl,
565
- prompt,
566
- duration: source.duration ?? I2V_MOTION_MIN_DURATION,
567
- }, fetchImpl);
568
- const id = submitted?.job?.id;
569
- if (!id) throw Object.assign(new Error(ko("The server did not return a motion job ID.", "생성 작업 ID를 받지 못했어요.")), { reason: "The motion server did not return a job id." });
570
- setI2vMotion((current) => ({ ...current, status: "queued", job: submitted.job, dailyRemaining: submitted.dailyRemaining }));
571
- const finished = await waitForMotionJob(id, {
572
- fetchImpl,
573
- onUpdate: (job) => setI2vMotion((current) => ({ ...current, job, status: job?.status ?? current.status })),
574
- });
575
- commandContext?.check();
576
- const job = finished?.job;
577
- if (job?.status !== "done") throw Object.assign(new Error(job?.error || ko("AI motion generation failed.", "AI 모션 생성에 실패했어요.")), job?.error ? {} : { reason: "The AI video generation failed." });
578
- motionRequest.succeed();
579
- setI2vMotion((current) => ({ ...current, job, status: "done", dailyRemaining: finished.dailyRemaining }));
580
- let footage = null;
581
- if (job.video?.url) {
582
- const motionSource = { kind: "url", url: job.video.url, name: `Fal H3 Max Turbo · ${job.resolution}` };
583
- setMultiModelSource(motionSource);
584
- // Put the completed clip through the same probe/ingest path as a
585
- // manually supplied URL so GVHMR sees measured fps, duration and
586
- // a ready extraction card without another generation request.
587
- footage = (await ingestFootage(motionSource, commandContext)) ?? null;
588
- appContext.shared.setResult({
589
- mode: "video",
590
- modelLabel: "Fal H3 Max Turbo",
591
- prompt,
592
- frame: source.a?.dataUrl ?? null,
593
- videoUrl: job.video.url,
594
- motion: {
595
- videoUrl: job.video.url,
596
- resolution: job.resolution,
597
- width: job.width,
598
- height: job.height,
599
- fps: job.fps,
600
- duration: job.resultDuration ?? job.duration,
601
- cost: job.cost,
602
- },
603
- });
604
- appContext.shared.setResultOpen(true);
605
- // Applied = the clip is in the Studio, ready for GVHMR extraction.
606
- motionRequest.apply();
607
- // The result modal and the studio modal are both z-30; never stack them.
608
- appContext.shared.setI2vMotionStudioOpen(false);
609
- appContext.notify((isKo, ko) => isKo ? "AI 영상이 준비됐어요 · 추출 패널에서 GVHMR을 실행하세요" : "AI video is ready · run GVHMR from the extraction panel");
610
- }
611
- return { job, footage, dailyRemaining: finished.dailyRemaining ?? null };
612
- } catch (error) {
613
- const cancelled = Boolean(commandContext && (commandContext.signal.aborted || error?.code === "STALE_TARGET"));
614
- // 429 is the account's daily cap (workers/api daily_cap).
615
- motionRequest.fail(error, cancelled ? "aborted" : error?.status === 429 ? "quota" : "generation_failed");
616
- if (cancelled) throw error;
617
- setI2vMotion((current) => ({ ...current, status: "error", error: error.message || String(error) }));
618
- return { failed: error.reason ?? `The AI video generation failed: ${error.message || error}` };
619
- }
620
- }
621
- /** Why the chip cannot start an AI video motion now, as the user reads it:
622
- * one already running, or none left today. The lock has its own AI motion card line. */
623
- function i2vMotionUnavailable() {
624
- if (!["idle", "done", "error", "failed"].includes(i2vMotion.status)) return ko("A generation is already running", "이미 생성이 돌고 있어요");
625
- if (i2vMotion.dailyRemaining === 0) return ko("No AI video motion generations left today", "오늘 남은 AI 영상 모션 생성이 없어요");
626
- return null;
627
- }
628
- function generateI2vMotionFromUi(instruction) {
629
- if (!appContext.shared.i2vMotionEnabled) {
630
- // Locked, the action's availability check refuses before
631
- // generateI2vMotion runs, so the chip records its own lock (#466).
632
- startMotionRequest({ surface: "agent", input_mode: "still" }).block("locked");
633
- showI2vMotionLock();
634
- } else {
635
- // The chip clears the typed instruction when clicked, so a refusal says why.
636
- const reason = i2vMotionUnavailable();
637
- if (reason) { appContext.notify(reason); return null; }
638
- }
639
- return appContext.shared.runStudioAction("motion.generateFromVideo", { instruction });
640
- }
641
- function updateI2vMotionQuota(dailyRemaining) { setI2vMotion((current) => ({ ...current, dailyRemaining })); }
642
- return {
643
- chooseMultiModelFile,
644
- pasteMultiModelUrl,
645
- useMultiModelUrl,
646
- ingestPlatformFootage,
647
- ingestFootage,
648
- extractMultiModelMotion,
649
- extractMultiModelMotionGpu,
650
- deliverExtraTakes,
651
- captureI2vStill,
652
- enterI2vFraming,
653
- markI2vPose,
654
- clearI2vPose,
655
- clearI2vMotion,
656
- restoreI2vCamera,
657
- framingDistance,
658
- showI2vMotionLock,
659
- generateI2vMotion,
660
- i2vMotionUnavailable,
661
- generateI2vMotionFromUi,
662
- updateI2vMotionQuota,
663
- };
664
- }