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
@@ -0,0 +1,80 @@
1
+ // What the agent's motion work looks like while it runs, for the user's eyes. Every motion.compile publishes its
2
+ // draft here, installed or not: the take it baked, its QA (total, groups, goals) and its beats (timeline). The
3
+ // images the agent asks to see (verify_result) are published here too. The viewport plays the latest draft as a
4
+ // ghost, the timeline lays out its beats, and the agent panel draws the QA trend, the goals and the images.
5
+ // In-page only: nothing here is saved with the project, undone, or sent to the agent. Each character keeps its
6
+ // last KEEP drafts; the images keep the last KEEP. A new agent turn starts clean (clearProgress).
7
+
8
+ import { qaProblems } from "./qa-label.js";
9
+ const KEEP = 12;
10
+ const drafts = new Map(), sheets = [], listeners = new Set();
11
+ let version = 0, counter = 0;
12
+
13
+ function emit() {
14
+ version++;
15
+ for (const listener of [...listeners]) {
16
+ try { listener(); } catch { /* a broken view never stops the agent */ }
17
+ }
18
+ }
19
+
20
+ const pick = (row, keys) => Object.fromEntries(keys.filter(k => row?.[k] !== undefined).map(k => [k, row[k]]));
21
+
22
+ /**
23
+ * publishDraft(characterId, { take, installed, specId, label, qa, timeline }) -> the stored draft:
24
+ * { n (1, 2, ... per character), at (ms), characterId, take, frames, installed, specId, label,
25
+ * qa: { ok, total, problems, groups: [{ kind, subject, count, first, last, worst }], goals: [{ name, kind, ok, value, need, at }] },
26
+ * timeline: the compile's beats (pipeline.js timelineOf) }.
27
+ */
28
+ export function publishDraft(characterId, { take, installed = false, specId = null, label = null, qa = null, timeline = [] }) {
29
+ const list = drafts.get(characterId) ?? [];
30
+ const draft = Object.freeze({
31
+ n: (list.at(-1)?.n ?? 0) + 1, at: Date.now(), seq: ++counter, characterId, take, frames: take?.frameCount ?? 0,
32
+ installed: !!installed, specId, label,
33
+ qa: { ok: !!qa?.ok, total: qa?.total ?? 0, problems: qaProblems(qa),
34
+ groups: (qa?.groups ?? []).map(g => pick(g, ["kind", "subject", "count", "first", "last", "worst"])),
35
+ goals: (qa?.goals ?? []).map(g => pick(g, ["name", "kind", "ok", "value", "need", "at"])) },
36
+ timeline: timeline ?? [],
37
+ });
38
+ drafts.set(characterId, [...list, draft].slice(-KEEP));
39
+ emit();
40
+ return draft;
41
+ }
42
+
43
+ /** The drafts of a character, oldest first (at most KEEP). */
44
+ export const draftsOf = characterId => drafts.get(characterId) ?? [];
45
+
46
+ /** The newest draft of a character, or of any character when none is named; null when there is none. */
47
+ export function latestDraft(characterId) {
48
+ if (characterId !== undefined) return draftsOf(characterId).at(-1) ?? null;
49
+ let best = null;
50
+ for (const list of drafts.values()) if (list.length && (!best || list.at(-1).seq > best.seq)) best = list.at(-1);
51
+ return best;
52
+ }
53
+
54
+ /** publishSheet({ characterId, dataUrl, width, height, caption, frames }) -> the stored image record. */
55
+ export function publishSheet({ characterId = null, dataUrl, width = 0, height = 0, caption = "", frames = [] }) {
56
+ const sheet = Object.freeze({ at: Date.now(), seq: ++counter, characterId, dataUrl, width, height, caption, frames });
57
+ sheets.push(sheet);
58
+ sheets.splice(0, Math.max(0, sheets.length - KEEP));
59
+ emit();
60
+ return sheet;
61
+ }
62
+
63
+ /** The published images, oldest first (at most KEEP). */
64
+ export const progressSheets = () => [...sheets];
65
+
66
+ /** Forget every draft and image (a new agent turn). */
67
+ export function clearProgress() {
68
+ drafts.clear();
69
+ sheets.length = 0;
70
+ emit();
71
+ }
72
+
73
+ /** subscribeProgress(listener) -> unsubscribe; with progressVersion it fits React's useSyncExternalStore. */
74
+ export function subscribeProgress(listener) {
75
+ listeners.add(listener);
76
+ return () => { listeners.delete(listener); };
77
+ }
78
+
79
+ /** A number that changes on every publish or clear. */
80
+ export const progressVersion = () => version;
@@ -0,0 +1,30 @@
1
+ // The agent turn's controls, and its mark. One user message is one editor transaction
2
+ // (#702, #715): every edit of the turn lands in one composed session, so the message is one
3
+ // undo step. A script runs inside that turn, after edits the same message already made (an
4
+ // installed take, a placed set). When the script fails, only its own edits roll back:
5
+ // agent.turn.mark records where it starts, and agent.turn.rollback restores exactly what came
6
+ // after the mark and leaves the turn open, so the earlier edits still land with the turn.
7
+ import { StudioProtocolError } from './studio-agent-protocol.js';
8
+
9
+ const TURN_CONTROLS = ['begin', 'mark', 'rollback', 'finish', 'cancel'];
10
+ /** The wire schema of each agent.turn.* control: the owning turn's id, nothing else. */
11
+ export const agentTurnControls = (object, identifier) => Object.fromEntries(TURN_CONTROLS.map(verb => [`agent.turn.${verb}`, object({ turnId: identifier })]));
12
+
13
+ /**
14
+ * agent.turn.mark or agent.turn.rollback on the open turn. Answers the transaction its receipt
15
+ * describes: a mark affects nothing, a rollback names the ids the rolled-back edits touched.
16
+ * The bus adds each later edit's ids to `turn.mark.since`.
17
+ */
18
+ export function markAgentTurn(id, turn) {
19
+ if (id === 'agent.turn.mark') {
20
+ turn.mark = { point: turn.session?.savepoint() ?? null, affectedIds: new Set(turn.affectedIds), since: new Set() };
21
+ return { txId: turn.txId, affectedIds: [], toasts: [] };
22
+ }
23
+ const { mark } = turn;
24
+ if (!mark) throw new StudioProtocolError('STALE_TARGET', 'The agent turn has no mark to roll back to.');
25
+ turn.mark = null;
26
+ // No session at the mark: everything the turn authored came after it.
27
+ turn.session?.rollback(mark.point ?? undefined);
28
+ turn.affectedIds = mark.affectedIds;
29
+ return { txId: turn.txId, affectedIds: [...mark.since], toasts: [] };
30
+ }
package/src/analytics.js CHANGED
@@ -905,7 +905,7 @@ function runtimeConfig() {
905
905
  function isLoopbackOrigin(origin) {
906
906
  try {
907
907
  const parsed = new URL(origin);
908
- return parsed.protocol === "http:" && parsed.hostname === "127.0.0.1";
908
+ return parsed.protocol === "http:" && ["127.0.0.1", "localhost"].includes(parsed.hostname);
909
909
  } catch {
910
910
  return false;
911
911
  }
@@ -1051,17 +1051,10 @@ export async function initAnalytics() {
1051
1051
  void recordDeviceProfile();
1052
1052
  if (resolved.distribution === "npm") {
1053
1053
  track("app:session_started");
1054
- // This follows the session marker so the two events form one
1055
- // capability baseline in funnel queries.
1056
- void recordMotionBackendState();
1057
1054
  if (resolved.firstLaunch) {
1058
1055
  const heardFrom = resolved.firstLaunchHeardFrom;
1059
1056
  track("install:first_launch", heardFrom ? { heard_from: heardFrom } : {});
1060
1057
  }
1061
- } else {
1062
- // Hosted sessions have PostHog's native session marker rather than
1063
- // the npm-only custom event above.
1064
- void recordMotionBackendState();
1065
1058
  }
1066
1059
  } catch {
1067
1060
  console.info("[analytics] initialization failed");
@@ -1070,17 +1063,6 @@ export async function initAnalytics() {
1070
1063
  await initPromise;
1071
1064
  }
1072
1065
 
1073
- async function recordMotionBackendState() {
1074
- let health = null;
1075
- try {
1076
- const response = await fetch("/ardy/health", { signal: AbortSignal.timeout(5000) });
1077
- if (response.ok) health = await response.json();
1078
- } catch {
1079
- // No bridge is a normal hosted/demo state.
1080
- }
1081
- track("motion:backend_state", motionBackendState(health));
1082
- }
1083
-
1084
1066
  export function track(event, props = {}) {
1085
1067
  if (!initialized || !enabled || !posthog) return;
1086
1068
  try {
@@ -1,8 +1,13 @@
1
- import { createContext, useContext } from 'react';
1
+ import { createContext, useContext, useMemo } from 'react';
2
2
  import { StudioProtocolError } from './studio-agent-protocol.js';
3
3
 
4
4
  export const AppContext = createContext(null);
5
- export function useBus() { return useContext(AppContext).bus; }
5
+ // Reading `bus` refreshes the studio binding (it serializes the whole scene), so a panel must not read it while it
6
+ // renders: playback re-renders panels every frame. The hook hands back a stable handle that opens the bus per call.
7
+ export function useBus() {
8
+ const app = useContext(AppContext);
9
+ return useMemo(() => ({ run: (...args) => app.bus.run(...args), subscribe: (...args) => app.bus.subscribe(...args) }), [app]);
10
+ }
6
11
 
7
12
  const ref = current => ({ current });
8
13
 
@@ -98,6 +103,11 @@ export function createAppContext({
98
103
  closed = true; activeComposition = null;
99
104
  return remember(changed);
100
105
  },
106
+ // A savepoint inside the open composition (a script inside an agent turn): rollback(point)
107
+ // restores each owner to it, and an owner first touched after it to where it began. The
108
+ // composition stays open, so edits made before the savepoint still land with it.
109
+ savepoint() { check(); return new Map([...members].map(([handle, row]) => [handle, row.session.mark()])); },
110
+ rollback(point = new Map()) { check(); for (const [handle, row] of [...members].reverse()) row.session.rollback(point.get(handle)); },
101
111
  cancel(options) {
102
112
  if (closed) return false;
103
113
  closed = true; activeComposition = null;
package/src/app-stage.jsx CHANGED
@@ -22,8 +22,6 @@ export {
22
22
  characterModelUrl,
23
23
  CHARACTER_MODEL_LABELS,
24
24
  nextCharacterId,
25
- DEMO_MOTION_URL,
26
- DEMO_MOTION_PROMPT,
27
25
  OBJECT_DELETE_UNDO_MS,
28
26
  ASSET_DELETE_UNDO_MS,
29
27
  CLAY,
@@ -64,7 +62,7 @@ export {
64
62
 
65
63
  export * from "./stage-attach-frames.js";
66
64
 
67
- export { Character, MotionTrails } from "./stage-characters.jsx";
65
+ export { Character } from "./stage-characters.jsx";
68
66
 
69
67
  export {
70
68
  ShotRig,
@@ -105,40 +103,10 @@ export {
105
103
  } from "./stage-capture.jsx";
106
104
  export {
107
105
  TIMELINE_FPS,
108
- ARDY_FPS,
109
- toArdyFrame,
110
- posePlacementFrame,
111
- toArdyFrameEntries,
112
- toArdySegments,
113
106
  DEFAULT_DURATION_S,
114
107
  DEFAULT_PLAYBACK_SPEED,
115
- BRIDGE_RECHECK_MS,
116
- ARDY_PROMPT_HORIZON_FRAMES,
117
108
  MAX_WAYPOINTS,
118
- ARDY_PROMPT_MAX,
119
- ARDY_DURATION_MIN,
120
- ARDY_DURATION_MAX,
121
- ARDY_SEED_MAX,
122
- ARDY_PRESERVE_DEFAULT,
123
- DEFAULT_PROMPT_CLIPS,
124
- buildPromptSchedule,
125
- ikTracksInRange,
126
- PRESERVE_TRACK_LIMBS,
127
- PRESERVE_LIMB_ORDER,
128
- preserveLimbLabel,
129
- preserveTracksSummary,
130
- LINE_EDIT_TRACK_OPTIONS,
131
- lineTrackLabel,
132
- LINE_EDIT_DEFAULT_TRACK,
133
- LINE_CURVE_MARKER_STRIDE,
134
- MIN_CURVE_POINTS,
135
- LINE_PREVIEW_DEBOUNCE_MS,
136
- LINE_CAPABILITY_RETRY_MS,
137
- LINE_EDIT_REFUSALS,
138
- LINE_CURVE_REFUSALS,
139
- MULTIMODEL_REASONS,
140
- MULTIMODEL_SAMPLE_FPS,
141
- } from "./ardy-bridge-contract.js";
109
+ } from "./timeline-defaults.js";
142
110
 
143
111
  /* ------------------------------------------------------------------ 3D --- */
144
112
 
@@ -3,7 +3,7 @@ import { ko } from "./locale.js";
3
3
  import { CHARACTER_MODEL_IDS, PROXY_FIGURE_MODEL } from "./scenes.js";
4
4
  import { OBJECT_LIBRARY } from "./scene-objects.js";
5
5
  import { displayObjectLabel } from "./object-catalog.jsx";
6
- import { assetAspect, isMeshAssetId, isSupportedMeshType } from "./scene-assets.js";
6
+ import { assetAspect, isImageAssetId, isMeshAssetId, isSupportedMeshType } from "./scene-assets.js";
7
7
  import { assetKind, formatAssetBytes } from "./asset-shelf.js";
8
8
  import { assetRecord } from "./scene-asset-cache.js";
9
9
  import ResourceStatus from "./resource-status.jsx";
@@ -99,14 +99,15 @@ function loadThumb(id) {
99
99
  // card hides. A present record whose bytes fail below resolves null
100
100
  // instead, and the card stays visible so it can be deleted.
101
101
  if (!record) return undefined;
102
- if (isMeshAssetId(record.id) || isSupportedMeshType(record.type)) {
102
+ // An HDR environment file (hdr- id) is no picture either: it gets the same generic card.
103
+ if (!isImageAssetId(record.id) || isSupportedMeshType(record.type)) {
103
104
  // A GLB is not a picture. Decoding it as one would mark every
104
105
  // model "unreadable" in Manage storage — show a generic cube
105
106
  // instead, using the same card preview the catalogue already has.
106
107
  return {
107
108
  name: record.name,
108
109
  bytesLabel: formatAssetBytes(record.bytes.byteLength),
109
- kind: "mesh",
110
+ kind: isMeshAssetId(record.id) || isSupportedMeshType(record.type) ? "mesh" : "environment",
110
111
  mesh: true,
111
112
  };
112
113
  }
@@ -167,7 +168,7 @@ function StorageAssetRow({ id, onDelete, deleting, usageCount = 0, graphSignatur
167
168
  const usageLabel = ko(`Used by ${usageCount} scene object${usageCount === 1 ? "" : "s"}`, `${usageCount}개 씬 오브젝트에서 사용 중`);
168
169
  const deleteLabel = ko(`Delete ${name} from storage`, `${name}을(를) 저장소에서 삭제`);
169
170
  const confirmationId = `asset-storage-warning-${id}`;
170
- const kindLabel = thumb?.kind === "mesh" ? ko("Model", "모델") : thumb?.kind === "matte" ? ko("Matte", "매트") : ko("Image", "이미지");
171
+ const kindLabel = thumb?.kind === "mesh" ? ko("Model", "모델") : thumb?.kind === "environment" ? ko("HDR environment", "HDR 환경") : thumb?.kind === "matte" ? ko("Matte", "매트") : ko("Image", "이미지");
171
172
  return (
172
173
  <li className="asset-storage-row">
173
174
  {mesh ? (
@@ -486,7 +487,7 @@ export function FolderGrid({ folder, query, ...props }) {
486
487
  />
487
488
  );
488
489
  });
489
- if (!takeVersions.length) empty = ko("No takes for this character yet. Generate Motion creates one.", "이 인물의 테이크가 아직 없어요. Generate Motion으로 만들 수 있어요.");
490
+ if (!takeVersions.length) empty = ko("No takes for this character yet. Ask the agent to compile one.", "이 인물의 테이크가 아직 없어요. 에이전트에게 모션 컴파일을 요청하세요.");
490
491
  } else if (folder === "poses") {
491
492
  tiles = poses.map((pose, index) => ({ pose, index, label: pose?.name || pose?.label || ko(`Pose ${index + 1}`, `포즈 ${index + 1}`) }))
492
493
  .filter(({ label }) => matchesQuery(label, query))
@@ -59,6 +59,26 @@ function cloneRailFollow(value) {
59
59
  return null;
60
60
  }
61
61
 
62
+ /**
63
+ * What a rail shot's head holds: up to 4 cast ids (their middle, frame by
64
+ * frame, at their chest or at a world height) or one world point. Null = the follow subject (every older shot).
65
+ * Like the crane, an aim and a lens of its own (focalMm) ride the rail and
66
+ * are left out of the block without one, so untouched shots stay byte-stable.
67
+ */
68
+ function cloneRailAim(value) {
69
+ const ids = Array.isArray(value?.ids) ? value.ids.filter((id) => typeof id === "string" && id).slice(0, 4) : [];
70
+ if (ids.length) return Number.isFinite(value.height) ? { ids, height: value.height } : { ids };
71
+ const point = value?.point;
72
+ return point && [point.x, point.y, point.z].every(Number.isFinite) ? { point: { x: point.x, y: point.y, z: point.z } } : null;
73
+ }
74
+
75
+ /** The aim and lens fields a block with a rail carries (none without one). */
76
+ export function railShotFields(value, cameraRail) {
77
+ const aim = cameraRail ? cloneRailAim(value.aim) : null;
78
+ const focalMm = cameraRail && Number.isFinite(value.focalMm) && value.focalMm > 0 ? value.focalMm : null;
79
+ return { ...(aim ? { aim } : {}), ...(focalMm ? { focalMm } : {}) };
80
+ }
81
+
62
82
  /** Build a complete block from a partial or stored camera value. */
63
83
  export function createCameraBlock(input = {}) {
64
84
  const value = input && typeof input === "object" ? input : {};
@@ -82,6 +102,7 @@ export function createCameraBlock(input = {}) {
82
102
  railFollow: cloneRailFollow(value.railFollow),
83
103
  craneHeight,
84
104
  dollyTiming: timingIsFlat(dollyTiming) ? null : dollyTiming,
105
+ ...railShotFields(value, cameraRail),
85
106
  };
86
107
  }
87
108
 
@@ -0,0 +1,120 @@
1
+ // Pure camera direction helpers shared by playback, export and the agent's
2
+ // receipts: the per-frame follow/rail camera of every shot, where a rail
3
+ // shot's head aims, and the stops a chain of camera keys makes.
4
+
5
+ import { createCameraBlock } from "./camera-block.js";
6
+ import { buildFollowTrack, buildRail, buildRailFollowTrack } from "./camera-follow.js";
7
+ import { shortestArc } from "./camera-move.js";
8
+ import { RAIL_SCHEDULE_LEGACY, RAIL_SCHEDULE_RANGE, resolveRailSchedule } from "./camera-rail-schedule.js";
9
+ import { shotAtFrame } from "./cuts.js";
10
+ import { objectTransformAt } from "./object-path.js";
11
+ import { placementAt } from "./root-path.js";
12
+ import { sampleAt } from "./sample-at.js";
13
+ import { SUBJECT_HEIGHT_M, focalMmToFov } from "./shot.js";
14
+ import { takeHips } from "./motion3/take.js";
15
+
16
+ const DEG = Math.PI / 180;
17
+ /** where a character's aim sits on its body: chest height, as a share of its stature, or above a take's hips */
18
+ const CHEST = 0.75, HIPS_TO_CHEST = 0.35;
19
+
20
+ /**
21
+ * The follow and rail cameras of every shot, one sample per timeline frame
22
+ * (null where a shot keys its camera or its rail is off). A rail shot with
23
+ * an aim holds that aim instead of the follow subject and runs its rail on
24
+ * schedule (no distance pull, no pitch offset), with its own lens when the
25
+ * block names one. aimAt(id, frame) answers one cast member's aim point.
26
+ */
27
+ export function buildShotCameraTracks({ shots, subjectTrack, fps, yawDeg = 0, aimAt = () => null, filmback = {} }) {
28
+ const combined = new Array(subjectTrack.length).fill(null);
29
+ const initialDir = { x: Math.sin(yawDeg * DEG), z: Math.cos(yawDeg * DEG) };
30
+ for (const shot of shots) {
31
+ const camera = createCameraBlock(shot.camera);
32
+ if (camera.mode === "keys") continue;
33
+ const start = shot.startFrame, subjectSlice = subjectTrack.slice(start, Math.min(subjectTrack.length, shot.endFrame + 1));
34
+ const params = { ...camera.followCam, craneHeight: camera.craneHeight, dollyTiming: camera.dollyTiming, initialDir };
35
+ const rail = camera.mode === "rail" ? buildRail(camera.cameraRail) : null;
36
+ if (!rail) {
37
+ buildFollowTrack(subjectSlice, fps, params).forEach((sample, offset) => { combined[start + offset] = sample; });
38
+ continue;
39
+ }
40
+ const schedule = resolveRailSchedule({ railFollow: camera.railFollow, cameraRail: camera.cameraRail, frameCount: subjectSlice.length });
41
+ if (schedule.kind !== RAIL_SCHEDULE_RANGE && schedule.kind !== RAIL_SCHEDULE_LEGACY) continue;
42
+ const from = start + schedule.startFrame, length = schedule.endFrame - schedule.startFrame + 1;
43
+ const aim = camera.aim ? Array.from({ length }, (_, offset) => railAimPoint(camera.aim, from + offset, aimAt)) : null;
44
+ const aimed = aim?.every(Boolean) ? aim : null;
45
+ const track = aimed
46
+ ? buildRailFollowTrack(aimed, fps, rail, { ...params, aimHeight: aimed.reduce((sum, point) => sum + point.y, 0) / length, distanceInfluence: 0, pitchOffsetDeg: 0 })
47
+ : buildRailFollowTrack(subjectSlice.slice(schedule.startFrame, schedule.endFrame + 1), fps, rail, params);
48
+ const fovDeg = camera.focalMm ? focalMmToFov(camera.focalMm, filmback.sensorId, filmback.aspectRatio) / DEG : null;
49
+ track.forEach((sample, offset) => { combined[from + offset] = fovDeg ? { ...sample, fovDeg } : sample; });
50
+ }
51
+ return combined;
52
+ }
53
+
54
+ /** A rail aim at one frame: its world point, or the middle of its cast (null when one is missing). */
55
+ function railAimPoint(aim, frame, aimAt) {
56
+ if (aim.point) return aim.point;
57
+ const points = aim.ids.map((id) => aimAt(id, frame));
58
+ if (points.some((point) => !point)) return null;
59
+ const mean = (axis) => points.reduce((sum, point) => sum + point[axis], 0) / points.length;
60
+ return { x: mean("x"), y: aim.height ?? mean("y"), z: mean("z") };
61
+ }
62
+
63
+ /**
64
+ * One cast member's aim point at a frame: a character's chest where its take
65
+ * (or placement) puts it, an object's middle where its route (or placement)
66
+ * puts it. clipOf(character) is the take the character plays.
67
+ */
68
+ export function castAimPoint(id, frame, { characters = [], objects = [], shots = [], clipOf = () => null, frameCount, fps }) {
69
+ const character = characters.find((entry) => entry.id === id);
70
+ if (character) {
71
+ const clip = clipOf(character), scale = character.scale ?? 1, yaw = (character.rot ?? 0) * DEG;
72
+ // A take v3 moves the hips in the character's placement frame (motion3/take.js), not its root.
73
+ if (clip?.hips) {
74
+ const [x, y, z] = takeHips(clip, frame);
75
+ return { x: character.x + (x * Math.cos(yaw) + z * Math.sin(yaw)) * scale, y: (character.y ?? 0) + (y + HIPS_TO_CHEST) * scale, z: character.z + (-x * Math.sin(yaw) + z * Math.cos(yaw)) * scale };
76
+ }
77
+ const at = placementAt(character, frame, { shotAt: (at) => shotAtFrame(shots, at),
78
+ takeRoot: clip ? sampleAt({ frameCount: clip.frames, motion: clip }, null, frame).subject : null });
79
+ return { x: at.x, y: (character.y ?? 0) + CHEST * SUBJECT_HEIGHT_M * (character.scale ?? 1), z: at.z };
80
+ }
81
+ const object = objects.find((entry) => entry.id === id);
82
+ if (!object) return null;
83
+ const at = objectTransformAt(object, frame, { frameCount, fps }) ?? object;
84
+ return { x: at.x, y: at.y + ((object.height ?? 0) * (object.scaleY ?? 1)) / 2, z: at.z };
85
+ }
86
+
87
+ /** The follow subject's path, where its take v3 moves its body rather than its root (a follow or
88
+ * aimless rail camera keeps up with the walk); any other subject keeps the path it has. */
89
+ export function followSubjectTrack(subjectTrack, subjectId, cast) {
90
+ const character = cast.characters?.find((entry) => entry.id === subjectId);
91
+ if (!subjectTrack || !character || !cast.clipOf(character)?.hips) return subjectTrack;
92
+ return subjectTrack.map((point, frame) => castAimPoint(subjectId, frame, cast) ?? point);
93
+ }
94
+
95
+ /**
96
+ * The frames of the keys a camera halts at for no reason: an interior key the
97
+ * move runs on through in the same direction. Every key eases in and out, so
98
+ * the camera slows to a stop there; a hold, a reversal or a new direction is
99
+ * a stop the shot means and is not listed.
100
+ */
101
+ export function cameraKeyStops(keys = []) {
102
+ const steps = keys.slice(1).map((key, index) => {
103
+ const a = keys[index].framing, b = key.framing;
104
+ return [b.pos.x - a.pos.x, b.pos.y - a.pos.y, b.pos.z - a.pos.z, shortestArc(a.yaw, b.yaw), b.pitch - a.pitch, (b.fovDeg - a.fovDeg) * DEG];
105
+ });
106
+ const length = (step) => Math.hypot(...step);
107
+ return keys.slice(1, -1).filter((_, index) => {
108
+ const u = steps[index], v = steps[index + 1], dot = u.reduce((sum, value, axis) => sum + value * v[axis], 0);
109
+ return length(u) > 0.02 && length(v) > 0.02 && dot > 0.5 * length(u) * length(v);
110
+ }).map((key) => key.frame);
111
+ }
112
+
113
+ /** A receipt warning for a shot whose camera keys halt a move (none when they do not). */
114
+ export function cameraKeyWarnings(shot) {
115
+ const stops = cameraKeyStops(shot.cameraKeys);
116
+ if (!stops.length) return [];
117
+ const listed = `${stops.slice(0, 4).join(", ")}${stops.length > 4 ? ` +${stops.length - 4}` : ""}`;
118
+ return [{ code: "CAMERA_KEY_STOPS", id: shot.id, count: stops.length,
119
+ message: `Camera stops at keys ${listed} (keys ease in/out); use a rail (shot.setCameraRail) or 2 keys.`.slice(0, 120) }];
120
+ }
@@ -6,7 +6,7 @@ import { isProxyFigure } from "./scenes.js";
6
6
  import { isKo } from "./locale.js";
7
7
  import { StudioProtocolError } from "./studio-agent-protocol.js";
8
8
 
9
- export const CHARACTER_CAPABILITIES = Object.freeze(["rig", "ik", "pose", "motion", "mocap", "physics", "trails", "lineEdit"]);
9
+ export const CHARACTER_CAPABILITIES = Object.freeze(["rig", "ik", "pose", "motion"]);
10
10
  const PROXY_BODY_RADIUS = 0.22;
11
11
  const PROXY_BODY_HEIGHT = 1.45;
12
12
  const PROXY_HEAD_RADIUS = 0.12;
@@ -36,7 +36,7 @@ export function proxyFigureBounds(character, placement = character) {
36
36
  return { min, max };
37
37
  }
38
38
 
39
- /** `{ rig, ik, pose, motion, mocap, physics, trails, lineEdit }` - all false
39
+ /** `{ rig, ik, pose, motion }` - all false
40
40
  * for a capsule figure, all true for a rigged character. */
41
41
  export function characterCapabilities(character) {
42
42
  const rigged = !isProxyFigure(character);
@@ -48,13 +48,7 @@ const FEATURE_LABELS = {
48
48
  rig: ["the rig", "리그"],
49
49
  ik: ["IK", "IK"],
50
50
  pose: ["Pose mode", "포즈 모드"],
51
- motion: ["Motion generation", "모션 생성"],
52
- mocap: ["Motion capture", "모션 캡처"],
53
- photoPose: ["Photo pose", "사진 포즈"],
54
- physics: ["Physics", "물리 보정"],
55
- collision: ["Collision cleanup", "충돌 정리"],
56
- trails: ["Motion trails", "궤적선"],
57
- lineEdit: ["Line edit", "라인 편집"],
51
+ motion: ["Compiled motion", "모션 컴파일"],
58
52
  take: ["Loading a take", "테이크 불러오기"],
59
53
  };
60
54
 
@@ -2,7 +2,8 @@
2
2
  // the editor ports own native state/history, and the existing journal owns
3
3
  // idempotency. Synchronous UI actions stay synchronous (including user gestures).
4
4
  import { HISTORY_LIMIT } from './history.js';
5
- import { StudioProtocolError, StudioSchemas, validateStudioSchema, validateReceipt, validateStudioIdentity } from './studio-agent-protocol.js';
5
+ import { StudioProtocolError, StudioSchemas, validateStudioSchema, validateReceipt, validateStudioIdentity, refusalCode } from './studio-agent-protocol.js';
6
+ import { agentTurnControls, markAgentTurn } from './agent-turn-mark.js';
6
7
 
7
8
  // A batching/retention adapter over the existing object history owner. The
8
9
  // native store still owns every snapshot and transition; this tracks only the
@@ -62,9 +63,7 @@ export function createCommandBus({ registry, ports }) {
62
63
  const object = properties => ({ type: 'object', properties, required: Object.keys(properties), additionalProperties: false });
63
64
  const argsSchema = { type: 'object', properties: {}, required: [], additionalProperties: true };
64
65
  const controls = {
65
- 'agent.turn.begin': object({ turnId: identifier }),
66
- 'agent.turn.finish': object({ turnId: identifier }),
67
- 'agent.turn.cancel': object({ turnId: identifier }),
66
+ ...agentTurnControls(object, identifier),
68
67
  'run.begin': object({ id: identifier, args: argsSchema }),
69
68
  'run.update': object({ txId: identifier, args: argsSchema }),
70
69
  'run.commit': object({ txId: identifier }),
@@ -94,6 +93,8 @@ export function createCommandBus({ registry, ports }) {
94
93
  }
95
94
  if (agentTurn?.txId !== args.turnId) fail('STALE_TARGET', 'The agent turn is no longer open.');
96
95
  const turn = agentTurn;
96
+ // A script's mark, and the rollback to it that keeps the turn open (agent-turn-mark.js).
97
+ if (id === 'agent.turn.mark' || id === 'agent.turn.rollback') return transactionReceipt(id, markAgentTurn(id, turn), request, before);
97
98
  // A rolled-back turn restores every owner it touched and leaves no entry.
98
99
  if (id === 'agent.turn.cancel') { agentTurn = null; turn.session?.cancel(); return transactionReceipt(id, turn, request, before); }
99
100
  try { turn.historyEntryId = turn.session?.commit().historyEntryId; }
@@ -202,7 +203,7 @@ export function createCommandBus({ registry, ports }) {
202
203
  function refusal(request, before, error, mutated) {
203
204
  const changed = mutated ?? (ports.read().revision !== before.revision);
204
205
  return validateReceipt({ ok: false, commandId: request.commandId, host: before.host,
205
- code: error.code ?? 'INVALID_ARGUMENT', phase: changed ? 'commit' : 'admission', affectedIds: [], expectedTargets: [], currentTargets: [],
206
+ code: refusalCode(error), phase: changed ? 'commit' : 'admission', affectedIds: [], expectedTargets: [], currentTargets: [],
206
207
  mutated: changed, preserved: { authoredState: changed ? 'changed' : 'unchanged' }, recovery: { action: 'inspect', retryAllowed: false },
207
208
  message: [...String(error.message || error)].slice(0, 500).join('') });
208
209
  }
@@ -219,7 +220,7 @@ export function createCommandBus({ registry, ports }) {
219
220
  if (entry.kind === 'mutation' && changed && !historyEntryId && !turn) fail('UNCERTAIN_APPLY', `${entry.id} changed the scene without one undoable entry.`);
220
221
  const completed = entry.kind === 'job' || entry.kind === 'document' || (entry.kind === 'mutation' && changed && (turn || after.revision > before.revision + 1));
221
222
  const ids = completed || changed ? result.affectedIds : entry.kind === 'transient' ? [before.host.sceneId] : [];
222
- if (turn && changed) for (const id of ids) turn.affectedIds.add(id);
223
+ if (turn && changed) for (const id of ids) { turn.affectedIds.add(id); turn.mark?.since.add(id); }
223
224
  const build = level => validateReceipt({ ok: true, commandId: request.commandId, receiptId: crypto.randomUUID(), host: before.host,
224
225
  action: entry.id, summary: result.summary, status: completed ? 'completed' : entry.kind === 'transient' ? 'transient' : changed ? 'applied' : 'noop',
225
226
  authored: changed, ...(completed ? { kind: entry.kind, ...(result.output === undefined ? {} : { output: result.output }), ...(same(before.host, after.host) ? {} : { nextHost: after.host }) } : entry.kind === 'transient' ? { view: { before: before.viewRevision ?? 0, after: after.viewRevision ?? 0 } } : { mutated: changed }),
@@ -292,8 +293,9 @@ export function createCommandBus({ registry, ports }) {
292
293
  // Job controls observe/cancel an admitted identity; completion itself
293
294
  // can advance the revision while their wire request is in transit.
294
295
  // Document identity and the job's own publication fence still apply.
295
- if (!['job.await', 'job.cancel', 'agent.turn.finish', 'agent.turn.cancel'].includes(id) && request.expectedRevision !== before.revision) fail('STALE_SCENE', 'Authored state changed; obtain fresh intent.');
296
- if (before.busy && !turn && !transactions.has(validated.txId) && !id.startsWith('job.')) fail('TARGET_BUSY', 'Finish the current editor gesture first.');
296
+ // A readOnly action (declared data, e.g. motion.qa) only measures: no revision or gesture fence, the host check above still holds.
297
+ if (!entry?.readOnly && !['job.await', 'job.cancel', 'agent.turn.mark', 'agent.turn.rollback', 'agent.turn.finish', 'agent.turn.cancel'].includes(id) && request.expectedRevision !== before.revision) fail('STALE_SCENE', 'Authored state changed; obtain fresh intent.');
298
+ if (!entry?.readOnly && before.busy && !turn && !transactions.has(validated.txId) && !id.startsWith('job.')) fail('TARGET_BUSY', 'Finish the current editor gesture first.');
297
299
  }
298
300
  if (controls[id]) return mapResult(control(id, validated, request, before), remember, rejected);
299
301
  if (transactions.size) fail('TARGET_BUSY', 'Finish or cancel the open command transaction first.');
@@ -9,8 +9,7 @@ import { createCharacterEntry, CHARACTER_KIND_IDS, POSTURES, PROXY_FIGURE_MODEL,
9
9
  import { KIND_FIXED_REFUSAL } from '../character-kind.js';
10
10
  import { track } from '../analytics.js';
11
11
  import { DEFAULT_POSE } from '../poses.js';
12
- import { createStableItemId, updateStableItem, removeStableItem } from '../stable-items.js';
13
- import { movePromptClipFrames } from '../ardy/prompt-clips.js';
12
+ import { createStableItemId } from '../stable-items.js';
14
13
  import { STUDIO_TOOL_SCHEMAS, StudioSchemas } from '../studio-agent-protocol.js';
15
14
 
16
15
  const id = StudioSchemas.TargetGuard.properties.targetId;
@@ -21,7 +20,7 @@ const record = { type: 'object', properties: {}, additionalProperties: true };
21
20
  const newCharacter = { ...record, properties: { model: { type: 'string', enum: [...CHARACTER_KIND_IDS] }, posture: { type: 'string', enum: [...POSTURES] } }, required: [] };
22
21
  const characterPatch = { ...record, properties: { posture: { type: 'string', description: `One of ${POSTURES.join(', ')}; anything else stands.` } }, required: [] };
23
22
  const mutation = (id, label, schema, exposure = 'open') => ({ id, label, description: label, kind: 'mutation', undoDomain: 'cast', input: schema, exposure });
24
- const blocks = elementSetSchema('character').properties.set.properties.layer.properties.promptClips;
23
+
25
24
  const semantic = [
26
25
  mutation('character.set', 'Set character fields', elementSetSchema('character')),
27
26
  mutation('character.add', 'Add character', input({ character: newCharacter })),
@@ -29,7 +28,7 @@ const semantic = [
29
28
  mutation('character.update', 'Update character', input({ characterId: id, patch: characterPatch })),
30
29
  studioActionDeclaration('character.move'),
31
30
  mutation('character.setPose', 'Set character pose', input({ characterId: id, pose: { oneOf: [id, record, { type: 'null' }] }, clearMotion: { type: 'boolean' } }, ['characterId', 'pose'])),
32
- mutation('character.setPromptBlocks', 'Set prompt blocks', input({ characterId: id, blocks })),
31
+
33
32
  mutation('characters.arrange', 'Arrange characters', STUDIO_TOOL_SCHEMAS.arrange_characters),
34
33
  mutation('cast.replace', 'Replace cast', input({ characters: { type: 'array', items: record, minItems: 1 } }), 'ui-only'),
35
34
  mutation('cast.setCustomPoses', 'Set pose library', input({ poses: { type: 'array', items: record } }), 'ui-only'),
@@ -37,9 +36,6 @@ const semantic = [
37
36
  mutation('cast.removePose', 'Remove saved pose', input({ id }), 'ui-only'),
38
37
  mutation('cast.showExtras', 'Show extra subjects', input({ show: { type: 'boolean' } }), 'ui-only'),
39
38
  mutation('cast.setLayer', 'Set character layer', input({ characterId: id, layer: record }), 'ui-only'),
40
- ...['add', 'move', 'resize', 'change', 'remove'].map(verb => mutation(`character.${verb}PromptBlock`, `${verb} prompt block`,
41
- input({ characterId: id, id, frame: { type: 'number' }, edge: { type: 'string', enum: ['start', 'end'] }, text: { type: 'string', maxLength: 2000 } },
42
- verb === 'add' ? ['characterId', 'frame'] : verb === 'remove' ? ['characterId', 'id'] : verb === 'change' ? ['characterId', 'id', 'text'] : verb === 'resize' ? ['characterId', 'id', 'edge', 'frame'] : ['characterId', 'id', 'frame']), 'ui-only')),
43
39
  ];
44
40
  export const declarations = Object.freeze([...semantic, ...["character.addWaypoint", "character.moveWaypoint", "character.removeWaypoint", "character.clearWaypoints"].map(studioActionDeclaration)]);
45
41
 
@@ -56,29 +52,6 @@ export function register(registry, ports) {
56
52
  const object = ports.state().objects.find(row => row.id === value) ?? fail('STALE_TARGET', `Object ${value} is not in this scene.`);
57
53
  if (object.attach) fail('INVALID_ARGUMENT', 'A character cannot be grouped under an object that a character carries.');
58
54
  };
59
- function promptEdit(command, args) {
60
- const before = character(args.characterId), clips = before.layer.promptClips;
61
- let next;
62
- if (command === 'character.addPromptBlock') {
63
- const snapped = Math.max(0, Math.round(args.frame / 48) * 48);
64
- const blocked = clips.some(clip => snapped < clip.endFrame && snapped + 48 > clip.startFrame);
65
- const startFrame = blocked ? Math.max(snapped, ...clips.map(clip => clip.endFrame)) : snapped;
66
- next = [...clips, { id: createStableItemId('prompt-clip'), startFrame, endFrame: startFrame + 48, text: '' }];
67
- } else if (command === 'character.removePromptBlock') next = removeStableItem(clips, args.id, 'promptClips');
68
- else if (command === 'character.changePromptBlock') next = updateStableItem(clips, args.id, clip => ({ ...clip, text: args.text }), 'promptClips');
69
- else if (command === 'character.movePromptBlock') next = movePromptClipFrames(clips, args.id, args.frame, 48);
70
- else {
71
- next = updateStableItem(clips, args.id, clip => {
72
- const snapped = Math.max(0, Math.round(args.frame / 48) * 48);
73
- return args.edge === 'start' ? { ...clip, startFrame: Math.min(Math.max(snapped, clip.endFrame - 120), clip.endFrame - 48) }
74
- : { ...clip, endFrame: Math.min(Math.max(clip.startFrame + 48, snapped), clip.startFrame + 120) };
75
- }, 'promptClips');
76
- const resized = next.find(clip => clip.id === args.id);
77
- if (next.some(clip => clip.id !== args.id && resized.startFrame < clip.endFrame && resized.endFrame > clip.startFrame)) return;
78
- }
79
- patch(args.characterId, { layer: { ...before.layer, promptClips: next } });
80
- owner().extendTimeline(next.reduce((end, clip) => Math.max(end, clip.endFrame), 24));
81
- }
82
55
  const methods = {
83
56
  'character.add': ({ character: value }) => {
84
57
  const row = createCharacterEntry({ ...value, id: value.id ?? createStableItemId('character') }, owner().read().length);
@@ -94,9 +67,7 @@ export function register(registry, ports) {
94
67
  },
95
68
  'character.move': ({ characterId, ...value }) => patch(characterId, value),
96
69
  'character.setPose': ({ characterId, pose: value, clearMotion }) => { character(characterId); owner().applyPose(characterId, pose(value), clearMotion); },
97
- 'character.setPromptBlocks': ({ characterId, blocks }) => {
98
- const entry = character(characterId); patch(characterId, { layer: { ...entry.layer, promptClips: blocks } });
99
- },
70
+
100
71
  'cast.replace': ({ characters }) => owner().write(characters),
101
72
  'cast.setCustomPoses': ({ poses }) => owner().writeState(before => ({ ...before, customPoses: poses })),
102
73
  'cast.savePose': ({ pose, characterId, clearMotion }) => {
@@ -127,7 +98,7 @@ export function register(registry, ports) {
127
98
  for (const declaration of semantic.slice(1)) registry.register({ ...declaration, available: mounted, run(args, context) {
128
99
  if (declaration.id === 'characters.arrange') { const plan = owner().arrange(args); return { affectedIds: plan.affectedIds, summary: 'Arranged characters.', checks: plan.checks, warnings: plan.warnings }; }
129
100
  const before = owner().read();
130
- if (declaration.id.endsWith('PromptBlock')) promptEdit(declaration.id, args); else methods[declaration.id](args);
101
+ methods[declaration.id](args);
131
102
  // The Assets tile and the MCP live path report themselves; this is the agent and
132
103
  // any other non-UI bus caller adding a capsule figure.
133
104
  if (declaration.id === 'character.add' && context?.origin && context.origin !== 'ui' && isProxyFigure(owner().read().find(row => !before.some(prior => prior.id === row.id)))) track('cast:proxy_added', { surface: 'agent' });
@@ -5,7 +5,7 @@ import { registerElementKind } from '../elements.js';
5
5
  const elements = STUDIO_ELEMENTS.filter(row => row.path.startsWith('character.')).map(row => {
6
6
  const { min, max, gizmo, ...element } = row;
7
7
  if (row.path === 'character.position') return { ...element, documentPath: { x: 'x', y: 'y', z: 'z' } };
8
- if (row.path === 'character.promptBlocks') return { ...element, documentPath: 'layer.promptClips' };
8
+
9
9
  if (row.path === 'character.pose') return { ...element, readback: item => ({ text: item?.pose?.id ?? null }) };
10
10
  return element;
11
11
  });