cozyclay 2.1.0 → 3.0.0

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