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
@@ -31,15 +31,13 @@ const entries = [
31
31
  { path: "character.identityImage", type: "image", persisted: true, undoDomain: "cast", normalizer: "createCharacterEntry", note: "data:image only" },
32
32
  { path: "character.pose", type: "id", persisted: true, undoDomain: "cast", normalizer: "createCharacterEntry" },
33
33
  { path: "character.waypoints", type: "array", persisted: true, undoDomain: "cast", normalizer: "createCharacterEntry", actions: ["character.addWaypoint", "character.moveWaypoint", "character.removeWaypoint", "character.clearWaypoints"], note: "root path pins, addressed by frame" },
34
- { path: "character.promptBlocks", type: "array", persisted: true, undoDomain: "cast", normalizer: "createCharacterEntry", note: "stored at layer.promptClips" },
35
- { path: "character.motionRef.url", type: "string", persisted: true, undoDomain: "cast", normalizer: "createCharacterEntry" },
36
- { path: "character.motionRef.motionId", type: "id", persisted: true, undoDomain: "cast", normalizer: "createCharacterEntry" },
34
+
37
35
  { path: "character.sessionMotion", type: "array", persisted: false, undoDomain: "cast", normalizer: "createCharacterEntry", note: "dropped by createCharacterEntry" },
38
36
  { path: "character.ikKeys", type: "array", persisted: false, undoDomain: "cast", normalizer: "createCharacterEntry", actions: ["character.setIkKey", "character.removeIkKey", "character.clearIkKeys"], note: "dropped by createCharacterEntry; keys travel as JSON quaternions/positions" },
39
- { path: "object.renderer", type: "enum", persisted: true, undoDomain: "objects", normalizer: "normalizeSceneObject", enum: ["cube", "sphere", "capsule", "cylinder", "cone", "plane", "chair", "car", "small-plane", "empty"] },
37
+ { path: "object.renderer", type: "enum", persisted: true, undoDomain: "objects", normalizer: "normalizeSceneObject", enum: ["cube", "sphere", "capsule", "cylinder", "cone", "plane", "chair", "car", "small-plane", "empty", "light", "effect"], note: "light is made from catalogue kind point-light or spot-light; effect (smoke, fire, rain, trail...) from kind effect" },
40
38
  { path: "object.position", type: "vec3", persisted: true, undoDomain: "objects", normalizer: "normalizeSceneObject", min: { x: -240, y: 0, z: -240 }, max: { x: 240, y: 240, z: 240 }, note: "y is the object's base: y=0 rests on the floor; height/supportY rise from it" },
41
- { path: "object.rotation", type: "vec3", persisted: true, undoDomain: "objects", normalizer: "normalizeSceneObject", min: { x: -180, y: -180, z: -180 }, max: { x: 180, y: 180, z: 180 }, note: "rot/rotX/rotZ degrees, wrapped at the upper bound" },
42
- { path: "object.scale", type: "vec3", persisted: true, undoDomain: "objects", normalizer: "normalizeSceneObject", min: { x: 0.1, y: 0.1, z: 0.1 }, max: { x: 100, y: 100, z: 100 } },
39
+ { path: "object.rotation", type: "vec3", persisted: true, undoDomain: "objects", normalizer: "normalizeSceneObject", min: { x: -180, y: -180, z: -180 }, max: { x: 180, y: 180, z: 180 }, angle: true, note: "rot/rotX/rotZ degrees, wrapped at the upper bound" },
40
+ { path: "object.scale", type: "vec3", persisted: true, undoDomain: "objects", normalizer: "normalizeSceneObject", min: { x: 0.01, y: 0.01, z: 0.01 }, max: { x: 100, y: 100, z: 100 } },
43
41
  { path: "object.name", type: "string", persisted: true, undoDomain: "objects", normalizer: "normalizeSceneObject" },
44
42
  { path: "object.color", type: "color", persisted: true, undoDomain: "objects", normalizer: "normalizeSceneObject" },
45
43
  { path: "object.opacity", type: "number", persisted: true, undoDomain: "objects", normalizer: "normalizeSceneObject", min: 0.05, max: 1, note: "1 is solid; lower is see-through, e.g. 0.3 for clear glass" },
@@ -48,6 +46,9 @@ const entries = [
48
46
  { path: "object.path", type: "array", persisted: true, undoDomain: "objects", normalizer: "normalizeSceneObject" },
49
47
  { path: "object.remove", type: "boolean", persisted: true, undoDomain: "objects", normalizer: null, note: "lifecycle operation, not a document field" },
50
48
  { path: "object.cutout", type: "image", persisted: true, undoDomain: "objects", normalizer: "normalizeSceneObject", actions: ["asset.import"], note: "assetId-backed cutout record; imported pictures and models are placed by asset.import" },
49
+ { path: "object.light", type: "object", persisted: true, undoDomain: "objects", normalizer: "normalizeSceneObject", note: "renderer light only. { type point|spot, color, intensity 0..1000 candela (lighter 0.45, candle 0.3, desk lamp 5, street lamp 40), range m (0 none), angle 1..89 deg spot half-angle, softness 0..1, shadow (first 2 lights only; inside a solid record it is blocked: receipt LIGHT_BLOCKED), flicker 0..1 (lighter 0.6), aim {x,y,z} world point or null = local -Y }, e.g. { intensity: 40, aim: { x: 0, y: 0, z: 1 } }; a take level track scales it" },
50
+ { path: "object.glow", type: "object", persisted: true, undoDomain: "objects", normalizer: "normalizeSceneObject", note: "any record: self-lit colour plus additive halo, same under any stage light; casts no shadow, so a light inside a glowing bulb shines out. { color, intensity 0..20, halo 0..4 x the record's radius }, e.g. ember { color: \"#ff5a1f\", intensity: 6, halo: 2 }; null removes; a take level track scales it" },
51
+ { path: "object.effect", type: "object", persisted: true, undoDomain: "objects", normalizer: "normalizeSceneObject", note: "renderer effect only (object.add kind \"effect\"). { preset: smoke|exhale|steam|fire|sparks|dust|rain|snow|trail, rate particles/s, life s, size m start diameter, grow end/start size, speed m/s along the record's local +Y (rotX 90 aims +Z), spread 0..1 cone, gravity m/s2 (+ falls, - rises), wind {x,y,z} m/s, color, opacity 0..1, additive (emissive: fire, sparks), seed int, area {x,y,z} m birth box (rain/snow/dust fall through it from its top), width m trail ribbon across local Y }. A new preset loads its defaults and fields in the same set override them, e.g. { preset: \"fire\", rate: 40 }; the record stores the merged numbers. Particles are born where the record is at each frame (route, parent, take carry), only while it and its parents are shown, at rate x the take level. To emit from the body (a breath at the mouth), a motion spec entry objects.<id> = { follow: \"landmark:mouth\", aim: \"face\" } moves it there every frame. Measure with inspect_studio { scope: \"entities\", ids }: effect { frame (the playhead), alive, emitter [x,y,z], bounds { min, max } m }" },
51
52
  { path: "stage.keyLight.x", type: "number", persisted: true, undoDomain: "stage", normalizer: "createSceneStage", min: -30, max: 30 },
52
53
  { path: "stage.keyLight.y", type: "number", persisted: true, undoDomain: "stage", normalizer: "createSceneStage", min: 0.5, max: 30 },
53
54
  { path: "stage.keyLight.z", type: "number", persisted: true, undoDomain: "stage", normalizer: "createSceneStage", min: -30, max: 30 },
@@ -60,6 +61,25 @@ const entries = [
60
61
  { path: "stage.camera", documentPath: "shotAspect", type: "enum", persisted: true, undoDomain: "stage", normalizer: "createSceneStage", enum: ["16:9", "2.39:1", "9:16", "1:1", "4:3", "12:7", "fal 480P"], note: "output aspect ratio" },
61
62
  { path: "stage.cameraPresetId", type: "id", nullable: true, persisted: true, undoDomain: "stage", normalizer: "createSceneStage" },
62
63
  { path: "stage.sensorId", type: "enum", persisted: true, undoDomain: "stage", normalizer: "createSceneStage", enum: ["super16", "super35", "fullFrame", "65mm"] },
64
+ // The stage look (src/stage-lighting.js). stage.applyLighting writes all of it from a recipe; each field stays tweakable.
65
+ { path: "stage.keyLight.color", type: "color", nullable: true, persisted: true, undoDomain: "stage", normalizer: "createSceneStage", note: "key light colour #rrggbb, overrides warmth, e.g. \"#9db6e6\" cool moonlight or \"#ffad5c\" low sun; null = white tinted by warmth" },
66
+ { path: "stage.lighting.preset", type: "enum", persisted: true, readOnly: true, undoDomain: "stage", normalizer: "createSceneStage", enum: ["studio", "day", "overcast", "golden", "dusk", "night", "interior", "stage"], note: "label of the last recipe stage.applyLighting applied; studio is the neutral grey default" },
67
+ { path: "stage.lighting.sky", type: "color", nullable: true, persisted: true, undoDomain: "stage", normalizer: "createSceneStage", note: "background and fog colour at the horizon, e.g. \"#0b1426\" night; null = the UI theme's studio grey" },
68
+ { path: "stage.lighting.skyTop", type: "color", nullable: true, persisted: true, undoDomain: "stage", normalizer: "createSceneStage", note: "sky colour overhead: shot camera and exports grade from sky at the horizon to this, e.g. \"#020408\"; null = flat sky" },
69
+ { path: "stage.lighting.ambient", type: "number", persisted: true, undoDomain: "stage", normalizer: "createSceneStage", min: 0, max: 3, note: "multiplier on the hemisphere and ambient light; 1 = studio, 0.6 = night, 0 = black stage" },
70
+ { path: "stage.lighting.hemiSky", type: "color", persisted: true, undoDomain: "stage", normalizer: "createSceneStage", note: "hemisphere light from above, e.g. \"#34507f\" cool night sky; studio \"#ffffff\"" },
71
+ { path: "stage.lighting.hemiGround", type: "color", persisted: true, undoDomain: "stage", normalizer: "createSceneStage", note: "hemisphere bounce from below, e.g. \"#6e5136\" warm ground; studio \"#5a5d64\"" },
72
+ { path: "stage.lighting.fill", type: "number", persisted: true, undoDomain: "stage", normalizer: "createSceneStage", min: 0, max: 3, note: "multiplier on the fixed fill and rim lights; 1 = studio, 0 = key only" },
73
+ { path: "stage.lighting.fog", type: "number", persisted: true, undoDomain: "stage", normalizer: "createSceneStage", min: 0, max: 1, note: "haze 0..1 fading the set into the sky colour: 0 = far horizon only, 0.45 = from ~18 m to ~38 m, 1 = from 2 m to 14 m" },
74
+ { path: "stage.lighting.exposure", type: "number", persisted: true, undoDomain: "stage", normalizer: "createSceneStage", min: 0.2, max: 3, note: "camera exposure multiplier on lit surfaces; 1 = studio, e.g. 1.4 to lift a dark frame" },
75
+ { path: "stage.lighting.shadowSoftness", type: "number", persisted: true, undoDomain: "stage", normalizer: "createSceneStage", min: 0, max: 1, note: "key shadow penumbra; 0 = crisp (studio), 0.75 = soft moonlight, 1 = overcast" },
76
+ { path: "stage.lighting.envLight", type: "number", persisted: true, undoDomain: "stage", normalizer: "createSceneStage", min: 0, max: 2, note: "sky environment light on every surface: a map from sky, skyTop, hemiGround and a glow toward the key (sun or moon) tints and fills clay from all sides; it takes over the flat hemisphere as it rises. 0 = hemisphere only (studio), 1 = the map alone at the same brightness (every other recipe), e.g. 1.4 for a brighter fill" },
77
+ { path: "stage.lighting.ao", type: "number", persisted: true, undoDomain: "stage", normalizer: "createSceneStage", min: 0, max: 1, note: "ambient occlusion strength: contact darkening within ~0.5 m where a body meets the floor or a prop and in creases (armpits, the back of a squat's knees), in the viewport and every export. 0 = off (studio), 0.8 = most recipes, 1 = overcast" },
78
+ // An HDR environment map (src/stage-hdr.js): the light and, with hdrBackground, the backdrop.
79
+ { path: "stage.lighting.hdr", type: "id", nullable: true, persisted: true, undoDomain: "stage", normalizer: "createSceneStage", note: "HDR environment map that replaces the sky environment light: a built-in (\"builtin:night-street\", \"builtin:overcast-field\", \"builtin:studio-softbox\") or a file the user loaded (\"hdr-<32 hex>\"); inspect_studio { scope: \"catalogue\" } environments lists them with each map's brightest direction. It lights the set only while envLight > 0 (its light = map x hdrIntensity x envLight; studio has envLight 0, so set envLight 1). stage.applyLighting clears it unless keep has \"lighting.hdr\". null = the look's own sky" },
80
+ { path: "stage.lighting.hdrRotation", type: "number", persisted: true, undoDomain: "stage", normalizer: "createSceneStage", min: -180, max: 180, angle: true, note: "degrees the HDR map turns about the vertical axis, light and backdrop together: +90 carries a light at +x (screen right from the default camera) to -z (behind the subject), so a brightest [x, y, z] lands at [x cos r + z sin r, y, z cos r - x sin r]; wrapped at the upper bound" },
81
+ { path: "stage.lighting.hdrIntensity", type: "number", persisted: true, undoDomain: "stage", normalizer: "createSceneStage", min: 0, max: 4, note: "multiplier on the HDR map's radiance in its light and its backdrop; 1 = the file as authored, e.g. 0.5 to calm a bright daylight map" },
82
+ { path: "stage.lighting.hdrBackground", type: "boolean", persisted: true, undoDomain: "stage", normalizer: "createSceneStage", note: "true = the HDR map is the backdrop behind the set (the floor fades into its horizon), in the viewport, shot and every export; false = keep the look's sky as the backdrop and use the map only as light" },
63
83
  { path: "shot.crud", type: "array", persisted: true, undoDomain: "shot", normalizer: null, actions: ["shot.create", "shot.split", "shot.duplicate", "shot.remove", "shot.setRange", "shot.reorder"], note: "create/split/duplicate/reorder/remove/range through the shared action registry" },
64
84
  { path: "shot.cameraKeys", type: "array", persisted: true, undoDomain: "shot", normalizer: null, frameMin: 0 },
65
85
  { path: "shot.cameraRail", type: "array", persisted: true, undoDomain: "shot", normalizer: "repairCamera", actions: ["shot.setCameraRail", "shot.clearCameraRail"], note: "rail points; crane/dolly timing follow the shot's camera block" },
@@ -74,7 +94,7 @@ const entries = [
74
94
  { path: "selection", type: "id", persisted: false, undoDomain: null, normalizer: null },
75
95
  { path: "timeline", type: "number", persisted: false, undoDomain: null, normalizer: null },
76
96
  { path: "view.mode", type: "enum", persisted: false, undoDomain: null, normalizer: null, enum: ["scene", "pose", "camera", "motion"] },
77
- { path: "view.partColoursGuideModeInset", type: "array", persisted: false, undoDomain: null, normalizer: null, actions: ["view.setPartColours", "view.setGuideMode", "view.setInset"], note: "partColours/guideMode/inset; viewer settings, not document fields" },
97
+ { path: "view.partColoursGuideModeInset", type: "array", persisted: false, undoDomain: null, normalizer: null, actions: ["view.setPartColours", "view.setGuideMode", "view.setInset", "view.setGhosts"], note: "partColours/guideMode/inset/ghosts; viewer settings, not document fields" },
78
98
  { path: "read.sceneDescription", type: "string", persisted: false, undoDomain: null, normalizer: null },
79
99
  { path: "read.captureFrame", type: "image", persisted: false, undoDomain: null, normalizer: null },
80
100
  { path: "undo", type: "boolean", persisted: false, undoDomain: null, normalizer: null },
@@ -0,0 +1,18 @@
1
+ // The world box an object row reports, in inspect_studio entities, the per-turn context and an
2
+ // action readback alike: a body's own box at the playhead (entityWorldBounds, metres, 4 decimals);
3
+ // for a node with no body (an empty or a light) the union of every body under it, so a group
4
+ // built from parts shows where the whole thing ends; null when nothing there can be measured.
5
+ import { entityWorldBounds } from "./studio-agent-commands.js";
6
+ import { descendantsOf, isEmptyObject } from "./scene-objects.js";
7
+
8
+ const AXES = ["x", "y", "z"];
9
+
10
+ /** World AABB {min,max} of scene object `object` in refreshed Studio state `s`, or null. */
11
+ export function objectRowBounds(object, s, ports) {
12
+ const state = { ...s, frame: s.view.frame }, measure = o => entityWorldBounds(o, state, { bounds: ports.bounds });
13
+ if (!isEmptyObject(object)) return measure(object);
14
+ const boxes = descendantsOf(s.objects, object.id).filter(o => !isEmptyObject(o)).map(measure).filter(Boolean);
15
+ if (!boxes.length) return null;
16
+ return { min: Object.fromEntries(AXES.map(a => [a, Math.min(...boxes.map(b => b.min[a]))])),
17
+ max: Object.fromEntries(AXES.map(a => [a, Math.max(...boxes.map(b => b.max[a]))])) };
18
+ }
@@ -0,0 +1,8 @@
1
+ // The error codes a Studio receipt or failure may carry. Shared by the browser
2
+ // and sidecar; no imports.
3
+ export const STUDIO_ERROR_CODES = Object.freeze([
4
+ "INVALID_ARGUMENT", "INVALID_CONTEXT", "INVALID_IDENTITY", "INVALID_TURN_ID", "INVALID_SESSION_ID", "INVALID_RECEIPT", "INVALID_RANGE", "INVALID_REQUEST",
5
+ "UNKNOWN_TOOL", "UNKNOWN_VARIANT", "DUPLICATE_NAME", "CONTEXT_LIMIT", "CONTEXT_TOO_LARGE", "AMBIGUOUS_TARGET", "AMBIGUOUS_BASIS", "TARGET_NOT_READY", "TARGET_BUSY",
6
+ "STALE_TARGET", "STALE_SCENE", "STALE_ENVIRONMENT", "STALE_CURSOR", "CAPABILITY_MISSING", "LIVE_HUB_UNAVAILABLE", "AUTH_REQUIRED", "GENERATION_LIMIT", "RATE_LIMITED", "BACKEND_UNAVAILABLE",
7
+ "VERIFICATION_FAILED", "REPAIR_REGRESSED", "CANCELLED", "UNCERTAIN_APPLY", "UNDO_CONFLICT", "CONFIRMATION_REQUIRED", "TIMEOUT", "UNDO_EXPIRED",
8
+ ]);
@@ -0,0 +1,152 @@
1
+ // What the export camera shows of a take (verify_result motion, exportFraming): at each sampled frame
2
+ // the camera the export renders there (ports.sampleStateAt: the shot's camera where a shot covers the
3
+ // frame, else the Studio camera as it stands), and through it the character's joints (as motion.sample
4
+ // reads them) and the take's objects (moved by the take's tracks, object-travel.js), projected as
5
+ // frame_shot projects a subject (studio-agent-commands.js projectSubject). Measurements, not verdicts:
6
+ // how much of the character and of each object is inside the frame, and over which sampled frames a
7
+ // part (head, hands, feet) or an object (the outline of each of its bodies) is cut or out. Frames are
8
+ // absolute timeline frames; a span joins consecutive samples ([first, last] sampled frame).
9
+ import { Euler, Vector3 } from "three";
10
+ import { StudioProtocolError } from "./studio-agent-protocol.js";
11
+ import { projectSubject, entityWorldBounds } from "./studio-agent-commands.js";
12
+ import { focalMmToFov, SENSOR_FORMATS } from "./shot.js";
13
+ import { shotAtFrame } from "./cuts.js";
14
+ import { sceneObjectsAt } from "./object-travel.js";
15
+ import { descendantsOf, isEmptyObject } from "./scene-objects.js";
16
+ import { skeletonFor } from "./motion3/rigs/index.js";
17
+ import { fk } from "./motion3/compiler/skeleton.js";
18
+ import { add, apply, ry } from "./motion3/compiler/math.js";
19
+
20
+ const SAMPLES = 24, TAKE_SCHEMA = "cozyclay.take.v3", BARE_OBJECT = /^object:(.+?)(\.(?:center|top|base))?$/s;
21
+ const PARTS = Object.freeze({ head: ["Head", "HeadTop_End"], hand_l: ["LeftHand", "LeftHandMiddle4", "LeftHandIndex4"], hand_r: ["RightHand", "RightHandMiddle4", "RightHandIndex4"],
22
+ foot_l: ["LeftFoot", "LeftToe_End"], foot_r: ["RightFoot", "RightToe_End"] });
23
+ const r2 = v => Math.round(v * 100) / 100;
24
+
25
+ /** Up to n frames spread evenly over [lo, hi], both ends included. */
26
+ export function exportSampleFrames(lo, hi, n = SAMPLES) {
27
+ if (hi <= lo) return [lo];
28
+ const count = Math.min(n, hi - lo + 1);
29
+ return [...new Set(Array.from({ length: count }, (_, i) => Math.round(lo + (i * (hi - lo)) / (count - 1))))];
30
+ }
31
+
32
+ /** [[first, last], ...] of the sampled frames whose flag is set, consecutive samples joined. */
33
+ export function sampleSpans(frames, flags) {
34
+ const spans = [];
35
+ frames.forEach((frame, i) => {
36
+ if (!flags[i]) return;
37
+ if (i > 0 && flags[i - 1]) spans.at(-1)[1] = frame; else spans.push([frame, frame]);
38
+ });
39
+ return spans;
40
+ }
41
+
42
+ /** World joint positions of a v3 take at timeline frame f, in take bone order (motion.sample's reading). */
43
+ function jointsAt(sk, take, f) {
44
+ const B = take.bones.length, ff = Math.max(0, Math.min(take.frameCount - 1, Math.round(f)));
45
+ const quats = Array.from({ length: B }, (_, b) => Array.from(take.rot.subarray((ff * B + b) * 4, (ff * B + b) * 4 + 4)));
46
+ const state = fk(sk, quats, Array.from(take.hips.subarray(ff * 3, ff * 3 + 3)));
47
+ const yaw = ry(take.placement?.yaw ?? 0), at = take.placement?.position ?? [0, 0, 0];
48
+ return state.P.map(p => add(at, apply(yaw, p)));
49
+ }
50
+
51
+ /** Scene object ids a spec names: spec.objects keys, object:<id> refs and any string value that is an object id. */
52
+ export function takeObjectIds(spec, objects) {
53
+ const known = new Set(objects.map(o => o.id)), ids = new Set(Object.keys(spec?.objects ?? {}).filter(id => known.has(id)));
54
+ const walk = value => {
55
+ if (typeof value === "string") { const id = BARE_OBJECT.exec(value)?.[1] ?? value; if (known.has(id)) ids.add(id); }
56
+ else if (Array.isArray(value)) value.forEach(walk);
57
+ else if (value && typeof value === "object") Object.values(value).forEach(walk);
58
+ };
59
+ walk(spec);
60
+ return [...ids];
61
+ }
62
+
63
+ /** How a set of world points lands in the camera: the projection and how much of its screen box is inside the frame. */
64
+ function landing(points, view) {
65
+ const forward = new Vector3().subVectors(view.lookAt, view.position);
66
+ const behind = points.filter(p => p.clone().sub(view.position).dot(forward) <= 0).length;
67
+ if (behind === points.length) return { behind: true, cut: true, fraction: 0, screenFraction: 0 };
68
+ const { screenBounds: b, checks } = projectSubject(points, view.camera, view.fov, view.aspect);
69
+ if (behind) return { behind: true, cut: true, fraction: null, screenFraction: checks.screenFraction };
70
+ // the share of each screen axis inside [-1, 1]; a box flat along an axis is in or out whole
71
+ const share = ({ min, max }) => max - min > 1e-6 ? Math.max(0, Math.min(1, max) - Math.max(-1, min)) / (max - min) : Math.abs(min) <= 1 ? 1 : 0;
72
+ return { behind: false, cut: checks.clipped, fraction: share(b.x) * share(b.y), screenFraction: checks.screenFraction };
73
+ }
74
+
75
+ const corners = box => [box.min.x, box.max.x].flatMap(x => [box.min.y, box.max.y].flatMap(y => [box.min.z, box.max.z].map(z => new Vector3(x, y, z))));
76
+ const RING = Array.from({ length: 8 }, (_, i) => [Math.cos(i * Math.PI / 4), Math.sin(i * Math.PI / 4)]);
77
+ // A body's outline in its own frame (base pivot, local +Y up, 1 m primitives scaled): rims for the round
78
+ // primitives, so a disc seen face on is not measured by the corners of its box; box corners otherwise.
79
+ const OUTLINES = { cylinder: [[0, 1], [1, 1]], cone: [[0, 1], [1, 0]], sphere: [[0, 0], [0.5, 1], [1, 0]], capsule: [[0, 0], [0.25, 1], [0.75, 1], [1, 0]] };
80
+ /** World points outlining a body as drawn at its posed record: the same placement geometry() uses for a box. */
81
+ function bodyPoints(object, state, ports) {
82
+ const outline = OUTLINES[object.renderer];
83
+ if (object.attach || object.path || !object.footprint) { const box = entityWorldBounds(object, state, { bounds: ports.bounds }); return box ? corners(box) : []; }
84
+ const w = object.footprint.width * object.scaleX / 2, d = object.footprint.depth * object.scaleZ / 2, h = object.height * object.scaleY;
85
+ const local = outline ? outline.flatMap(([y, r]) => r ? RING.map(([c, s]) => [w * r * c, y * h, d * r * s]) : [[0, y * h, 0]])
86
+ : [-w, w].flatMap(x => [0, h].flatMap(y => [-d, d].map(z => [x, y, z])));
87
+ const rotation = new Euler((object.rotX ?? 0) * Math.PI / 180, (object.rot ?? 0) * Math.PI / 180, (object.rotZ ?? 0) * Math.PI / 180, "XYZ");
88
+ return local.map(([x, y, z]) => new Vector3(x, y, z).applyEuler(rotation).add(new Vector3(object.x, object.y ?? 0, object.z)));
89
+ }
90
+ const lowest = (rows, frames) => rows.reduce((best, row, i) => row !== null && (best === null || row < best[0]) ? [r2(row), frames[i]] : best, null);
91
+
92
+ /**
93
+ * exportFraming for one character's installed v3 take. scene: the binding's command state (objects,
94
+ * characters, camera, filmback, shotDocument); ports: { sampleStateAt, bounds }; range: verify_result's
95
+ * range ("whole_clip" or { startFrame, endFrameExclusive }).
96
+ */
97
+ export function exportFraming({ character, take, scene, ports, range }) {
98
+ const sensorId = scene.filmback?.sensorId, aspect = scene.filmback?.aspectRatio;
99
+ if (!SENSOR_FORMATS[sensorId] || !(aspect > 0)) throw new StudioProtocolError("TARGET_NOT_READY", "The shot camera and filmback must be available.");
100
+ if (typeof ports.sampleStateAt !== "function") throw new StudioProtocolError("CAPABILITY_MISSING", "This editor cannot sample the export camera at timeline frames.");
101
+ const sk = skeletonFor(take.rig ?? character.model), last = take.frameCount - 1;
102
+ const lo = range && range !== "whole_clip" ? Math.max(0, range.startFrame) : 0, hi = range && range !== "whole_clip" ? Math.min(last, range.endFrameExclusive - 1) : last;
103
+ const frames = exportSampleFrames(lo, hi), shots = scene.shotDocument?.shots ?? [];
104
+ const tracks = new Map(Object.entries(take.objects ?? {}).map(([id, track]) => [id, { frameCount: take.frameCount, characterId: character.id, ...track }]));
105
+ const objectIds = takeObjectIds(take.spec, scene.objects), partIndex = Object.fromEntries(Object.entries(PARTS).map(([part, bones]) => [part, bones.map(b => sk.index[b]).filter(i => i !== undefined)]));
106
+ const shotIds = new Set(), sources = new Set(), body = [], parts = Object.fromEntries(Object.keys(PARTS).map(p => [p, []])), objects = objectIds.map(() => []);
107
+ for (const frame of frames) {
108
+ const sampled = ports.sampleStateAt(frame, scene), camera = sampled?.camera ?? scene.camera;
109
+ if (!camera) throw new StudioProtocolError("TARGET_NOT_READY", "The shot camera must be available.");
110
+ const shot = shotAtFrame(shots, frame);
111
+ if (shot) shotIds.add(shot.id);
112
+ sources.add(shot ? "shot" : "studio");
113
+ const view = { camera, position: new Vector3(camera.position.x, camera.position.y, camera.position.z), lookAt: new Vector3(camera.lookAt.x, camera.lookAt.y, camera.lookAt.z),
114
+ fov: focalMmToFov(camera.focalMm, sensorId, aspect), aspect };
115
+ const joints = jointsAt(sk, take, frame).map(([x, y, z]) => new Vector3(x, y, z));
116
+ body.push(landing(joints, view));
117
+ for (const [part, index] of Object.entries(partIndex)) parts[part].push(landing(index.map(i => joints[i]), view));
118
+ const posed = sceneObjectsAt(scene.objects, frame, { frameCount: take.frameCount, fps: 24, objectTracks: tracks }), state = { ...scene, objects: posed, frame };
119
+ objectIds.forEach((id, k) => {
120
+ const object = posed.find(o => o.id === id), bodies = object && (isEmptyObject(object) ? descendantsOf(posed, id).filter(o => !isEmptyObject(o)) : [object]);
121
+ const points = (bodies ?? []).filter(o => !o.hidden).flatMap(o => bodyPoints(o, state, ports));
122
+ objects[k].push(points.length ? landing(points, view) : null);
123
+ });
124
+ }
125
+ const spans = (rows, key) => sampleSpans(frames, rows.map(row => !!row?.[key]));
126
+ const cut = Object.fromEntries(Object.entries(parts).map(([part, rows]) => [part, spans(rows, "cut")]).filter(([, s]) => s.length));
127
+ const fractions = body.map(row => row.screenFraction);
128
+ return {
129
+ coverage: "sampled-export-camera-joints-and-object-outlines",
130
+ camera: sources.size === 1 ? [...sources][0] : "mixed", shotIds: [...shotIds], sampledFrames: frames,
131
+ character: { id: character.id, screenFraction: [r2(Math.min(...fractions)), r2(Math.max(...fractions))], inFrame: lowest(body.map(row => row.fraction), frames),
132
+ ...(spans(body, "behind").length ? { behindCamera: spans(body, "behind") } : {}), ...(Object.keys(cut).length ? { cut } : {}) },
133
+ objects: objectIds.map((id, k) => {
134
+ const rows = objects[k], out = sampleSpans(frames, rows.map(row => row !== null && row.fraction === 0));
135
+ const partly = sampleSpans(frames, rows.map(row => row !== null && row.cut && row.fraction !== 0));
136
+ return { id, name: scene.objects.find(o => o.id === id)?.name ?? id, inFrame: lowest(rows.map(row => row?.fraction ?? null), frames), ...(out.length ? { out } : {}), ...(partly.length ? { cut: partly } : {}) };
137
+ }),
138
+ };
139
+ }
140
+
141
+ /** verify_result motion: result.exportFraming for every subject character with an installed v3 take (one: the row itself). */
142
+ export function exportFramingInto(result, { args, s, receipt, scene, ports }) {
143
+ const rows = [];
144
+ for (const id of (args.targets ?? receipt?.affectedIds ?? []).filter(id => s.characters.some(c => c.id === id))) {
145
+ const take = s.targets.get(id)?.motion, character = s.characters.find(c => c.id === id);
146
+ if (take?.schema !== TAKE_SCHEMA || !take.rot || !take.hips) continue;
147
+ // what could not be measured says why in its own row, beside the motion check's reasons
148
+ try { rows.push(exportFraming({ character, take, scene, ports, range: args.range })); }
149
+ catch (error) { rows.push({ character: { id }, reason: String(error?.message ?? error) }); }
150
+ }
151
+ if (rows.length) result.exportFraming = rows.length === 1 ? rows[0] : rows;
152
+ }
@@ -0,0 +1,103 @@
1
+ // verify_result checks ["lighting"]: how the shot frame is exposed, measured from
2
+ // the pixels the export path renders (the same frame capture_frame and the
3
+ // contact sheets show), not from the light settings. The agent reads the
4
+ // numbers; it does not have to judge darkness from a thumbnail.
5
+ import { PerspectiveCamera, Vector3 } from "three";
6
+ import { StudioProtocolError } from "./studio-agent-protocol.js";
7
+ import { entityWorldBounds } from "./studio-agent-commands.js";
8
+ import { focalMmToFov } from "./shot.js";
9
+ import { normalizeLighting } from "./stage-lighting.js";
10
+ import { readyHdrMap } from "./stage-hdr-map.js";
11
+
12
+ // Rec. 709 weights on the 8-bit sRGB values: luma as a monitor shows it.
13
+ const lumaOf = (data, i) => 0.2126 * data[i] + 0.7152 * data[i + 1] + 0.0722 * data[i + 2];
14
+ const round = (value, digits) => Number(value.toFixed(digits));
15
+ // A clipped pixel has a channel at full scale; a crushed one is black in every channel.
16
+ const CLIP = 254, CRUSH = 3;
17
+
18
+ /** Percentile of a 256-bin luma histogram, as 0..1. */
19
+ function percentile(histogram, count, fraction) {
20
+ let seen = 0;
21
+ for (let bin = 0; bin < 256; bin++) if ((seen += histogram[bin]) >= fraction * count) return bin / 255;
22
+ return 1;
23
+ }
24
+
25
+ /**
26
+ * Exposure statistics of one RGBA frame (rows bottom-up, as the GPU reads them
27
+ * back) and of pixel boxes inside it: meanLuma 0..1, clippedPct and crushedPct
28
+ * of the whole frame; per box its meanLuma and contrast (the spread between
29
+ * the 5th and 95th luma percentile, 0..1). A box is { id, x0, y0, x1, y1 } in
30
+ * pixels with y up, or { id } alone when the subject is out of frame.
31
+ */
32
+ export function frameLighting({ data, width, height }, boxes = []) {
33
+ let sum = 0, clipped = 0, crushed = 0;
34
+ for (let i = 0; i < width * height * 4; i += 4) {
35
+ sum += lumaOf(data, i);
36
+ const high = Math.max(data[i], data[i + 1], data[i + 2]);
37
+ if (high >= CLIP) clipped++;
38
+ else if (high <= CRUSH) crushed++;
39
+ }
40
+ const pixels = width * height;
41
+ const subjects = boxes.map(({ id, x0, y0, x1, y1 }) => {
42
+ if (x0 === undefined || x1 < x0 || y1 < y0) return { id, meanLuma: null, contrast: null };
43
+ const histogram = new Uint32Array(256);
44
+ let total = 0, count = 0;
45
+ for (let y = y0; y <= y1; y++) for (let x = x0; x <= x1; x++) {
46
+ const luma = lumaOf(data, (y * width + x) * 4);
47
+ histogram[Math.min(255, Math.round(luma))]++;
48
+ total += luma;
49
+ count++;
50
+ }
51
+ return { id, meanLuma: round(total / count / 255, 3), contrast: round(percentile(histogram, count, 0.95) - percentile(histogram, count, 0.05), 3) };
52
+ });
53
+ return { meanLuma: round(sum / pixels / 255, 3), clippedPct: round((100 * clipped) / pixels, 1), crushedPct: round((100 * crushed) / pixels, 1), subjects };
54
+ }
55
+
56
+ /** A world box projected through the shot camera to a clamped pixel box (y up), or null off screen. */
57
+ function pixelBox(bounds, state, width, height) {
58
+ const { camera, filmback } = state, aspect = width / height;
59
+ const view = new PerspectiveCamera((focalMmToFov(camera.focalMm, filmback.sensorId, aspect) * 180) / Math.PI, aspect, 0.01, 10000);
60
+ view.position.set(camera.position.x, camera.position.y, camera.position.z);
61
+ view.lookAt(camera.lookAt.x, camera.lookAt.y, camera.lookAt.z);
62
+ view.updateMatrixWorld(true);
63
+ const forward = new Vector3(camera.lookAt.x - camera.position.x, camera.lookAt.y - camera.position.y, camera.lookAt.z - camera.position.z);
64
+ const corners = [bounds.min.x, bounds.max.x].flatMap((x) => [bounds.min.y, bounds.max.y].flatMap((y) => [bounds.min.z, bounds.max.z].map((z) => new Vector3(x, y, z))))
65
+ .filter((corner) => corner.clone().sub(view.position).dot(forward) > 0).map((corner) => corner.project(view));
66
+ if (!corners.length) return null;
67
+ const px = (ndc, size) => Math.round(((ndc + 1) / 2) * size);
68
+ const box = { x0: px(Math.min(...corners.map((c) => c.x)), width), x1: px(Math.max(...corners.map((c) => c.x)), width) - 1,
69
+ y0: px(Math.min(...corners.map((c) => c.y)), height), y1: px(Math.max(...corners.map((c) => c.y)), height) - 1 };
70
+ const clamped = { x0: Math.max(0, box.x0), x1: Math.min(width - 1, box.x1), y0: Math.max(0, box.y0), y1: Math.min(height - 1, box.y1) };
71
+ return clamped.x1 >= clamped.x0 && clamped.y1 >= clamped.y0 ? clamped : null;
72
+ }
73
+
74
+ /** The lighting check for verify_result: the shot frame at the playhead, its
75
+ * subjects the target characters (every visible one when no target is a
76
+ * character). Fails as TARGET_NOT_READY when nothing can be rendered. */
77
+ export function lightingChecks(ids, state, ports) {
78
+ if (typeof ports.renderFrameBuffer !== "function") throw new StudioProtocolError("CAPABILITY_MISSING", "This editor cannot render the shot frame.");
79
+ if (!state.camera || !(state.camera.focalMm > 0)) throw new StudioProtocolError("TARGET_NOT_READY", "The shot camera must be available.");
80
+ const named = state.characters.filter((character) => ids.includes(character.id));
81
+ const characters = named.length ? named : state.characters.filter((character) => !character.hidden);
82
+ let image;
83
+ try { image = ports.renderFrameBuffer(state.frame); }
84
+ catch (error) { throw new StudioProtocolError("TARGET_NOT_READY", `The shot frame could not be rendered: ${error.message}`); }
85
+ const boxes = characters.map((character) => {
86
+ const bounds = entityWorldBounds(character, state, ports);
87
+ return { id: character.id, ...(bounds && !character.hidden ? pixelBox(bounds, state, image.width, image.height) : {}) };
88
+ });
89
+ // What lit the frame beyond the lights: the look, its environment light and its occlusion pass.
90
+ // An HDR environment is named with whether its map had loaded for this frame (until then the look's sky lit it).
91
+ const { preset, envLight, ao, hdr, hdrRotation, hdrIntensity, hdrBackground } = normalizeLighting(state.stage?.lighting);
92
+ return { coverage: "rendered-shot-frame", frame: state.frame, ...frameLighting(image, boxes),
93
+ look: { preset, envLight, ao, ...(hdr ? { hdr, hdrLoaded: Boolean(readyHdrMap(hdr)), hdrRotation, hdrIntensity, hdrBackground } : {}) } };
94
+ }
95
+
96
+ /** Puts the lighting check on a verify_result answer, or its reason in unsupportedReasons. */
97
+ export function verifyLighting(result, ids, state, ports) {
98
+ try { result.checks = { ...result.checks, lighting: lightingChecks(ids, state, ports) }; }
99
+ catch (error) {
100
+ if (!(error instanceof StudioProtocolError)) throw error;
101
+ result.unsupportedReasons.lighting = error.message;
102
+ }
103
+ }
@@ -0,0 +1,150 @@
1
+ // verify_result's visual: the one image the agent asks to see. "frame" and "plan" are captures;
2
+ // "contact_sheet" tiles frames spread evenly, picked from the installed take's beats
3
+ // (frames: "beats") or named (frames: [f, ...]), seen by the shot camera or by views of its own
4
+ // (views, focus, trail); "trace" plots hand heights and distances of the take over the range (C11).
5
+ // The beats, the views and the trace read one lenient compile of the take's stored spec, as motion.qa does.
6
+ import { StudioProtocolError } from "./studio-agent-protocol.js";
7
+ import { buildContactSheet, paintDots, sampleContactSheetFrames, sheetGrid, viewTileSide } from "./studio-contact-sheet.js";
8
+ import { compileSpec } from "./motion3/pipeline.js";
9
+ import { motionScene } from "./motion3/scene.js";
10
+ import { skeletonFor } from "./motion3/rigs/index.js";
11
+ import { beatFrames, grabReleases, traceSeries, TRACE_COLORS } from "./motion3/beats.js";
12
+ import { depthViews, projectPoint, trailPoints, viewCameras } from "./motion3/views.js";
13
+ import { checkRef } from "./motion3/refs.js";
14
+ import { renderWithGhosts } from "./ghost-set.js";
15
+ import { GHOST_COLORS, sheetGhostSettings } from "./view-ghosts.js";
16
+ import { publishSheet } from "./agent-progress.js";
17
+ import { clampFrames } from "./motion3/lenient.js";
18
+ import { sheetCaption } from "./workflow/agent-progress-view.js";
19
+ // verify_result's lighting check also reads the rendered shot frame; the binding imports it from here.
20
+ export { verifyLighting } from "./studio-lighting-check.js";
21
+
22
+ const TIMELINE_FPS = 24, TRACE_WIDTH = 960, TRACE_HEIGHT = 360, PAD = 12;
23
+ const rgb = hex => [1, 3, 5].map(i => parseInt(hex.slice(i, i + 2), 16));
24
+
25
+ /** [first, last] absolute frames of a verify_result range ("whole_clip" or none: the timeline). */
26
+ const rangeOf = (range, frameCount) => range && range !== "whole_clip" ? [range.startFrame, range.endFrameExclusive - 1] : [0, frameCount - 1];
27
+
28
+ /** The first subject character with a compiled take, compiled again (null when none or it fails). */
29
+ function takeCompile(args, s, receipt) {
30
+ for (const id of args.targets ?? receipt?.affectedIds ?? []) {
31
+ const character = s.characters.find(c => c.id === id), take = s.targets.get(id)?.motion;
32
+ const sk = take?.schema === "cozyclay.take.v3" && take.spec ? skeletonFor(take.rig ?? character?.model) : null;
33
+ if (!character || !sk) continue;
34
+ try {
35
+ return { ...compileSpec({ fps: TIMELINE_FPS, ...take.spec }, { sk, scene: motionScene(s.objects) }, { lenient: true }), characterId: id };
36
+ } catch { return null; }
37
+ }
38
+ return null;
39
+ }
40
+
41
+ /**
42
+ * Line series on a dark plot, as RGBA rows bottom-up (WebGL read-back order, what the encodePng
43
+ * port flips): x is frames lo..hi, y one metre scale for every series. Grey lines mark each second
44
+ * and every 0.1 m; `marks` are frames drawn as light verticals (beat starts, grab, release).
45
+ */
46
+ export function traceRgba(series, [lo, hi], { width = TRACE_WIDTH, height = TRACE_HEIGHT, marks = [] } = {}) {
47
+ const data = new Uint8Array(width * height * 4);
48
+ const put = (x, y, [r, g, b]) => {
49
+ if (x < 0 || y < 0 || x >= width || y >= height) return;
50
+ const i = (y * width + x) * 4; data[i] = r; data[i + 1] = g; data[i + 2] = b; data[i + 3] = 255;
51
+ };
52
+ for (let y = 0; y < height; y++) for (let x = 0; x < width; x++) put(x, y, [22, 24, 29]);
53
+ let bottom = Math.min(...series.map(s => s.min)), top = Math.max(...series.map(s => s.max));
54
+ if (!(top > bottom)) { bottom = (Number.isFinite(bottom) ? bottom : 0) - 0.05; top = bottom + 0.1; }
55
+ const X = f => Math.round(PAD + ((f - lo) / Math.max(1, hi - lo)) * (width - 2 * PAD));
56
+ const Y = v => Math.round(PAD + ((v - bottom) / (top - bottom)) * (height - 2 * PAD));
57
+ for (let m = Math.ceil(bottom * 10); m <= top * 10; m++) for (let x = PAD; x < width - PAD; x++) put(x, Y(m / 10), m === 0 ? [90, 94, 104] : [44, 47, 55]);
58
+ for (let f = Math.ceil(lo / TIMELINE_FPS) * TIMELINE_FPS; f <= hi; f += TIMELINE_FPS) for (let y = PAD; y < height - PAD; y++) put(X(f), y, [44, 47, 55]);
59
+ for (const f of marks) if (f >= lo && f <= hi) for (let y = PAD; y < height - PAD; y++) put(X(f), y, [150, 154, 164]);
60
+ for (const s of series) {
61
+ const color = rgb(s.color);
62
+ for (let i = 1; i < s.values.length; i++) {
63
+ const a = s.values[i - 1], b = s.values[i];
64
+ if (a === null || b === null) continue;
65
+ const x0 = X(lo + i - 1), y0 = Y(a), x1 = X(lo + i), y1 = Y(b), n = Math.max(1, Math.abs(x1 - x0), Math.abs(y1 - y0));
66
+ for (let k = 0; k <= n; k++) {
67
+ const x = Math.round(x0 + ((x1 - x0) * k) / n), y = Math.round(y0 + ((y1 - y0) * k) / n);
68
+ put(x, y, color); put(x + 1, y, color); put(x, y + 1, color); put(x + 1, y + 1, color);
69
+ }
70
+ }
71
+ }
72
+ return { data, width, height };
73
+ }
74
+
75
+ const listOf = value => value === undefined ? [] : [value].flat();
76
+ const round2 = p => p.map(v => Math.round(v * 100) / 100);
77
+
78
+ /**
79
+ * A contact sheet from views of its own (motion3/views.js), a row per view, never the shot
80
+ * camera: each view is placed once for every frame, framed on args.focus (default the whole
81
+ * character) and the trails. A trail is a dot per frame of its ref over the range, so spacing is
82
+ * speed: frames up to the tile's frame solid, later ones faint, the tile's frame ringed. With
83
+ * args.ghosts every tile also draws the take's poses around its frame (ghost-set.js). args.views
84
+ * are the rendered ones (depthViews); `added` names the view added to them and why.
85
+ */
86
+ function viewSheet(args, r, { s, frames, span, grid, picks, ports, added }) {
87
+ if (!r) throw new StudioProtocolError("TARGET_NOT_READY", "views frame a character's compiled take; name a character that has one (run motion.compile first).");
88
+ const focus = listOf(args.focus), refs = listOf(args.trail), ctx = { sk: r.out.ctx.sk, scene: r.out.ctx.scene };
89
+ for (const [field, list] of [["focus", focus], ["trail", refs]]) list.forEach((ref, i) => checkRef(r.expanded, message => { throw new StudioProtocolError("INVALID_ARGUMENT", message); }, ctx, `${field}[${i}]`, ref));
90
+ const trails = refs.map((ref, i) => ({ ref, color: TRACE_COLORS[i], points: trailPoints(r.out, ref, span) }));
91
+ const side = viewTileSide(grid), cameras = viewCameras(r.out, { views: args.views, frames, focus, trails });
92
+ if (!cameras.length) throw new StudioProtocolError("TARGET_NOT_READY", "views found no compiled frame of the take in the range.");
93
+ const cells = cameras.flatMap(cam => frames.map(frame => ({ cam, frame }))), target = args.ghosts ? s.targets.get(r.characterId) : null;
94
+ if (args.ghosts && !target?.rig) throw new StudioProtocolError("TARGET_NOT_READY", "ghosts pose a copy of the character's rig; it has not loaded in the editor yet.");
95
+ const sheet = buildContactSheet(cells, ({ cam, frame }) => {
96
+ const render = () => ports.renderFrameBuffer(frame, { eye: cam.eye, quaternion: cam.quaternion, fovDeg: cam.fovDeg, output: { width: side, height: side } });
97
+ const tile = target ? renderWithGhosts({ rig: target.rig, take: target.motion, objects: s.objects }, frame, render) : render();
98
+ const dots = trails.flatMap(t => t.points.map(({ f, p }) => [f, projectPoint(cam, p, tile.width, tile.height), rgb(t.color)])
99
+ .filter(([, q]) => q).map(([f, [x, y], color]) => ({ x, y, color, r: f === frame ? 4 : 1.5, alpha: f <= frame ? 1 : 0.35, ring: f === frame })));
100
+ return { ...tile, data: paintDots(tile.data, tile.width, tile.height, dots) };
101
+ }, grid);
102
+ const views = cameras.map(cam => ({ name: cam.name, eye: round2(cam.eye), target: round2(cam.target) }));
103
+ const ref = { frames, layout: grid.layout, views, ...(added ? { addedView: added } : {}), focus: focus.length ? focus : "character", ...(trails.length ? { trails: trails.map(({ ref: of, color }) => ({ ref: of, color })) } : {}), ...(picks ? { picks } : {}),
104
+ ...(target ? { ghosts: { ...sheetGhostSettings(), ...GHOST_COLORS } } : {}) };
105
+ return [{ dataUrl: ports.encodePng(sheet.data, sheet), width: sheet.width, height: sheet.height, frames, layout: grid.layout, views: views.map(v => v.name) }, ref];
106
+ }
107
+
108
+ /** Answers args.visual into result.visualRefs and keeps the image for resolve_studio_image. */
109
+ export function studioVisual(args, { s, receipt, result, images, ports }) {
110
+ const keep = (image, ref) => {
111
+ const imageId = crypto.randomUUID();
112
+ images.set(imageId, { ...image, revision: s.revision, receiptId: result.receiptId });
113
+ result.visualRefs.push({ imageId, ...ref });
114
+ // the user sees what the agent looked at (agent-progress.js: the agent panel's progress card)
115
+ const kind = image.kind ?? (args.visual === "contact_sheet" ? "contact sheet" : args.visual);
116
+ publishSheet({ characterId: args.targets?.[0] ?? receipt?.affectedIds?.[0] ?? null, dataUrl: image.dataUrl, width: image.width, height: image.height,
117
+ caption: sheetCaption({ kind, views: image.views ?? [], frames: image.frames ?? [] }), frames: image.frames ?? [] });
118
+ };
119
+ if (args.visual !== "contact_sheet" && (args.views || args.focus || args.trail)) throw new StudioProtocolError("INVALID_ARGUMENT", 'views, focus and trail shape a contact sheet; send visual: "contact_sheet".');
120
+ if (args.ghosts && (args.visual !== "contact_sheet" || !args.views)) throw new StudioProtocolError("INVALID_ARGUMENT", 'ghosts are drawn on a contact sheet from views of its own; send visual: "contact_sheet" with views, e.g. ["side"].');
121
+ if (!args.views && (args.focus || args.trail)) throw new StudioProtocolError("INVALID_ARGUMENT", 'focus and trail are drawn by views of its own; add views, e.g. ["front", "side"] (the shot camera stays the user\'s).');
122
+ if (args.visual === "contact_sheet") {
123
+ // One image of frames across the range, each rendered through the export
124
+ // path, which puts the playhead pose, shot camera and props back.
125
+ const span = rangeOf(args.range, s.frameCount), r = args.frames === "beats" || args.views ? takeCompile(args, s, receipt) : null;
126
+ let frames = sampleContactSheetFrames(args.range, s.frameCount), picks, adjusted;
127
+ // named frames past the range are drawn at its nearest end, and visualRefs[0].adjusted says so (lenient.js)
128
+ if (Array.isArray(args.frames)) ({ frames, adjusted } = clampFrames(args.frames, span));
129
+ else if (args.frames === "beats") ({ frames, picks } = beatFrames({ range: span, even: frames, timeline: r?.timeline, groups: r?.qa.groups, objects: r?.expanded.objects }));
130
+ // Views of its own always include two that look across each other (motion3/views.js depthViews).
131
+ const depth = args.views ? depthViews(args.views) : null, grid = sheetGrid(frames.length, depth?.views.length ?? 0);
132
+ const said = ref => (adjusted ? { ...ref, adjusted } : ref);
133
+ if (depth) { const [image, ref] = viewSheet({ ...args, views: depth.views }, r, { s, frames, span, grid, picks, ports, added: depth.added }); return keep(image, said(ref)); }
134
+ const sheet = buildContactSheet(frames, ports.renderFrameBuffer, grid);
135
+ return keep({ dataUrl: ports.encodePng(sheet.data, sheet), width: sheet.width, height: sheet.height, frames, layout: grid.layout }, said({ frames, layout: grid.layout, ...(picks ? { picks } : {}) }));
136
+ }
137
+ if (args.visual === "trace") {
138
+ const r = takeCompile(args, s, receipt);
139
+ if (!r) throw new StudioProtocolError("TARGET_NOT_READY", "visual trace plots a character's compiled take; name a character that has one (run motion.compile first).");
140
+ const { range, series } = traceSeries(r.out, r.expanded, rangeOf(args.range, s.frameCount));
141
+ const marks = [...(r.timeline ?? []).map(entry => entry.f?.[0]), ...grabReleases(r.timeline, r.expanded.objects).map(row => row.f)];
142
+ const plot = traceRgba(series, range, { marks });
143
+ return keep({ dataUrl: ports.encodePng(plot.data, plot), width: plot.width, height: plot.height, kind: "trace" },
144
+ { kind: "trace", range, series: series.map(({ values, ...summary }) => summary) });
145
+ }
146
+ // A frame is the shot camera's view; a plan is the Top View, the set from above
147
+ // with names and the shot camera's wedge.
148
+ const capture = args.visual === "plan" ? (ports.capturePlan ?? (() => { throw new StudioProtocolError("CAPABILITY_MISSING", "This editor cannot render the top view."); }))() : ports.capture();
149
+ keep(capture, args.visual === "plan" ? { kind: "plan" } : {});
150
+ }
@@ -0,0 +1,80 @@
1
+ // The refusal texts of validateStudioSchema (studio-agent-protocol.js). Every text starts with the
2
+ // path ("$.args.topic") and says what the schema allows there: the allowed values, the allowed
3
+ // fields, the expected type or bounds. What arrived is named by its kind ("got a string"), or by
4
+ // a short preview of a scalar (at most 40 characters), never as a whole payload such as an image.
5
+ // Shared by the browser and the sidecar: no imports.
6
+ const LIST_MAX = 24, PREVIEW_MAX = 40;
7
+
8
+ const preview = value => {
9
+ const text = JSON.stringify(value) ?? String(value);
10
+ return text.length > PREVIEW_MAX ? `${text.slice(0, PREVIEW_MAX - 3)}...` : text;
11
+ };
12
+ const kindOf = value => (value === null ? "null" : Array.isArray(value) ? "an array" : typeof value === "object" ? "an object"
13
+ : typeof value === "number" && !Number.isFinite(value) ? String(value) : `a ${typeof value}`);
14
+ /** A list of names, cut after LIST_MAX with how many there are in all. */
15
+ const listOf = values => (values.length > LIST_MAX ? `${values.slice(0, LIST_MAX).join(", ")}, ... (${values.length} in all)` : values.join(", "));
16
+ const typeName = schema => (Object.hasOwn(schema, "const") ? JSON.stringify(schema.const) : schema.type === "integer" ? "an integer"
17
+ : schema.type === "array" ? `an array${schema.items?.type ? ` of ${schema.items.type}s` : ""}`
18
+ : schema.type === "object" ? "an object" : schema.type === "null" ? "null" : `a ${schema.type}`);
19
+
20
+ /** Wrong type, or a constant that differs: what the schema wants and what arrived. */
21
+ export const wrongType = (path, schema, value) => `${path}: expected ${typeName(schema)}, got ${Object.hasOwn(schema, "const") ? preview(value) : kindOf(value)}.`;
22
+
23
+ export const unknownField = (path, schema, key) => {
24
+ const allowed = Object.keys(schema.properties);
25
+ return `${path} has unknown field ${preview(key)}; ${allowed.length ? `allowed: ${listOf(allowed)}` : "it takes no fields"}.`;
26
+ };
27
+
28
+ export const missingField = (path, key) => `${path}: missing required field "${key}".`;
29
+
30
+ /** An array of the wrong type or length. */
31
+ export function arrayRefusal(path, schema, value) {
32
+ if (!Array.isArray(value)) return wrongType(path, schema, value);
33
+ const lo = schema.minItems ?? 0, hi = schema.maxItems ?? Infinity;
34
+ const want = lo === hi ? `exactly ${lo}` : hi === Infinity ? `at least ${lo}` : lo === 0 ? `at most ${hi}` : `${lo} to ${hi}`;
35
+ return `${path}: expected ${want} item${lo === hi && lo === 1 ? "" : "s"}, got ${value.length}.`;
36
+ }
37
+
38
+ export const duplicateItem = (path, items) => {
39
+ const seen = new Set(), twice = items.map(item => JSON.stringify(item)).find(text => (seen.has(text) ? true : (seen.add(text), false)));
40
+ return `${path}: items must be unique; ${twice && twice.length <= PREVIEW_MAX ? twice : "one item"} appears twice.`;
41
+ };
42
+
43
+ /** A string of the wrong type, empty, too long, or off its pattern. */
44
+ export function stringRefusal(path, schema, value) {
45
+ if (typeof value !== "string") return wrongType(path, schema, value);
46
+ if (!value.trim()) return `${path}: must not be empty.`;
47
+ const length = [...value].length;
48
+ if (schema.maxLength !== undefined && length > schema.maxLength) return schema.description ? `${path}: over ${schema.maxLength} characters. ${schema.description}` : `${path}: at most ${schema.maxLength} characters, got ${length}.`;
49
+ return `${path}: ${preview(value)} does not match ${schema.pattern}.`;
50
+ }
51
+
52
+ export const enumRefusal = (path, schema, value) => `${path}: ${preview(value)} is not one of: ${listOf(schema.enum)}.`;
53
+
54
+ /** A number of the wrong type, not an integer, or not above an exclusive minimum. */
55
+ export function numberRefusal(path, schema, value) {
56
+ if (typeof value !== "number" || !Number.isFinite(value)) return wrongType(path, schema, value);
57
+ if (schema.type === "integer" && !Number.isSafeInteger(value)) return `${path}: expected an integer, got ${value}.`;
58
+ return `${path}: must be greater than ${schema.exclusiveMinimum}, got ${value}.`;
59
+ }
60
+
61
+ const isPlain = schema => ((schema.oneOf ?? schema.anyOf) ? (schema.oneOf ?? schema.anyOf).every(isPlain) : schema.type !== "object" && schema.type !== "array");
62
+ const bounded = schema => [schema.minimum, schema.maximum].some(bound => bound !== undefined) ? ` within [${[schema.minimum ?? "-inf", schema.maximum ?? "inf"].join(", ")}]` : "";
63
+ /** Each value or type a schema admits, in words: true, false, "ifClean", an integer within [1, 8]. */
64
+ const formsOf = schema => ((schema.oneOf ?? schema.anyOf) ? (schema.oneOf ?? schema.anyOf).flatMap(formsOf)
65
+ : Object.hasOwn(schema, "const") ? [JSON.stringify(schema.const)] : schema.enum ? schema.enum.map(v => JSON.stringify(v))
66
+ : schema.type === "boolean" ? ["true", "false"] : [`${typeName(schema)}${schema.type === "number" || schema.type === "integer" ? bounded(schema) : ""}`]);
67
+
68
+ /**
69
+ * A union no branch matched, when every branch failed at the union's own path and the branches are
70
+ * plain values (or the value is not an object): `$.args.install must be true, false or "ifClean",
71
+ * got "yes".` Otherwise null, and the caller surfaces the deepest branch failure.
72
+ */
73
+ export function unionRefusal(path, failures, value) {
74
+ if (!failures.every(({ error }) => error.details?.path === path)) return null;
75
+ if (value !== null && typeof value === "object" && !failures.every(({ branch }) => isPlain(branch))) return null;
76
+ const forms = [...new Set(failures.flatMap(({ branch }) => formsOf(branch)))];
77
+ const shown = forms.length > LIST_MAX ? [...forms.slice(0, LIST_MAX), `... (${forms.length} in all)`] : forms;
78
+ const words = shown.length > 1 ? `${shown.slice(0, -1).join(", ")} or ${shown.at(-1)}` : shown[0];
79
+ return `${path} must be ${words}, got ${value !== null && typeof value === "object" ? kindOf(value) : preview(value)}.`;
80
+ }