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,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
- }