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,207 @@
1
+ // CozyClay 3 motion: the agent writes a motion spec (keys, anchors, props, fits, generators,
2
+ // checks, goals), the deterministic compiler bakes every frame, QA measures it, and the take is
3
+ // installed on the character. Cheap enough to run in a loop until QA reports nothing. The words
4
+ // the agent reads live in motion3/docs.js; the contracts in docs/motion-api-v3.1.md.
5
+ import { fail, characterOf } from "./shared.js";
6
+ import { motionScene } from "../motion3/scene.js";
7
+ import { skeletonFor } from "../motion3/rigs/index.js";
8
+ import { fk } from "../motion3/compiler/skeleton.js";
9
+ import { add, apply, ry } from "../motion3/compiler/math.js";
10
+ import { resolveBone } from "../motion3/bones.js";
11
+ import { MOTION_ACTION_HELP as HELP } from "../motion3/docs.js";
12
+ import { DESCRIBE_TOPICS } from "../motion3/schema/fields.js";
13
+ import { describe, describeMany } from "../motion3/describe.js";
14
+ import { createSpecStore, specIdOf, SPEC_STORE_SIZE } from "../motion3/spec-store.js";
15
+ import { applyPatch } from "../motion3/json-patch.js";
16
+ import { deltaOf } from "../motion3/delta.js";
17
+ import { fitOutput, reportPage } from "../motion3/report.js";
18
+ import { keyObject } from "../motion3/key-edit.js";
19
+ import { expandUse } from "../motion3/skills/index.js";
20
+ import { publishDraft } from "../agent-progress.js";
21
+ import { carryEdit } from "../motion3/edit-carry.js";
22
+ import { normalizeCompileArgs } from "../motion3/lenient.js";
23
+ import { createCompileRunner } from "./motion3-compile.js";
24
+
25
+ const TIMELINE_FPS = 24;
26
+ const id = { type: "string", minLength: 1 };
27
+ const specId = { type: "string", pattern: "^spec-[0-9a-f]{12}$" };
28
+ const openObject = { type: "object", properties: {}, required: [], additionalProperties: true };
29
+ const input = (properties, required = Object.keys(properties)) => ({ type: "object", properties, required, additionalProperties: false });
30
+ const list = (items, maxItems) => ({ type: "array", items, minItems: 1, maxItems });
31
+ const numbers = n => ({ type: "array", items: { type: "number" }, minItems: n, maxItems: n });
32
+
33
+ // readOnly: reads the scene and the take, never changes them (C10).
34
+ const declarations = Object.freeze([
35
+ { id: "motion.compile", label: "Compile motion", kind: "job", domain: "motion", timeoutMs: 120000, description: HELP.compile,
36
+ // patch may be empty: base alone (or patch: []) re-measures the stored spec, with new probes; "never" is install: false
37
+ input: input({ characterId: id, spec: openObject, base: specId, patch: { type: "array", items: openObject, maxItems: 64 },
38
+ install: { oneOf: [{ type: "boolean" }, { const: "ifClean" }, { const: "never" }], default: true },
39
+ probes: list(openObject, 16), repair: input({ rounds: { type: "integer", minimum: 1, maximum: 8, default: 4 } }, []) }, ["characterId"]) },
40
+ { id: "motion.qa", label: "Measure motion", kind: "job", domain: "motion", timeoutMs: 120000, readOnly: true, description: HELP.qa,
41
+ input: input({ characterId: id, checks: openObject }, ["characterId"]) },
42
+ { id: "motion.sample", label: "Sample motion", kind: "job", domain: "motion", timeoutMs: 30000, readOnly: true, description: HELP.sample,
43
+ input: input({ characterId: id, frames: { type: "array", items: { type: "integer", minimum: 0 }, minItems: 1, maxItems: 64 },
44
+ bones: { type: "array", items: id, maxItems: 65 } }, ["characterId", "frames"]) },
45
+ { id: "motion.describe", label: "Describe motion spec", kind: "job", domain: "motion", timeoutMs: 10000, readOnly: true, description: HELP.describe,
46
+ input: input({ topic: { type: "string", enum: DESCRIBE_TOPICS }, name: id,
47
+ topics: list({ oneOf: [{ type: "string", enum: DESCRIBE_TOPICS }, input({ topic: { type: "string", enum: DESCRIBE_TOPICS }, name: id }, ["topic"])] }, 8) }, []) },
48
+ { id: "motion.report", label: "Read motion report", kind: "job", domain: "motion", timeoutMs: 10000, readOnly: true, description: HELP.report,
49
+ input: input({ specId, part: { type: "string", enum: ["groups", "timeline", "probes"], default: "groups" }, from: { type: "integer", minimum: 0, default: 0 } }, ["specId"]) },
50
+ { id: "motion.keyObject", label: "Key held object", kind: "job", domain: "motion", timeoutMs: 120000, description: HELP.keyObject,
51
+ input: input({ characterId: id, objectId: id, frame: { type: "integer", minimum: 0 }, pos: numbers(3), rot: numbers(4), aim: numbers(3), roll: { type: "number" },
52
+ angle: { type: "number" }, remove: { type: "boolean", default: false } }, ["characterId", "objectId", "frame"]) },
53
+ ]);
54
+ export { declarations };
55
+
56
+ const DEFAULT_BONES = ["Hips", "Head", "LeftHand", "RightHand", "LeftFoot", "RightFoot"];
57
+ // the inputs a compile fixed instead of refusing (lenient.js), kept in every answer (report.js keeps the head)
58
+ const MAX_ADJUSTED = 8;
59
+ const adjustedOf = notes => (notes.length ? { adjusted: notes.length > MAX_ADJUSTED ? [...notes.slice(0, MAX_ADJUSTED - 1), `${notes.length - MAX_ADJUSTED + 1} more`] : notes } : {});
60
+ const adjustedSay = notes => (notes.length ? `; ${notes.length} input${notes.length > 1 ? "s" : ""} adjusted (output.adjusted)` : "");
61
+ const unknownSpec = (what, value, then = "send spec") => fail("INVALID_ARGUMENT", `${what} "${value}" is not known (the editor keeps the last ${SPEC_STORE_SIZE} compiled specs this session); ${then}`);
62
+
63
+ const sameArray = (a, b) => a?.length === b?.length && Array.prototype.every.call(a, (v, i) => v === b[i]);
64
+ /** True when `installed` is the take `take` would install: the same spec, placement, bones and object tracks. */
65
+ function sameTake(installed, take) {
66
+ return installed?.schema === take.schema && installed.frameCount === take.frameCount && installed.rig === take.rig
67
+ && specIdOf(installed.spec ?? null) === specIdOf(take.spec) && JSON.stringify(installed.placement) === JSON.stringify(take.placement)
68
+ && sameArray(installed.rot, take.rot) && sameArray(installed.hips, take.hips) && JSON.stringify(installed.objects ?? {}) === JSON.stringify(take.objects);
69
+ }
70
+
71
+ /** then(value) now, or when the promise settles (a compile answers at once in Node, later from the browser's worker). */
72
+ const after = (value, then) => (typeof value?.then === "function" ? value.then(then) : then(value));
73
+ /** Runs `run`, turning a MOTION_SPEC_INVALID error into INVALID_ARGUMENT with the exact message. */
74
+ function asArgument(run) {
75
+ try {
76
+ return run();
77
+ } catch (error) {
78
+ if (/^MOTION_SPEC_INVALID/.test(error.message)) fail("INVALID_ARGUMENT", error.message.replace(/^MOTION_SPEC_INVALID:\s*/, ""));
79
+ throw error;
80
+ }
81
+ }
82
+
83
+ export function register(registry, ports) {
84
+ const store = createSpecStore();
85
+ const owner = () => ports.storeDomain("motion");
86
+ const mounted = () => Boolean(ports.storeDomain?.("motion")) || "The motion owner is not mounted.";
87
+ const always = () => true;
88
+ const rigOf = character => {
89
+ const sk = skeletonFor(character.model);
90
+ if (!sk) fail("TARGET_NOT_READY", `${character.subject || character.id} is a ${character.model} body, which motion compile cannot drive; use an X Bot or Y Bot character.`);
91
+ if (Math.abs((character.scale ?? 1) - 1) > 1e-6) fail("TARGET_NOT_READY", "Set the character's scale back to 1 before compiling motion.");
92
+ return sk;
93
+ };
94
+ // one compile worker per editor in the browser, the calling thread elsewhere (motion3-compile.js)
95
+ const runner = createCompileRunner();
96
+ const workerWarning = () => { const note = runner.takeWarning(); return note ? { warnings: [note] } : {}; };
97
+ /**
98
+ * spec on the character's rig and the current scene (compile-job.js) -> { stored (the spec a take keeps and its specId
99
+ * names), qa, ..., take when bake is given }, at once or as a promise. opts: probes, checks, lenient, repair.
100
+ */
101
+ const compile = (character, spec, opts = {}, bake) => {
102
+ rigOf(character);
103
+ const placed = bake && { position: [character.x ?? 0, character.y ?? 0, character.z ?? 0], yaw: character.rot ?? 0, ...bake };
104
+ return runner.run({ model: character.model, scene: motionScene(ports.state().objects), spec, ...opts, ...(placed ? { bake: placed } : {}) });
105
+ };
106
+ /** The whole output, kept in the store (motion.report, delta) and cut to the receipt budget (report.js). */
107
+ const report = (r, head, extra = {}) => {
108
+ const specIdValue = store.put(r.stored);
109
+ store.keepParts(specIdValue, { groups: r.qa.groups, timeline: r.timeline, probes: r.probes ?? {} });
110
+ return fitOutput({ ...head, specId: specIdValue, qa: r.qa, observations: r.observations, ...extra, ...(r.probes ? { probes: r.probes } : {}), timeline: r.timeline,
111
+ fixed: r.fixed, ...(r.search ? { search: r.search } : {}), ...(r.used.length ? { skills: r.used } : {}), ms: r.ms });
112
+ };
113
+ /**
114
+ * A compiled take on the character, installed as one undo entry; put() keeps the timing edit of the take it replaces
115
+ * (edit-carry.js) and returns what it did with it (a note for the receipt), or undefined.
116
+ */
117
+ const put = (character, take, context) => {
118
+ let note;
119
+ const apply = () => {
120
+ const id = character.id, edit = owner().layer?.(id)?.take?.edit, full = edit && owner().fullMotionFor?.(id), carried = full && carryEdit(edit, full.frames ?? full.frameCount, take.frameCount);
121
+ owner().replace(id, { ...take, frames: take.frameCount });
122
+ if (carried?.edit) { owner().writeLayer(id, { take: { ...owner().layer(id).take, edit: carried.edit } }); ports.storeDomain("cast")?.syncTimeline?.(); }
123
+ note = carried?.note;
124
+ };
125
+ if (context?.commit) context.commit(apply); else apply();
126
+ return note;
127
+ };
128
+ const installed = characterId => owner().motionFor(characterId)?.schema === "cozyclay.take.v3" ? owner().motionFor(characterId)
129
+ : fail("TARGET_NOT_READY", "This character has no compiled take; run motion.compile first.");
130
+
131
+ // normalizeArgs: other spellings of install and repair become the declared ones before the schema check (lenient.js)
132
+ registry.register({ ...declarations[0], normalizeArgs: normalizeCompileArgs, available: mounted, target: args => args.characterId, run({ characterId, spec, base, patch, install: asked = true, probes, repair }, context) {
133
+ const install = asked === "never" ? false : asked;
134
+ const character = characterOf(ports, characterId);
135
+ if ((spec === undefined) === (base === undefined)) fail("INVALID_ARGUMENT", "Send exactly one of spec or base (the specId of a compiled spec, with patch).");
136
+ if (patch !== undefined && base === undefined) fail("INVALID_ARGUMENT", "patch applies to base, the specId of a compiled spec; send base with it, or the whole spec.");
137
+ const patched = [], source = spec ?? asArgument(() => applyPatch(store.get(base) ?? unknownSpec("base", base), patch ?? [], patched));
138
+ // the take is always baked: the user watches every draft, installed or not
139
+ return after(compile(character, source, { probes, repair }, {}), r => {
140
+ const { qa, take } = r, notes = [...patched, ...(r.notes ?? [])];
141
+ // ifClean: install only a clean final result (qa.ok includes the goals; after repair when asked)
142
+ const wanted = install === "ifClean" ? qa.ok : install;
143
+ let installs = wanted, kept = false, timing;
144
+ // open ranges are stored with the values the search chose, so motion.qa re-measures this take
145
+ if (wanted) {
146
+ // ifClean of the take already installed changes nothing (no undo entry; IK edits on it stay)
147
+ kept = install === "ifClean" && sameTake(owner().motionFor(characterId), take);
148
+ installs = !kept;
149
+ if (installs) timing = put(character, take, context);
150
+ }
151
+ const delta = base === undefined ? {} : { delta: deltaOf(store.parts(base)?.groups ?? [], qa.groups, base) };
152
+ const how = kept ? "; already installed, unchanged" : install === "ifClean" ? (installs ? "; installed" : "; not installed") : "";
153
+ const output = report(r, { frames: r.frameCount, fps: TIMELINE_FPS, installed: wanted, ...adjustedOf(notes) }, { ...delta, ...(r.repair ? { repair: r.repair } : {}), ...(timing ? { timing } : {}) });
154
+ // agent-progress.js: viewport ghost, timeline beats, agent panel
155
+ publishDraft(characterId, { take, installed: installs || kept, specId: output.specId, label: r.label, qa, timeline: r.timeline });
156
+ return { affectedIds: installs ? [characterId] : [], summary: `Compiled ${r.frameCount} frames; QA ${qa.ok ? "clean" : `${qa.total} issues`}${how}${adjustedSay(notes)}.`, output, ...workerWarning() };
157
+ });
158
+ } });
159
+ registry.register({ ...declarations[1], available: mounted, run({ characterId, checks }) {
160
+ const character = characterOf(ports, characterId), take = installed(characterId);
161
+ return after(compile(character, take.spec, { checks }), r => ({ affectedIds: [], summary: `QA ${r.qa.ok ? "clean" : `${r.qa.total} issues`}${adjustedSay(r.notes ?? [])}.`,
162
+ output: report(r, adjustedOf(r.notes ?? [])), ...workerWarning() }));
163
+ } });
164
+ registry.register({ ...declarations[2], available: mounted, run({ characterId, frames, bones = DEFAULT_BONES }) {
165
+ const character = characterOf(ports, characterId), take = installed(characterId), sk = rigOf(character);
166
+ const yaw = ry(take.placement?.yaw ?? 0), at = take.placement?.position ?? [0, 0, 0], B = take.bones.length;
167
+ const rows = frames.map(f => {
168
+ const ff = Math.min(take.frameCount - 1, f);
169
+ const quats = Array.from({ length: B }, (_, b) => Array.from(take.rot.subarray((ff * B + b) * 4, (ff * B + b) * 4 + 4)));
170
+ const state = fk(sk, quats, Array.from(take.hips.subarray(ff * 3, ff * 3 + 3)));
171
+ return { frame: ff, bones: Object.fromEntries(bones.map(name => {
172
+ const { bone, hint } = resolveBone(sk, name);
173
+ if (bone === null) fail("INVALID_ARGUMENT", `Unknown bone ${name}${hint || "."}`);
174
+ return [name, add(at, apply(yaw, state.P[sk.index[bone]])).map(v => Math.round(v * 1e4) / 1e4)];
175
+ })) };
176
+ });
177
+ return { affectedIds: [], summary: `Sampled ${rows.length} frames.`, output: { frames: rows } };
178
+ } });
179
+ registry.register({ ...declarations[3], available: always, run({ topic, name, topics }) {
180
+ if (topic === undefined && topics === undefined) fail("INVALID_ARGUMENT", "Send topic (and name) for one answer, or topics: [{ topic, name? }] for several in one call.");
181
+ if (topics) {
182
+ // C19: a topic beside topics joins them; a name beside topics goes to the one ask that can take it
183
+ const asks = [...(topic === undefined ? [] : [{ topic }]), ...topics.map(t => (typeof t === "string" ? { topic: t } : { ...t }))];
184
+ const open = asks.filter(a => a.name === undefined && (asks.length === 1 || a.topic === "skills"));
185
+ if (name !== undefined && open.length !== 1) fail("INVALID_ARGUMENT", "name goes with one topic; inside topics give each its own: topics: [{ topic, name? }].");
186
+ if (name !== undefined) open[0].name = name;
187
+ const output = asArgument(() => describeMany(asks));
188
+ return { affectedIds: [], summary: `Described ${output.topics.map(a => (a.name ? `${a.topic} ${a.name}` : a.topic)).join(", ")}${output.more ? `; ${output.more.length} more did not fit` : ""}.`, output };
189
+ }
190
+ return { affectedIds: [], summary: `Described ${topic}${name ? ` ${name}` : ""}.`, output: asArgument(() => describe(topic, name)) };
191
+ } });
192
+ registry.register({ ...declarations[4], available: always, run({ specId: wanted, part = "groups", from = 0 }) {
193
+ const page = reportPage(store.parts(wanted) ?? unknownSpec("specId", wanted, "compile it again"), wanted, part, from);
194
+ return { affectedIds: [], summary: `${page.items.length} ${part} from ${from}.`, output: page };
195
+ } });
196
+ // motion.keyObject (C14): one key of a held object (or a hinge angle) in the installed take's spec, compiled
197
+ // and installed whatever QA says (a user edit), as one undo entry
198
+ registry.register({ ...declarations[5], available: mounted, target: args => args.characterId, run({ characterId, ...args }, context) {
199
+ const character = characterOf(ports, characterId), take = installed(characterId), scene = motionScene(ports.state().objects);
200
+ const { spec, keyed } = asArgument(() => keyObject(take.spec, scene, args, expandUse(take.spec, { sk: rigOf(character), scene, lenient: true }).spec.objects));
201
+ return after(compile(character, spec, { lenient: true }, { prompt: take.prompt }), r => {
202
+ const timing = put(character, r.take, context);
203
+ return { affectedIds: [characterId], summary: `${keyed.removed ? "Removed the key of" : "Keyed"} ${keyed.objectId} at f${keyed.f}; QA ${r.qa.ok ? "clean" : `${r.qa.total} issues`}; installed.`,
204
+ output: report(r, { frames: r.frameCount, fps: TIMELINE_FPS, installed: true, keyed }, timing ? { timing } : {}), ...workerWarning() };
205
+ });
206
+ } });
207
+ }
@@ -8,6 +8,8 @@ import { expandSculptParts, normalizeSculptRecipe, patchSculptRecipe } from '../
8
8
  import { rankingRow, scoreSculptAgainstReference } from '../sculpt-compare.js';
9
9
  import { browserSculptImages, shrinkRgba } from './sculpt-images.js';
10
10
  import { clampWarning } from '../receipt-findings.js';
11
+ import { effectPresetSet } from '../effects/effect-record.js';
12
+ import { withLightWarnings } from '../light-blockers.js';
11
13
  import { STUDIO_TOOL_SCHEMAS, StudioSchemas } from '../studio-agent-protocol.js';
12
14
 
13
15
  const id = StudioSchemas.TargetGuard.properties.targetId;
@@ -80,7 +82,7 @@ export function register(registry, ports) {
80
82
  // goes to the top where it is seen, not below a long expanded group.
81
83
  const next = args.first ? [placed, ...before] : [...before, placed];
82
84
  owned().write(args.parent === undefined ? next : setSceneObjectParent(next, placed.id, args.parent));
83
- return result(before, 'Added object.');
85
+ return withLightWarnings(result(before, 'Added object.'), owned().read());
84
86
  },
85
87
  'object.sculpt': args => {
86
88
  const before = owned().read();
@@ -156,13 +158,13 @@ export function register(registry, ports) {
156
158
  },
157
159
  'objects.arrange': args => {
158
160
  const plan = owned().arrange(args);
159
- return { affectedIds: plan.affectedIds, summary: 'Arranged objects.', checks: plan.checks, warnings: plan.warnings };
161
+ return withLightWarnings({ affectedIds: plan.affectedIds, summary: 'Arranged objects.', checks: plan.checks, warnings: plan.warnings }, owned().read());
160
162
  },
161
163
  };
162
164
  // Scalar scale is a convenience at the command boundary; the registry still
163
165
  // owns all field mapping, normalization, publication and patch aliases.
164
166
  registerElementSet({ register(entry) {
165
- const expand = op => typeof op.set.scale === 'number' ? { ...op, set: { ...op.set, scale: { x: op.set.scale, y: op.set.scale, z: op.set.scale } } } : op;
167
+ const expand = op => effectPresetSet(typeof op.set.scale === 'number' ? { ...op, set: { ...op.set, scale: { x: op.set.scale, y: op.set.scale, z: op.set.scale } } } : op, ports.state().objects);
166
168
  registry.register({ ...entry, available, run: args => {
167
169
  const ops = (args.ops ?? [args]).map(expand);
168
170
  let draft = owned().read();
@@ -173,7 +175,7 @@ export function register(registry, ports) {
173
175
  if (object.attach) fail('CAPABILITY_MISSING', 'Use object.group to preserve an attached object world transform.');
174
176
  draft = setSceneObjectParent(draft, id, parent);
175
177
  }
176
- return entry.run(args.ops ? { ops } : ops[0]);
178
+ return withLightWarnings(entry.run(args.ops ? { ops } : ops[0]), owned().read());
177
179
  } });
178
180
  } }, ports, semantic[0]);
179
181
  for (const declaration of semantic.slice(1)) registry.register({ ...declaration, available, run: methods[declaration.id] });
@@ -7,6 +7,9 @@ import { track } from '../analytics.js';
7
7
  import { isImageAssetId } from '../scene-assets.js';
8
8
  import { createStableItemId, updateStableItem } from '../stable-items.js';
9
9
  import { railFollowForNewGeometry } from '../camera-rail-schedule.js';
10
+ import { buildRail } from '../camera-follow.js';
11
+ import { cameraKeyWarnings } from '../camera-direction.js';
12
+ import { TIMELINE_FPS } from '../timeline-defaults.js';
10
13
  import { studioActionDeclaration, studioActionRefusal } from '../studio-actions.js';
11
14
  import { STUDIO_TOOL_SCHEMAS, StudioSchemas } from '../studio-agent-protocol.js';
12
15
  import { elementSetSchema, registerElementSet } from './elements.js';
@@ -18,7 +21,7 @@ const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
18
21
  const number = { type: 'number' };
19
22
  const frame = { type: 'integer', minimum: 0 };
20
23
  const input = (properties, required = Object.keys(properties)) => ({ type: 'object', properties, required, additionalProperties: false });
21
- const mutation = (id, label, schema) => ({ id, label, description: label, kind: 'mutation', undoDomain: 'shot', input: schema });
24
+ const mutation = (id, label, schema, description = label) => ({ id, label, description, kind: 'mutation', undoDomain: 'shot', input: schema });
22
25
  const existing = ['shot.create', 'shot.createStill', 'shot.setCaption', 'shot.setCastOverride', 'shot.setHold', 'shot.split', 'shot.duplicate', 'shot.remove', 'shot.setRange', 'shot.setCameraRail', 'shot.clearCameraRail', 'shot.reorder'].map(studioActionDeclaration);
23
26
  const extra = [
24
27
  mutation('shot.set', 'Set shot fields', elementSetSchema('shot')),
@@ -30,7 +33,8 @@ const extra = [
30
33
  mutation('shot.clearKeys', 'Clear camera keys', input({ shotId: id })),
31
34
  mutation('shot.setTimeline', 'Set shot timeline', input({ frameCount: { type: 'integer', minimum: 24, maximum: 28800 } })),
32
35
  mutation('shot.setLens', 'Set camera lens', input({ fovDeg: { ...number, minimum: 14, maximum: 90 } })),
33
- mutation('shot.frame', 'Frame the shot', { ...input({ ...STUDIO_TOOL_SCHEMAS.frame_shot.properties, preset: { type: 'string' } }, []), oneOf: [STUDIO_TOOL_SCHEMAS.frame_shot, input({ preset: { type: 'string' } })] }),
36
+ mutation('shot.frame', 'Frame the shot', { ...input({ ...STUDIO_TOOL_SCHEMAS.frame_shot.properties, preset: { type: 'string' } }, []), oneOf: [STUDIO_TOOL_SCHEMAS.frame_shot, input({ preset: { type: 'string' } })] },
37
+ 'Frame a shot on one character (the frame_shot tool): framing.intent { size, level, view, side, focalMm } or framing.exact { position, lookAt, focalMm }. Without keyAtFrame a shot that holds one framing is reframed whole: a held shot. keyAtFrame adds a camera key there; every key eases in and out, so the camera slows to a stop at each one: key a pan or zoom from one spot with two keys (its start and end), lay a moving camera as a rail (shot.setCameraRail) and cut to a new shot for a new view. On a rail shot it puts the shot back on keys. Answers checks { derivedSize, screenFraction, clipped, behindCamera, occluded, cameraSide } for the subject at that frame.'),
34
38
  { ...mutation('shot.replace', 'Replace shot authoring', input({ shots: { type: 'array', items: { type: 'object', properties: {}, additionalProperties: true } } })), exposure: 'ui-only' },
35
39
  { ...mutation('shot.captureCamera', 'Capture camera framing', input({ shotId: id }, [])), exposure: 'ui-only' },
36
40
  // The Board's Stylize result (or null to clear it). UI-only: the picture has
@@ -40,6 +44,14 @@ const extra = [
40
44
  ];
41
45
  export const declarations = Object.freeze([extra[0], ...existing, ...extra.slice(1).map(entry => entry.id === 'shot.frame' ? entry : { ...entry, exposure: 'ui-only' })]);
42
46
 
47
+ // shot.setRange also takes range: [startFrame, endFrameExclusive] or flat startFrame/endFrameExclusive
48
+ // fields; both become range: { startFrame, endFrameExclusive } before validation (registry normalizeArgs).
49
+ function lenientRange(args) {
50
+ const { range, startFrame, endFrameExclusive, ...rest } = args, flat = Object.fromEntries(Object.entries({ startFrame, endFrameExclusive }).filter(([, v]) => v !== undefined));
51
+ if (Array.isArray(range) && range.length === 2 && !Object.keys(flat).length) return { ...rest, range: { startFrame: range[0], endFrameExclusive: range[1] } };
52
+ return range === undefined && Object.keys(flat).length ? { ...rest, range: flat } : args;
53
+ }
54
+
43
55
  export function register(registry, ports) {
44
56
  const owner = () => ports.storeDomain('shot');
45
57
  const mounted = () => Boolean(ports.storeDomain?.('shot')) || 'The shots document owner is not mounted.';
@@ -97,6 +109,27 @@ export function register(registry, ports) {
97
109
  let next = 0;
98
110
  writeStills(current.map(row => row.kind === 'still' ? order[next++] : row));
99
111
  }
112
+ // A rail owns the frames it covers, so the camera keys there go. Its answer
113
+ // names the move it makes: length, speed, height, aim and lens.
114
+ function setRail({ shotId, points, height, aim, aimHeight, focalMm }) {
115
+ const shot = shotOf(shotId), cast = [...ports.state().characters, ...ports.state().objects];
116
+ for (const id of Array.isArray(aim) ? aim : []) if (!cast.some(row => row.id === id)) fail('STALE_TARGET', `Aim ${id} is not a character or object in this scene.`);
117
+ const railFollow = railFollowForNewGeometry(shot.camera?.railFollow, shot.endFrame - shot.startFrame + 1);
118
+ const span = railFollow?.mode === 'range' ? [shot.startFrame + railFollow.startFrame, shot.startFrame + railFollow.endFrame] : [shot.startFrame, shot.endFrame];
119
+ const patch = { cameraRail: points, mode: 'rail', railFollow, ...(focalMm === undefined ? {} : { focalMm }),
120
+ ...(height === undefined ? {} : { craneHeight: { points: typeof height === 'number' ? [{ t: 0, height }, { t: 1, height }] : height } }),
121
+ ...(aim === undefined && aimHeight === undefined ? {} : { aim: aimOf(aim === undefined ? createCameraBlock(shot.camera).aim : aim && (Array.isArray(aim) ? { ids: aim } : { point: aim }), aimHeight) }) };
122
+ const keys = shot.cameraKeys.filter(key => key.frame < span[0] || key.frame > span[1]);
123
+ patchShot(shotId, row => ({ ...row, cameraKeys: keys, camera: updateCameraBlock(row.camera, patch) }));
124
+ const camera = createCameraBlock(shotOf(shotId).camera), metres = buildRail(camera.cameraRail)?.length ?? 0, seconds = (span[1] - span[0] + 1) / TIMELINE_FPS;
125
+ const heights = camera.craneHeight?.points.map(point => point.height) ?? [camera.followCam.height], speed = metres / seconds;
126
+ const aimed = camera.aim?.ids ? `${camera.aim.ids.join(', ')}${camera.aim.height === undefined ? '' : ` at ${camera.aim.height.toFixed(2)} m`}` : (camera.aim ? `(${['x', 'y', 'z'].map(axis => camera.aim.point[axis].toFixed(2)).join(', ')})` : 'the follow subject');
127
+ const note = `${metres.toFixed(1)} m over ${seconds.toFixed(1)} s (${speed.toFixed(2)} m/s), height ${[...new Set([heights[0], heights.at(-1)])].map(h => h.toFixed(2)).join('-')} m, aim ${aimed}, ${camera.focalMm ? `${camera.focalMm} mm` : 'scene lens'}${shot.cameraKeys.length > keys.length ? `; ${shot.cameraKeys.length - keys.length} camera keys removed` : ''}.`;
128
+ const fast = speed > camera.followCam.maxDollySpeed ? [{ code: 'RAIL_TOO_FAST', id: shotId, message: `${speed.toFixed(1)} m/s is past the ${camera.followCam.maxDollySpeed} m/s dolly cap, so the camera lags; lengthen the shot or the rail.` }] : [];
129
+ return { note, warnings: fast };
130
+ }
131
+ // aimHeight moves a cast aim off the chest to a world height (a whole body, a face).
132
+ const aimOf = (aim, aimHeight) => (aim?.ids && aimHeight !== undefined ? { ids: aim.ids, height: aimHeight } : aim);
100
133
  const methods = {
101
134
  'shot.create': () => storyboard() ? createStill() : owner().write(current => addShotAtFrame(current, ports.state().frame, owner().state().frameCount, owner().capture())),
102
135
  'shot.createStill': args => createStill(args),
@@ -119,9 +152,7 @@ export function register(registry, ports) {
119
152
  const edges = startFrame > shot.endFrame ? [['end', endFrame], ['start', startFrame]] : [['start', startFrame], ['end', endFrame]];
120
153
  owner().write(current => edges.reduce((rows, [edge, frame]) => resizeShot(rows, shotId, edge, frame, owner().state().frameCount), current));
121
154
  },
122
- 'shot.setCameraRail': ({ shotId, points }) => patchShot(shotId, shot => ({ ...shot, camera: updateCameraBlock(shot.camera, {
123
- cameraRail: points, mode: 'rail', railFollow: railFollowForNewGeometry(shot.camera?.railFollow, shot.endFrame - shot.startFrame + 1),
124
- }) })),
155
+ 'shot.setCameraRail': args => setRail(args),
125
156
  'shot.clearCameraRail': ({ shotId }) => {
126
157
  const shot = shotOf(shotId);
127
158
  if (!createCameraBlock(shot.camera).cameraRail) fail('TARGET_NOT_READY', `${shot.name || shotId} has no camera rail.`);
@@ -159,14 +190,12 @@ export function register(registry, ports) {
159
190
  };
160
191
  registerElementSet(registry, ports, extra[0]);
161
192
  for (const declaration of declarations.filter(row => row.id !== 'shot.set')) registry.register({ ...declaration,
193
+ ...(declaration.id === 'shot.setRange' ? { normalizeArgs: lenientRange } : {}),
162
194
  available: state => mounted() === true ? (availability[declaration.id] ?? (existing.includes(declaration) ? hasShots : () => true))(state) : mounted(),
163
195
  run(args, context) {
164
- if (declaration.id === 'shot.frame') {
165
- const plan = owner().frame(args);
166
- return { affectedIds: plan.affectedIds, summary: 'Framed the shot.' };
167
- }
196
+ if (declaration.id === 'shot.frame') return framed(args);
168
197
  const before = owner().read(), newPanel = declaration.id === 'shot.createStill' || (declaration.id === 'shot.create' && storyboard());
169
- methods[declaration.id](args);
198
+ const answered = methods[declaration.id](args), answer = typeof answered?.note === 'string' ? answered : null;
170
199
  // Only the origin travels: never the caption, name or id of the panel.
171
200
  if (newPanel) track('storyboard:panel_created', { source: context?.origin && context.origin !== 'ui' ? 'agent' : 'manual' });
172
201
  // The owner re-normalizes every shot on write, so compare content, not
@@ -174,8 +203,17 @@ export function register(registry, ports) {
174
203
  // names (100), and the first 100 stand for the batch.
175
204
  const after = owner().read(), changed = changedIds(before.map(row => after.find(next => next.id === row.id && same(next, row)) ?? row), after).slice(0, 100);
176
205
  const affectedIds = changed.length ? changed : [args.shotId ?? ports.state().activeSceneId];
177
- return { affectedIds, summary: changed.length ? `${declaration.label}: ${changed.map(id => after.find(row => row.id === id)).filter(Boolean).map(shotLabel).join('; ')}.` : `${declaration.label}.` };
206
+ const summary = changed.length ? `${declaration.label}: ${changed.map(id => after.find(row => row.id === id)).filter(Boolean).map(shotLabel).join('; ')}.` : `${declaration.label}.`;
207
+ return { affectedIds, summary: answer?.note ? `${summary.slice(0, -1)}: ${answer.note}`.slice(0, 240) : summary, ...(answer?.warnings ? { warnings: answer.warnings } : {}) };
178
208
  },
179
209
  });
210
+ // frame_shot answers what it measured, and what its keys do to a move.
211
+ function framed(args) {
212
+ const before = owner().read(), plan = owner().frame(args), shot = owner().read().find(row => row.id === plan.details.shotId);
213
+ const wasRail = createCameraBlock(before.find(row => row.id === shot.id)?.camera).mode === 'rail';
214
+ return { affectedIds: plan.affectedIds, summary: `Framed ${shotLabel(shot)}: ${plan.checks.derivedSize} at frame ${plan.details.frame}, ${shot.cameraKeys.length} camera key${shot.cameraKeys.length === 1 ? '' : 's'}.`.slice(0, 240),
215
+ checks: plan.checks, warnings: [...plan.warnings, ...(wasRail ? [{ code: 'RAIL_REPLACED', id: shot.id, message: 'This shot is back on camera keys; its rail no longer drives it. Re-lay it with shot.setCameraRail.' }] : []),
216
+ ...cameraKeyWarnings(shot)] };
217
+ }
180
218
  if (ports.storeDomain?.('shot')) registry.registerToolAlias('frame_shot', 'shot.frame');
181
219
  }
@@ -1,17 +1,30 @@
1
1
  import { elementSetSchema, registerElementSet } from './elements.js';
2
+ import { normalizeStage } from './elements/stage.js';
3
+ import { applyLightingRecipe, LIGHTING_KEEP_PATHS, LIGHTING_PRESETS } from '../stage-lighting.js';
2
4
  export { STAGE_FIELDS, normalizeStage } from './elements/stage.js';
3
5
 
4
6
  const input = elementSetSchema('stage');
5
7
  const declaration = (id, properties = input.properties) => ({ id, label: 'Set stage', kind: 'mutation', undoDomain: 'stage',
6
8
  description: 'Update authored stage fields, normalized by scene persistence.',
7
9
  input: { ...input, properties } });
10
+ const applyLighting = { id: 'stage.applyLighting', label: 'Apply lighting', kind: 'mutation', undoDomain: 'stage',
11
+ description: 'Give the stage a whole lighting look in one undoable step: writes every stage.lighting field (sky, skyTop, ambient, hemiSky, hemiGround, fill, fog, exposure, shadowSoftness, envLight, ao) and the key light\'s colour, intensity and position from the recipe, then sets lighting.preset to its name. studio is the neutral grey default; day is a high sun under a blue sky; overcast is soft, shadowless grey daylight; golden a low warm sun with long shadows; dusk a dim warm horizon glow under a blue sky; night a dark blue-black sky, a dim cool moon key with soft shadows, low cool ambient and haze; interior a warm tungsten lamp in a dark room; stage a black void lit by the key alone. Every recipe but studio lights the set with a sky environment map (envLight 1) and darkens contacts and creases (ao 0.6 to 1); studio keeps both at 0. keep lists paths left as they are, e.g. ["keyLight.position"] to keep where the light stands; a recipe also clears an HDR environment (stage.lighting.hdr) unless keep has "lighting.hdr". Tweak any value afterwards with stage.set; check the result with verify_result checks ["lighting"].',
12
+ input: { type: 'object', properties: { preset: { type: 'string', enum: [...LIGHTING_PRESETS] },
13
+ keep: { type: 'array', items: { type: 'string', enum: [...LIGHTING_KEEP_PATHS] }, minItems: 1, maxItems: LIGHTING_KEEP_PATHS.length, uniqueItems: true } }, required: ['preset'], additionalProperties: false } };
8
14
  export const declarations = Object.freeze([
9
15
  declaration('stage.set'),
10
16
  declaration('stage.setKeyLight', { keyLight: input.properties.keyLight }),
11
17
  declaration('stage.setEnvironment', Object.fromEntries(['environment', 'environmentImage', 'hasEnvSheet'].map(key => [key, input.properties[key]]))),
12
18
  declaration('stage.setStyle', { style: input.properties.style }),
13
19
  declaration('stage.setFilmback', Object.fromEntries(['shotAspect', 'cameraPresetId', 'sensorId'].filter(key => input.properties[key]).map(key => [key, input.properties[key]]))),
20
+ applyLighting,
14
21
  ]);
15
22
  export function register(registry, ports) {
16
- for (const declaration of declarations) registerElementSet(registry, ports, declaration);
23
+ for (const declaration of declarations.filter(entry => entry !== applyLighting)) registerElementSet(registry, ports, declaration);
24
+ registry.register({ ...applyLighting, available: () => true, run({ preset, keep = [] }) {
25
+ const domain = ports.storeDomain('stage'), before = domain.read();
26
+ const next = normalizeStage({ ...before, ...applyLightingRecipe(before, preset, keep) });
27
+ if (JSON.stringify(next) !== JSON.stringify(before)) domain.write(next);
28
+ return { affectedIds: [ports.state().activeSceneId], summary: `Applied the ${preset} lighting recipe${keep.length ? `, keeping ${keep.join(', ')}` : ''}.` };
29
+ } });
17
30
  }
@@ -4,6 +4,7 @@ import { STUDIO_TOOL_SCHEMAS } from '../studio-agent-protocol.js';
4
4
  import { ko } from '../locale.js';
5
5
  import { shotAtFrame } from '../cuts.js';
6
6
  import { fail } from './shared.js';
7
+ import { describeGhosts, setGhostSettings } from '../view-ghosts.js';
7
8
 
8
9
  const fields = STUDIO_TOOL_SCHEMAS.operate_studio.properties;
9
10
  const groups = { 'view.select': ['selection', 'shotId'], 'timeline.seek': ['frame'], 'timeline.play': ['playing'], 'view.setMode': ['mode', 'view'] };
@@ -11,7 +12,7 @@ const operations = Object.entries({ ...groups, 'view.update': Object.keys(fields
11
12
  id, label: id, description: 'Change selection, timeline or viewer state without authoring history.', kind: 'transient',
12
13
  input: { type: 'object', properties: Object.fromEntries(keys.map(key => [key, fields[key]])), required: [], additionalProperties: false },
13
14
  }));
14
- export const declarations = Object.freeze([...operations, ...["view.setPartColours", "view.setGuideMode", "view.setInset"].map(studioActionDeclaration)]);
15
+ export const declarations = Object.freeze([...operations, ...["view.setPartColours", "view.setGuideMode", "view.setInset", "view.setGhosts"].map(studioActionDeclaration)]);
15
16
  // A storyboard project has no Motion department; every door says so the same way.
16
17
  export const STORYBOARD_MOTION_REFUSAL = Object.freeze({ en: 'Motion tools are not part of a Storyboard project.', ko: '모션 도구는 스토리보드 프로젝트에 없어요.' });
17
18
  export const viewCommand = args => Object.entries(groups).find(([, keys]) => Object.keys(args).every(key => keys.includes(key)))?.[0] ?? 'view.update';
@@ -45,4 +46,6 @@ export function register(registry, ports) {
45
46
  viewAction("view.setPartColours", ({ mode }) => { ports.choosePartColours(mode); return `Part colours: ${mode}.`; });
46
47
  viewAction("view.setGuideMode", ({ mode }) => { ports.setGuideMode(mode); return `Composition guide: ${mode}.`; });
47
48
  viewAction("view.setInset", ({ collapsed }) => { ports.setInsetCollapsed(collapsed); return `Top-View inset ${collapsed ? "folded" : "unfolded"}.`; });
49
+ // The ghosts' setting lives with them (view-ghosts.js), not in the App: no port to reach.
50
+ viewAction("view.setGhosts", args => describeGhosts(setGhostSettings(args)));
48
51
  }
@@ -0,0 +1,42 @@
1
+ // The active scene's shot document and stage envelope, read from the stores at
2
+ // the moment they are used. App used to rebuild both during render, so a
3
+ // synchronous command read them one render behind: the Studio binding folds
4
+ // them into the scene revision (stage and scenes documents), the receipt
5
+ // answered revision N, and the next render moved the scene to N+1 by itself
6
+ // when the mirrors caught up. An agent's next write, admitted at the receipt's
7
+ // revision.after (a motion install, a cast move, a shot edit), then met
8
+ // STALE_SCENE although nothing had changed.
9
+ import { createShotAuthoringDocument } from "./shot-authoring.js";
10
+ import { savedTakeSource } from "./domains/motion/take-source.js";
11
+
12
+ // IK edits (Pose mode, IK keys, range pins) ride into the saved stage with each character.
13
+ const ikEditsOf = row => (!row || (!row.ikKeys?.length && !row.ikPins?.length) ? null : { keys: row.ikKeys, pins: row.ikPins ?? [], pinResiduals: row.ikPinResiduals ?? [] });
14
+
15
+ /** Ref-shaped mirrors (`.current`) for the scene snapshot and the stage domain.
16
+ * `actorStageRef.current` keeps the envelope App (or the stage domain) last set
17
+ * and always answers the cast as it is now: sessionMotion is stripped (baked
18
+ * takes are session-only and far too heavy for the stage envelope); a take v3
19
+ * persists as its source (spec, placement). */
20
+ export function createDocumentMirrors(appContext) {
21
+ const cast = () => appContext.storeDomain("cast")?.projection() ?? appContext.live.characters ?? [];
22
+ const activeWaypoints = list => (list.find(entry => entry.id === appContext.storeDomain("cast")?.activeId) ?? list[0])?.layer?.waypoints ?? [];
23
+ const shotDocumentRef = {
24
+ get current() {
25
+ const shots = appContext.storeDomain("shot");
26
+ return createShotAuthoringDocument({ shots: shots?.read() ?? [], waypoints: activeWaypoints(cast()), frameCount: shots?.state().frameCount ?? null });
27
+ },
28
+ };
29
+ let envelope = null;
30
+ const stageCharacters = () => {
31
+ const layers = appContext.storeDomain("motion")?.read() ?? null;
32
+ return cast().map(({ sessionMotion, ...entry }) => {
33
+ const row = layers?.find(layer => layer.id === entry.id);
34
+ return { ...entry, ikEdits: ikEditsOf(row), motionTake: savedTakeSource(row?.take) };
35
+ });
36
+ };
37
+ const actorStageRef = {
38
+ get current() { return envelope && { ...envelope, characters: stageCharacters() }; },
39
+ set current(value) { envelope = value; },
40
+ };
41
+ return { shotDocumentRef, actorStageRef };
42
+ }
@@ -38,6 +38,10 @@ export function createDocumentStore({ owned = {}, dev = import.meta.env?.DEV ??
38
38
  run(fn) { check(); running++; try { return fn(); } finally { running--; } },
39
39
  touch(domain) { check(); requireOwner(domain); },
40
40
  update(domain, update) { return session.run(() => write(domain, update)); },
41
+ // A point inside the open transaction (a script's start in an agent turn): rollback(mark)
42
+ // restores it and the transaction stays open; rollback() restores where it began.
43
+ mark() { check(); return slices; },
44
+ rollback(mark = before) { check(); publish(mark); },
41
45
  cancel({ restore = true } = {}) {
42
46
  if (active !== session) return false;
43
47
  active = null;