cozyclay 2.1.1 → 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 (577) hide show
  1. package/CHANGELOG.md +60 -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 +3 -3
  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-msLI71g1.js → first-shot-handoff-CC4xe4HM.js} +1 -1
  33. package/dist/assets/{landing-ChUvB_zL.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/sw.js +3 -6
  40. package/dist/workflow/index.html +5 -4
  41. package/mcp/LIVE-PROTOCOL.md +7 -47
  42. package/mcp/README.md +16 -26
  43. package/mcp/live-hub.mjs +6 -10
  44. package/mcp/package.json +1 -3
  45. package/mcp/tool-handlers.mjs +4 -168
  46. package/package.json +3 -22
  47. package/src/App.jsx +150 -2624
  48. package/src/agent-progress.js +79 -0
  49. package/src/agent-turn-mark.js +30 -0
  50. package/src/analytics.js +1 -19
  51. package/src/app-context.js +5 -0
  52. package/src/app-stage.jsx +2 -34
  53. package/src/asset-pane.jsx +6 -5
  54. package/src/camera-block.js +21 -0
  55. package/src/camera-direction.js +120 -0
  56. package/src/character-kind.js +3 -9
  57. package/src/command-bus.js +8 -6
  58. package/src/commands/cast.js +5 -34
  59. package/src/commands/elements/character.js +1 -1
  60. package/src/commands/elements/motion.js +1 -6
  61. package/src/commands/elements/object.js +6 -2
  62. package/src/commands/elements/shot.js +4 -1
  63. package/src/commands/elements.js +38 -14
  64. package/src/commands/index.js +3 -1
  65. package/src/commands/kits.js +37 -0
  66. package/src/commands/motion-timing.js +57 -0
  67. package/src/commands/motion.js +9 -152
  68. package/src/commands/motion3-compile.js +65 -0
  69. package/src/commands/motion3.js +207 -0
  70. package/src/commands/objects.js +6 -4
  71. package/src/commands/shot.js +49 -11
  72. package/src/commands/stage.js +14 -1
  73. package/src/commands/view.js +4 -1
  74. package/src/document-mirrors.js +42 -0
  75. package/src/document-store.js +4 -0
  76. package/src/domains/cast.js +19 -194
  77. package/src/domains/motion/motion-range-pin-rules.js +1 -1
  78. package/src/domains/motion/take-source.js +51 -0
  79. package/src/domains/motion.js +60 -1838
  80. package/src/domains/scenes.js +12 -55
  81. package/src/domains/shots.js +2 -2
  82. package/src/domains/stage.js +4 -2
  83. package/src/draft-ghost.js +46 -0
  84. package/src/draft-ghost.jsx +129 -0
  85. package/src/dualview.jsx +4 -3
  86. package/src/effects/EffectsLayer.jsx +112 -0
  87. package/src/effects/effect-meshes.js +232 -0
  88. package/src/effects/effect-record.js +103 -0
  89. package/src/effects/emitter.js +59 -0
  90. package/src/effects/simulate.js +261 -0
  91. package/src/effects/state.js +59 -0
  92. package/src/execution-telemetry.js +2 -3
  93. package/src/first-success-guide.jsx +5 -5
  94. package/src/ghost-set.js +257 -0
  95. package/src/held-object-edit.jsx +113 -0
  96. package/src/held-object-pose.js +175 -0
  97. package/src/hierarchy-model.js +1 -1
  98. package/src/hierarchy-panel.jsx +1 -1
  99. package/src/imported-mesh.jsx +103 -0
  100. package/src/kits/held.js +84 -0
  101. package/src/kits/index.js +70 -0
  102. package/src/kits/smoking.js +57 -0
  103. package/src/light-blockers.js +93 -0
  104. package/src/motion/beat-board-model.js +84 -0
  105. package/src/motion/beat-board.css +124 -0
  106. package/src/motion/beat-board.jsx +59 -0
  107. package/src/motion/center-of-mass.js +66 -0
  108. package/src/motion/fix-collisions.js +850 -0
  109. package/src/motion/physics-review.js +118 -0
  110. package/src/{ardy → motion}/physics-support.js +1 -76
  111. package/src/motion/rig-snapshot.js +22 -0
  112. package/src/motion/take-segments.jsx +140 -0
  113. package/src/{ardy → motion}/timeline-coordinates.js +0 -21
  114. package/src/{ardy → motion}/timeline.css +1 -2
  115. package/src/{ardy → motion}/timeline.jsx +42 -413
  116. package/src/motion/waypoints.js +76 -0
  117. package/src/motion3/beats.js +83 -0
  118. package/src/motion3/body.js +158 -0
  119. package/src/motion3/bones.js +47 -0
  120. package/src/motion3/carried.js +47 -0
  121. package/src/motion3/compile-job.js +75 -0
  122. package/src/motion3/compile-worker.js +10 -0
  123. package/src/motion3/compiler/anchors.js +105 -0
  124. package/src/motion3/compiler/arm-orient.js +128 -0
  125. package/src/motion3/compiler/channels.js +125 -0
  126. package/src/motion3/compiler/compile.js +205 -0
  127. package/src/motion3/compiler/gait-rhythm.js +97 -0
  128. package/src/motion3/compiler/grip.js +171 -0
  129. package/src/motion3/compiler/head-steady.js +48 -0
  130. package/src/motion3/compiler/hold.js +134 -0
  131. package/src/motion3/compiler/idle.js +113 -0
  132. package/src/motion3/compiler/limbs.js +170 -0
  133. package/src/motion3/compiler/math.js +72 -0
  134. package/src/motion3/compiler/pose.js +162 -0
  135. package/src/motion3/compiler/quat.js +49 -0
  136. package/src/motion3/compiler/skeleton.js +74 -0
  137. package/src/motion3/compiler/validate-common.js +32 -0
  138. package/src/motion3/compiler/validate-goals.js +118 -0
  139. package/src/motion3/compiler/validate-objects.js +143 -0
  140. package/src/motion3/compiler/validate-parts.js +116 -0
  141. package/src/motion3/compiler/validate.js +122 -0
  142. package/src/motion3/compiler/vocab.js +73 -0
  143. package/src/motion3/compiler/walk-pelvis.js +275 -0
  144. package/src/motion3/compiler/walk.js +207 -0
  145. package/src/motion3/compiler/wrist-clamp.js +32 -0
  146. package/src/motion3/delta.js +47 -0
  147. package/src/motion3/describe.js +174 -0
  148. package/src/motion3/docs.js +27 -0
  149. package/src/motion3/edit-carry.js +21 -0
  150. package/src/motion3/fix/ease.js +16 -0
  151. package/src/motion3/fix/ground.js +37 -0
  152. package/src/motion3/fix/index.js +52 -0
  153. package/src/motion3/fix/push.js +71 -0
  154. package/src/motion3/fix/reach.js +46 -0
  155. package/src/motion3/fix/retime.js +79 -0
  156. package/src/motion3/fix/self.js +211 -0
  157. package/src/motion3/fix/slide.js +70 -0
  158. package/src/motion3/fix/turn.js +51 -0
  159. package/src/motion3/follow.js +39 -0
  160. package/src/motion3/free-motion.js +173 -0
  161. package/src/motion3/grip-choice.js +130 -0
  162. package/src/motion3/hold-plan.js +157 -0
  163. package/src/motion3/json-patch.js +77 -0
  164. package/src/motion3/key-aim.js +105 -0
  165. package/src/motion3/key-edit.js +82 -0
  166. package/src/motion3/lenient.js +127 -0
  167. package/src/motion3/object-keys.js +236 -0
  168. package/src/motion3/object-pose.js +182 -0
  169. package/src/motion3/object-tracks.js +12 -0
  170. package/src/motion3/pipeline.js +102 -0
  171. package/src/motion3/playback.js +51 -0
  172. package/src/motion3/probes.js +91 -0
  173. package/src/motion3/qa/body-checks.js +34 -0
  174. package/src/motion3/qa/check-names.js +14 -0
  175. package/src/motion3/qa/checks.js +121 -0
  176. package/src/motion3/qa/contact-checks.js +51 -0
  177. package/src/motion3/qa/explain.js +80 -0
  178. package/src/motion3/qa/fmt.js +5 -0
  179. package/src/motion3/qa/foot-checks.js +72 -0
  180. package/src/motion3/qa/geometry.js +77 -0
  181. package/src/motion3/qa/goals.js +191 -0
  182. package/src/motion3/qa/ground.js +60 -0
  183. package/src/motion3/qa/hints-contact.js +179 -0
  184. package/src/motion3/qa/hints-goal.js +160 -0
  185. package/src/motion3/qa/hints-knee.js +24 -0
  186. package/src/motion3/qa/hints-motion.js +209 -0
  187. package/src/motion3/qa/hints-object.js +91 -0
  188. package/src/motion3/qa/index.js +42 -0
  189. package/src/motion3/qa/key-checks.js +82 -0
  190. package/src/motion3/qa/near.js +96 -0
  191. package/src/motion3/qa/object-checks.js +135 -0
  192. package/src/motion3/qa/patch.js +161 -0
  193. package/src/motion3/qa/severity.js +38 -0
  194. package/src/motion3/qa/spec-checks.js +65 -0
  195. package/src/motion3/qa/style.js +218 -0
  196. package/src/motion3/qa/wrist-checks.js +69 -0
  197. package/src/motion3/qa/wrist-obs.js +60 -0
  198. package/src/motion3/refs.js +120 -0
  199. package/src/motion3/repair-tune.js +121 -0
  200. package/src/motion3/repair.js +83 -0
  201. package/src/motion3/report.js +67 -0
  202. package/src/motion3/rigs/index.js +14 -0
  203. package/src/motion3/rigs/x-bot-tpose-body.json +1 -0
  204. package/src/motion3/rigs/x-bot-tpose.json +1 -0
  205. package/src/motion3/rigs/y-bot-tpose-body.json +1 -0
  206. package/src/motion3/rigs/y-bot-tpose.json +1 -0
  207. package/src/motion3/scene.js +60 -0
  208. package/src/motion3/schema/brief.js +89 -0
  209. package/src/motion3/schema/fields.js +44 -0
  210. package/src/motion3/schema/spec-examples.js +46 -0
  211. package/src/motion3/schema/topics.js +111 -0
  212. package/src/motion3/schema/types.js +147 -0
  213. package/src/motion3/search.js +117 -0
  214. package/src/motion3/second-hand.js +112 -0
  215. package/src/motion3/skills/basic.js +82 -0
  216. package/src/motion3/skills/book.js +76 -0
  217. package/src/motion3/skills/car.js +145 -0
  218. package/src/motion3/skills/frames-auto.js +29 -0
  219. package/src/motion3/skills/index.js +83 -0
  220. package/src/motion3/skills/info.js +65 -0
  221. package/src/motion3/skills/smoke-kit.js +109 -0
  222. package/src/motion3/skills/smoke.js +160 -0
  223. package/src/motion3/spec-store.js +59 -0
  224. package/src/motion3/take-edit.js +155 -0
  225. package/src/motion3/take.js +63 -0
  226. package/src/motion3/timeline.js +127 -0
  227. package/src/motion3/views.js +121 -0
  228. package/src/motion3/walk-plan.js +119 -0
  229. package/src/object-catalog.jsx +7 -1
  230. package/src/object-colors.js +98 -0
  231. package/src/object-gizmo-parts.js +115 -0
  232. package/src/object-gizmo.jsx +37 -122
  233. package/src/object-glow.js +139 -0
  234. package/src/object-hidden.js +33 -0
  235. package/src/object-look.js +196 -0
  236. package/src/object-size.js +10 -0
  237. package/src/object-travel.js +71 -6
  238. package/src/panels/CameraPanel.jsx +7 -7
  239. package/src/panels/HdrEnvironmentControls.jsx +59 -0
  240. package/src/panels/LightPanel.jsx +25 -4
  241. package/src/panels/ObjectLookPanel.jsx +88 -0
  242. package/src/panels/PosePanel.jsx +2 -17
  243. package/src/panels/RigControlPanel.jsx +5 -53
  244. package/src/panels/SubjectBox.jsx +4 -1
  245. package/src/panels/details.css +14 -0
  246. package/src/posestudio.jsx +4 -27
  247. package/src/project-browser.jsx +3 -4
  248. package/src/project-resources.js +18 -31
  249. package/src/project.js +13 -104
  250. package/src/props.jsx +25 -280
  251. package/src/rail-dot-drag.js +23 -0
  252. package/src/render-passes.js +10 -0
  253. package/src/resource-status.jsx +1 -0
  254. package/src/result-modal.jsx +4 -19
  255. package/src/room.jsx +20 -12
  256. package/src/scene-assets.js +8 -6
  257. package/src/scene-light-place.js +72 -0
  258. package/src/scene-lights.jsx +148 -0
  259. package/src/scene-objects.js +31 -139
  260. package/src/scenes.js +15 -105
  261. package/src/semantic-edit.js +2 -5
  262. package/src/set-pieces.jsx +183 -0
  263. package/src/settings-menu.jsx +3 -11
  264. package/src/shell/BottomDock.jsx +9 -51
  265. package/src/shell/DetailsSlot.jsx +16 -200
  266. package/src/shell/GhostsMenuGroup.jsx +74 -0
  267. package/src/shell/LibrarySlot.jsx +1 -3
  268. package/src/shell/MenuBar.jsx +3 -3
  269. package/src/shell/PreferencesDialog.jsx +15 -264
  270. package/src/shell/PreferencesSlot.jsx +2 -8
  271. package/src/shell/TopBar.jsx +5 -84
  272. package/src/shell/ViewportToolbar.jsx +10 -70
  273. package/src/shell/agent-glass.css +19 -6
  274. package/src/shell/details-selection.js +42 -0
  275. package/src/shell/ghosts-menu.css +22 -0
  276. package/src/shell/glass-regions.css +29 -3
  277. package/src/shell/preferences.css +6 -1
  278. package/src/shot-authoring.js +4 -7
  279. package/src/soft-shadows.js +37 -0
  280. package/src/stage-attach-frames.js +12 -12
  281. package/src/stage-canvas-rigs.jsx +3 -3
  282. package/src/stage-capture.jsx +42 -8
  283. package/src/stage-characters.jsx +22 -216
  284. package/src/stage-editor-gizmos.jsx +6 -6
  285. package/src/stage-environment.js +93 -0
  286. package/src/stage-hdr-builtins.js +107 -0
  287. package/src/stage-hdr-image.js +116 -0
  288. package/src/stage-hdr-library.js +70 -0
  289. package/src/stage-hdr-map.js +94 -0
  290. package/src/stage-hdr.js +130 -0
  291. package/src/stage-lighting.js +149 -0
  292. package/src/stage-occlusion.js +210 -0
  293. package/src/stage-sky.jsx +159 -0
  294. package/src/stage-startup-defaults.js +5 -9
  295. package/src/studio-actions.js +12 -14
  296. package/src/studio-agent-commands.js +19 -16
  297. package/src/studio-agent-motion.js +9 -9
  298. package/src/studio-agent-protocol.js +41 -71
  299. package/src/studio-app-binding.js +25 -45
  300. package/src/studio-asset-list.js +17 -0
  301. package/src/studio-contact-sheet.js +37 -5
  302. package/src/studio-elements.js +27 -7
  303. package/src/studio-entity-bounds.js +18 -0
  304. package/src/studio-error-codes.js +8 -0
  305. package/src/studio-export-framing.js +152 -0
  306. package/src/studio-lighting-check.js +103 -0
  307. package/src/studio-motion-visual.js +150 -0
  308. package/src/studio-schema-errors.js +80 -0
  309. package/src/studio-support-gap.js +74 -0
  310. package/src/styles.css +1 -19
  311. package/src/timeline-defaults.js +5 -0
  312. package/src/timeline-extent.js +8 -20
  313. package/src/tutorial-walk.js +17 -0
  314. package/src/ui-scrub.js +1 -1
  315. package/src/use-shot-camera-tracks.js +12 -0
  316. package/src/view-ghosts.js +97 -0
  317. package/src/view-ghosts.jsx +49 -0
  318. package/src/workflow/AgentPanel.jsx +47 -166
  319. package/src/workflow/AgentProgressCard.jsx +107 -0
  320. package/src/workflow/AgentReplyText.jsx +37 -0
  321. package/src/workflow/AgentTurnSteps.jsx +84 -0
  322. package/src/workflow/LoginPanel.jsx +140 -0
  323. package/src/workflow/WorkflowBuilder.jsx +7 -23
  324. package/src/workflow/agent-client.js +54 -51
  325. package/src/workflow/agent-panel.css +71 -1
  326. package/src/workflow/agent-progress-card.css +167 -0
  327. package/src/workflow/agent-progress-view.js +47 -0
  328. package/src/workflow/agent-turn-steps.css +113 -0
  329. package/src/workflow/agent-turn.js +191 -0
  330. package/src/workflow/login-client.js +34 -0
  331. package/src/workflow/motion-input.js +18 -31
  332. package/src/workflow/node-schema.js +1 -1
  333. package/src/workflow/reply-markdown.js +69 -0
  334. package/src/workflow/scene-asset-sync.js +8 -8
  335. package/tools/agent-session-stats.mjs +61 -0
  336. package/tools/dev-full.mjs +5 -56
  337. package/tools/eslint-baseline.json +12 -13
  338. package/tools/knip-baseline.json +0 -179
  339. package/tools/loc-baseline.json +31 -81
  340. package/tools/motion3/extract-body.mjs +103 -0
  341. package/tools/motion3/extract-rig.mjs +85 -0
  342. package/tools/process-supervisor.mjs +0 -88
  343. package/tools/run-tests.mjs +8 -124
  344. package/bin/agent/motion-runtime.mjs +0 -76
  345. package/dist/ardy/cskel27-rest.json +0 -557
  346. package/dist/assets/analytics-C9pMpW7s.js +0 -1
  347. package/dist/assets/app-B_2XB9rw.css +0 -1
  348. package/dist/assets/app-DAuvKPP8.js +0 -4920
  349. package/dist/assets/config-Bx7pYsD9.js +0 -1
  350. package/dist/assets/config-DeoFTYzb.css +0 -1
  351. package/dist/assets/demo-CvKuBgy5.js +0 -1
  352. package/dist/assets/scene-asset-sync-DKkpjKBV.js +0 -19
  353. package/dist/assets/scene-asset-sync-DjJ_cJ2R.css +0 -1
  354. package/dist/assets/ticket-BPk2Ni95.js +0 -1
  355. package/dist/assets/workflow-jJRBR94Z.js +0 -179
  356. package/dist/demo/walk-then-stop.npz +0 -0
  357. package/mcp/ardy-prompts.mjs +0 -218
  358. package/src/ardy/auto-fix-panel.css +0 -108
  359. package/src/ardy/auto-physics.js +0 -1194
  360. package/src/ardy/client.js +0 -153
  361. package/src/ardy/convert.js +0 -313
  362. package/src/ardy/cskel27-neutral.js +0 -39
  363. package/src/ardy/cskel27.js +0 -77
  364. package/src/ardy/export.js +0 -165
  365. package/src/ardy/fix-collisions.js +0 -2257
  366. package/src/ardy/foot-lock.js +0 -230
  367. package/src/ardy/motion-calibration.js +0 -118
  368. package/src/ardy/motion-edit.js +0 -213
  369. package/src/ardy/motion-url.js +0 -55
  370. package/src/ardy/mp4-duration.js +0 -102
  371. package/src/ardy/npz.js +0 -595
  372. package/src/ardy/physics-panel.css +0 -21
  373. package/src/ardy/physics-panel.jsx +0 -89
  374. package/src/ardy/physics-refine.js +0 -111
  375. package/src/ardy/physics-review.js +0 -567
  376. package/src/ardy/physics-temporal.js +0 -54
  377. package/src/ardy/platform-fit-panel.css +0 -64
  378. package/src/ardy/platform-fit-panel.jsx +0 -41
  379. package/src/ardy/platform-fit.js +0 -326
  380. package/src/ardy/playback.js +0 -711
  381. package/src/ardy/pose-pin.js +0 -97
  382. package/src/ardy/prompt-clips.js +0 -30
  383. package/src/ardy/retime.js +0 -211
  384. package/src/ardy/root-drop.js +0 -369
  385. package/src/ardy/timeline-resize.js +0 -13
  386. package/src/ardy/to-cskel27.js +0 -225
  387. package/src/ardy/trim.js +0 -33
  388. package/src/ardy/waypoints.js +0 -369
  389. package/src/ardy-bridge-contract.js +0 -281
  390. package/src/domains/motion/motion-video-capture.js +0 -664
  391. package/src/i2v-motion-client.js +0 -77
  392. package/src/i2v-motion-studio.jsx +0 -180
  393. package/src/line-edit.js +0 -1348
  394. package/src/motion/generation.js +0 -105
  395. package/src/motion-readiness-ui.jsx +0 -67
  396. package/src/motion-readiness.js +0 -40
  397. package/src/motion-resources.js +0 -177
  398. package/src/motion-store.js +0 -65
  399. package/src/motion-trail.js +0 -601
  400. package/src/multimodel-ingest.js +0 -394
  401. package/src/panels/PromptBlocksPanel.jsx +0 -444
  402. package/src/panels/VideoCapturePanel.jsx +0 -181
  403. package/src/pose-extract/browser.js +0 -42
  404. package/src/pose-extract/detector.js +0 -100
  405. package/src/pose-extract/fit.js +0 -227
  406. package/src/pose-extract/image-frame.js +0 -51
  407. package/src/pose-extract/index.js +0 -58
  408. package/src/pose-extract/keyframes.js +0 -74
  409. package/src/pose-extract/landmarks.js +0 -162
  410. package/src/pose-extract/mirror.js +0 -153
  411. package/src/pose-extract/take.js +0 -131
  412. package/src/pose-extract/video-frames.js +0 -91
  413. package/src/take-recipe.js +0 -165
  414. package/src/trail-key-conflicts.js +0 -39
  415. package/src/trail-pick.js +0 -59
  416. package/src/use-i2v-capture-modal.js +0 -15
  417. package/tools/ardy/BRIDGE.md +0 -37
  418. package/tools/ardy/GVHMR-NPZ.md +0 -68
  419. package/tools/ardy/README.md +0 -124
  420. package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
  421. package/tools/ardy/artifacts.mjs +0 -34
  422. package/tools/ardy/bridge.mjs +0 -1732
  423. package/tools/ardy/bvh-cskel27.mjs +0 -1265
  424. package/tools/ardy/cclay_constrained_generate.py +0 -561
  425. package/tools/ardy/cclay_gvhmr_worker.py +0 -264
  426. package/tools/ardy/cclay_motion_edit.py +0 -510
  427. package/tools/ardy/cclay_sequence_generate.py +0 -705
  428. package/tools/ardy/cclay_worker.py +0 -304
  429. package/tools/ardy/dump-npz.py +0 -205
  430. package/tools/ardy/extract-rest.mjs +0 -299
  431. package/tools/ardy/extract.mjs +0 -507
  432. package/tools/ardy/footage.mjs +0 -467
  433. package/tools/ardy/gvhmr-floor.mjs +0 -102
  434. package/tools/ardy/gvhmr-to-cskel27.mjs +0 -21
  435. package/tools/ardy/gvhmr_fastpath.py +0 -168
  436. package/tools/ardy/gvhmr_trajectory.py +0 -240
  437. package/tools/ardy/merge-text-encoder.py +0 -160
  438. package/tools/ardy/mocap-metrics.mjs +0 -200
  439. package/tools/ardy/mocap-quality-gate.mjs +0 -110
  440. package/tools/ardy/motion-stabilize.mjs +0 -348
  441. package/tools/ardy/npz.mjs +0 -414
  442. package/tools/ardy/pose-to-npz.mjs +0 -106
  443. package/tools/ardy/prompt-limits.mjs +0 -5
  444. package/tools/ardy/run-edit-on-box.sh +0 -73
  445. package/tools/ardy/run-local.mjs +0 -233
  446. package/tools/ardy/run-on-box.sh +0 -593
  447. package/tools/ardy/run-sequence-on-box.sh +0 -177
  448. package/tools/ardy/runners/gvhmr-worker.mjs +0 -206
  449. package/tools/ardy/runners/index.mjs +0 -18
  450. package/tools/ardy/runners/local.mjs +0 -455
  451. package/tools/ardy/runners/proc.mjs +0 -125
  452. package/tools/ardy/runners/remote.mjs +0 -252
  453. package/tools/ardy/setup-local.mjs +0 -189
  454. package/tools/ardy/setup-text-encoder-on-box.sh +0 -93
  455. package/tools/ardy/setup-text-encoder.py +0 -306
  456. package/tools/ardy/smpl-cskel27.mjs +0 -178
  457. package/tools/ardy/visual-qa.mjs +0 -196
  458. package/tools/ardy/vq-car.mjs +0 -52
  459. package/tools/bench/EXP3.md +0 -61
  460. package/tools/bench/cclay_bench_extract_incam.py +0 -125
  461. package/tools/bench/cclay_bench_extract_obs.py +0 -204
  462. package/tools/bench/cclay_bench_runner.py +0 -42
  463. package/tools/bench/cube-contact.mjs +0 -162
  464. package/tools/bench/exp3.mjs +0 -152
  465. package/tools/bench/extract-bench-lib.mjs +0 -216
  466. package/tools/bench/extract-bench.mjs +0 -302
  467. package/tools/bench/fal-generate.mjs +0 -88
  468. package/tools/bench/fit/README.md +0 -189
  469. package/tools/bench/fit/camera.mjs +0 -59
  470. package/tools/bench/fit/contact.mjs +0 -419
  471. package/tools/bench/fit/footlock.mjs +0 -234
  472. package/tools/bench/fit/motion.mjs +0 -85
  473. package/tools/bench/fit/pin.mjs +0 -27
  474. package/tools/bench/fit/remote.mjs +0 -105
  475. package/tools/bench/fit-bench.mjs +0 -126
  476. package/tools/bench/fit-sanity.mjs +0 -45
  477. package/tools/bench/metrics.mjs +0 -313
  478. package/tools/bench/obs/depth.mjs +0 -258
  479. package/tools/bench/obs/extrinsics.mjs +0 -175
  480. package/tools/bench/obs/fit_mannequin_betas.py +0 -340
  481. package/tools/bench/obs/ground.mjs +0 -294
  482. package/tools/bench/obs/heading.mjs +0 -151
  483. package/tools/bench/obs/ladder.mjs +0 -555
  484. package/tools/bench/obs/mannequin-betas.json +0 -124
  485. package/tools/bench/obs/remote.mjs +0 -83
  486. package/tools/bench/obs/rest_joints.py +0 -52
  487. package/tools/bench/obs/ybot-targets.mjs +0 -49
  488. package/tools/bench/obs-bench.mjs +0 -520
  489. package/tools/bench/score.mjs +0 -343
  490. package/tools/bench/summarize.mjs +0 -86
  491. package/tools/demo-worker/README.md +0 -137
  492. package/tools/demo-worker/api-client.mjs +0 -233
  493. package/tools/demo-worker/generate.mjs +0 -301
  494. package/tools/demo-worker/index.mjs +0 -466
  495. package/tools/demo-worker/sign-core.mjs +0 -112
  496. package/tools/demo-worker/sign.mjs +0 -161
  497. package/tools/gt-render/browser.mjs +0 -182
  498. package/tools/gt-render/camera-math.mjs +0 -276
  499. package/tools/gt-render/page.mjs +0 -267
  500. package/tools/gt-render/render.mjs +0 -514
  501. package/tools/gt-render/scene-box.mjs +0 -31
  502. package/tools/gt-render/take-transform.mjs +0 -87
  503. package/tools/kimodo/compare-seam.mjs +0 -108
  504. package/tools/kimodo/constraints.mjs +0 -244
  505. package/tools/kimodo/edit.mjs +0 -259
  506. package/tools/kimodo/effector-constraints.mjs +0 -200
  507. package/tools/kimodo/generate.mjs +0 -646
  508. package/tools/kimodo/local-output.mjs +0 -265
  509. package/tools/kimodo/measure-pose.mjs +0 -65
  510. package/tools/kimodo/measure-preserve.mjs +0 -899
  511. package/tools/kimodo/measure-waypoints.mjs +0 -55
  512. package/tools/kimodo/mlx-generate.py +0 -111
  513. package/tools/kimodo/pose-constraints.mjs +0 -248
  514. package/tools/kimodo/preserve-mask.mjs +0 -543
  515. package/tools/kimodo/read-npz.mjs +0 -189
  516. package/tools/kimodo/run-edit-on-box.mjs +0 -155
  517. package/tools/kimodo/run-sequence-on-box.mjs +0 -178
  518. package/tools/kimodo/runner.mjs +0 -284
  519. package/tools/kimodo/seam-batch.mjs +0 -77
  520. package/tools/kimodo/setup-local.mjs +0 -30
  521. package/tools/kimodo/setup-on-box.sh +0 -339
  522. package/tools/kimodo/soma77-to-cskel27.mjs +0 -305
  523. package/tools/morphgs/assets/gen_truth.py +0 -39
  524. package/tools/morphgs/assets/mesh_ori_rig.txt +0 -27
  525. package/tools/morphgs/assets/playback-check.mjs +0 -30
  526. package/tools/morphgs/demo-gate.sh +0 -26
  527. package/tools/morphgs/fbx2morphgs.mjs +0 -68
  528. package/tools/morphgs/morphgs-to-cskel27.mjs +0 -105
  529. package/tools/morphgs/patches/preprocess_src-none-mode.patch +0 -76
  530. package/tools/morphgs/setup-on-cluster.sh +0 -126
  531. package/tools/projflow/driver.py +0 -1069
  532. package/tools/projflow/generate.mjs +0 -805
  533. package/tools/projflow/hml22-to-cskel27.mjs +0 -735
  534. package/tools/projflow/line-edit-job.mjs +0 -547
  535. package/tools/projflow/replay.mjs +0 -452
  536. package/tools/projflow/runner.mjs +0 -269
  537. package/tools/projflow/service.mjs +0 -742
  538. package/tools/track/DESIGN.md +0 -513
  539. package/tools/track/backfill-provenance.mjs +0 -116
  540. package/tools/track/budget.mjs +0 -8
  541. package/tools/track/check-rig.mjs +0 -209
  542. package/tools/track/diagnostics.schema.json +0 -60
  543. package/tools/track/export-rig.mjs +0 -256
  544. package/tools/track/fallback.mjs +0 -7
  545. package/tools/track/fk-parity-fixture.mjs +0 -161
  546. package/tools/track/gate.mjs +0 -349
  547. package/tools/track/masks.mjs +0 -318
  548. package/tools/track/metrics.mjs +0 -201
  549. package/tools/track/publish-obs.mjs +0 -108
  550. package/tools/track/py/check_env.py +0 -61
  551. package/tools/track/py/eval_lr.py +0 -229
  552. package/tools/track/py/lr_viterbi.py +0 -294
  553. package/tools/track/py/masks.py +0 -405
  554. package/tools/track/py/objective.py +0 -382
  555. package/tools/track/py/rig.py +0 -508
  556. package/tools/track/py/scene.py +0 -326
  557. package/tools/track/py/test_joint_indices.py +0 -123
  558. package/tools/track/py/test_lr_viterbi.py +0 -195
  559. package/tools/track/py/test_masks.py +0 -229
  560. package/tools/track/py/test_rig.py +0 -288
  561. package/tools/track/py/test_scene.py +0 -354
  562. package/tools/track/py/test_track.py +0 -328
  563. package/tools/track/py/track.py +0 -411
  564. package/tools/track/remote.mjs +0 -140
  565. package/tools/track/rig-dump.mjs +0 -227
  566. package/tools/track/run-box-tests.mjs +0 -14
  567. package/tools/track/run-box.mjs +0 -168
  568. package/tools/track/setup-box.sh +0 -59
  569. package/tools/track/study-2d.mjs +0 -728
  570. /package/src/{ardy → motion}/collision-blockers.js +0 -0
  571. /package/src/{ardy → motion}/ground.js +0 -0
  572. /package/src/{ardy → motion}/ik-drag.js +0 -0
  573. /package/src/{ardy → motion}/ik-key-json.js +0 -0
  574. /package/src/{ardy → motion}/ik.js +0 -0
  575. /package/src/{ardy → motion}/physics-surface.js +0 -0
  576. /package/src/{ardy → motion}/playback-clock.js +0 -0
  577. /package/src/{ardy → motion}/range-pin.js +0 -0
@@ -1,452 +0,0 @@
1
- /**
2
- * replay.mjs — recipe replay (contract C10) and the per-field line-edit rules
3
- * both the C6 and the C10 paths validate with.
4
- *
5
- * A take is a RECIPE, not a result: seed + prompt blocks + the line edits the
6
- * artist drew on top. Determinism makes the result reconstructible, so
7
- * regenerating or extending a take no longer has to destroy its refinements —
8
- * the app sends the stored line edits back as `replay` and the bridge re-applies
9
- * them to the freshly generated take.
10
- *
11
- * WHY THIS IS ITS OWN MODULE and not more bridge code: everything here is pure
12
- * arithmetic plus one injectable job runner, so the whole contract — the
13
- * per-entry rules, the boundary warning, the sequential chaining — is testable
14
- * on a laptop with no GPU and no sidecar. bridge.mjs keeps only the wiring:
15
- * which take the chain starts from, where the artifacts go, and what it streams.
16
- *
17
- * THE CHAIN, stated once. Entry i reads entry i-1's OUTPUT; entry 0 reads the
18
- * freshly generated take. Every output lands in the request's artifact dir as
19
- * replay-<i>.npz and only the LAST one is registered — the intermediates are
20
- * evidence, not takes. A failing entry is skipped and the chain continues from
21
- * the last good file: a missing refinement beats a dead run.
22
- */
23
-
24
- import { join } from "node:path";
25
-
26
- import { TRACK_TO_HML22_JOINT, UNMAPPABLE_TRACKS } from "./generate.mjs";
27
-
28
- /** The tracks a line edit (C6) or a replay entry (C10) may name — the wrapper's
29
- * own track -> hml22 joint table, never a copy of it. */
30
- const LINE_EDIT_TRACK_IDS = Object.keys(TRACK_TO_HML22_JOINT);
31
- const LINE_EDIT_POINTS_MIN = 2;
32
- const LINE_EDIT_POINTS_MAX = 64; // matches src/line-edit.js MAX_LINE_POINTS
33
- /** C6's OTHER gesture: 3D pins (`pins3d`), one per scrubbed moment.
34
- *
35
- * A pin is a keyframe — "the hand is HERE at frame 100" — and it is the shape
36
- * ProjFlow is actually evaluated on: its whole spatial-control table is sparse
37
- * keyframes (1, 2, 5, 49, 196 of them) with exact satisfaction and the flow
38
- * prior filling the gaps. A drawn line is the dense end of the same mechanism.
39
- *
40
- * ONE is a legal edit, unlike points2d's two: a line needs two points to have a
41
- * direction, a pin needs none. EIGHT is the cap, and it is a UX number rather
42
- * than a solver one — 3 rows per pin is nothing next to 128 line rows — past
43
- * eight pins in one edit the artist is keyframing by hand and wants the pose
44
- * studio, not a reroute. */
45
- export const LINE_EDIT_PINS_MIN = 1;
46
- export const LINE_EDIT_PINS_MAX = 8;
47
- /** C10's cap. A recipe is an authoring history, not a render queue: 16 chained
48
- * box round trips is already a minute of wall time on top of the generation. */
49
- export const REPLAY_MAX = 16;
50
- /** Mirrors the bridge's own optional-seed cap; the bridge passes its constant in
51
- * so the two can never disagree, and this default only serves direct callers. */
52
- const DEFAULT_SEED_MAX = 2 ** 31 - 1;
53
- /** Frames either side of a block boundary that a replayed edit is warned about.
54
- * See boundaryWarningFor for why the number is small. */
55
- export const BOUNDARY_MARGIN = 5;
56
- /** Fields a replay entry may NOT carry: `replay` cannot be combined with the
57
- * modes that splice or rewrite an existing take, because then "the take the
58
- * replay applies to" has two candidate answers. */
59
- const REPLAY_EXCLUSIVE = ["lineEdit", "motionEdit", "regenerateSegments"];
60
-
61
- /* ------------------------------------------------------------------------- */
62
- /* the per-field rules, shared by C6 and C10 */
63
- /* ------------------------------------------------------------------------- */
64
-
65
- /**
66
- * The field checks a line-edit payload must pass, wherever it arrives from.
67
- *
68
- * `label` is the wire path the message names — "lineEdit" for a C6 request,
69
- * "replay[3]" for the fourth entry of a C10 replay — and it is the ONLY
70
- * difference between the two paths. The strings are otherwise byte-identical to
71
- * the ones the C6 validator has always returned.
72
- *
73
- * What is NOT here, deliberately: `sourceMotion` (C6 requires it, C10 forbids
74
- * it), the exclusivity rules, and `preview` (C6 only). Those belong to whoever
75
- * calls this.
76
- *
77
- * Frame numbers are all on the app's 24 fps clip clock, exactly as sent; the
78
- * 24 -> 20 conversion is line-edit-job.mjs's business and is not duplicated
79
- * here or in the bridge.
80
- *
81
- * Returns an error message naming the offending field, or null when valid.
82
- */
83
- export function validateLineEditFields(lineEdit, clipFrames, label = "lineEdit") {
84
- if (!lineEdit || typeof lineEdit !== "object" || Array.isArray(lineEdit)) {
85
- return `field '${label}' must be an object`;
86
- }
87
- // `chest` is a REAL pose-studio track and a real refusal: cskel27 Spine2 is
88
- // one of the five joints the hml22 skeleton has no source for (S2), so there
89
- // is nothing to constrain. Refused by name, with the alternative, rather than
90
- // silently retargeted onto a neighbouring spine joint — which would put the
91
- // drawn line on a curve the artist did not draw.
92
- if (typeof lineEdit.track === "string" && Object.hasOwn(UNMAPPABLE_TRACKS, lineEdit.track)) {
93
- return (
94
- `field '${label}.track' "${lineEdit.track}" cannot be line-edited: ${UNMAPPABLE_TRACKS[lineEdit.track]}; ` +
95
- "draw on spine or neck instead"
96
- );
97
- }
98
- if (typeof lineEdit.track !== "string" || !Object.hasOwn(TRACK_TO_HML22_JOINT, lineEdit.track)) {
99
- return (
100
- `field '${label}.track' ${JSON.stringify(lineEdit.track)} is not a line-editable IK track id; ` +
101
- `valid ids are ${LINE_EDIT_TRACK_IDS.join(", ")}`
102
- );
103
- }
104
- const range = lineEdit.frameRange;
105
- if (!range || typeof range !== "object" || Array.isArray(range)) {
106
- return `field '${label}.frameRange' must be an object { startFrame, endFrame }`;
107
- }
108
- if (!Number.isInteger(range.startFrame) || !Number.isInteger(range.endFrame)) {
109
- return `field '${label}.frameRange' startFrame and endFrame must be integers`;
110
- }
111
- // Half-open and inside the clip, like every other range that crosses this
112
- // boundary. The clip length comes from `duration`, which the app derives from
113
- // the take's own frame count; the job re-checks against the npz itself, since
114
- // only that file knows how long the take really is.
115
- if (range.startFrame < 0 || range.endFrame <= range.startFrame || range.endFrame > clipFrames) {
116
- return `field '${label}.frameRange' must be a non-empty half-open range inside 0..${clipFrames}`;
117
- }
118
- // Two points need two frames: one constrained frame is a pin, not a line.
119
- if (range.endFrame - range.startFrame < 2) {
120
- return `field '${label}.frameRange' must span at least 2 frames`;
121
- }
122
- /* ---------------------------- pins3d (C6 v2) ---------------------------
123
- * ONE EDIT IS ONE GESTURE. A stroke says "go along this shape" and pins say
124
- * "be exactly here at these moments"; combining them would ask the box to
125
- * satisfy a 2D path and a set of 3D points that were authored through
126
- * different mental models and are only accidentally compatible. So pins3d
127
- * REPLACES points2d and the camera — a pin is already in world metres and
128
- * needs no lens to be read through, which is also why a pinned edit survives
129
- * a camera move that would discard a drawn one.
130
- *
131
- * Checked BEFORE points2d so a pins payload never trips the "needs at least
132
- * 2 points" message about a field it deliberately omitted. */
133
- if (lineEdit.pins3d !== undefined) {
134
- const pins = lineEdit.pins3d;
135
- if (lineEdit.points2d !== undefined) {
136
- return `field '${label}' must carry either points2d (a drawn line) or pins3d (3D pins), not both`;
137
- }
138
- if (!Array.isArray(pins) || pins.length < LINE_EDIT_PINS_MIN) {
139
- return `field '${label}.pins3d' needs at least ${LINE_EDIT_PINS_MIN} pin`;
140
- }
141
- if (pins.length > LINE_EDIT_PINS_MAX) {
142
- return `field '${label}.pins3d' is capped at ${LINE_EDIT_PINS_MAX} pins, got ${pins.length}`;
143
- }
144
- let previousFrame = -1;
145
- for (let index = 0; index < pins.length; index += 1) {
146
- const pin = pins[index];
147
- if (!pin || typeof pin !== "object" || Array.isArray(pin)) {
148
- return `field '${label}.pins3d[${index}]' must be an object { frame, position }`;
149
- }
150
- if (!Number.isInteger(pin.frame)) {
151
- return `field '${label}.pins3d[${index}].frame' must be an integer app-clip frame`;
152
- }
153
- // Inside the range and half-open with it: a pin on endFrame names a
154
- // frame the edit does not own, and the splice would throw it away.
155
- if (pin.frame < range.startFrame || pin.frame >= range.endFrame) {
156
- return `field '${label}.pins3d[${index}].frame' ${pin.frame} is outside frameRange ${range.startFrame}..${range.endFrame}`;
157
- }
158
- // STRICTLY ascending: two pins on one frame are two answers to the
159
- // same question, and out-of-order pins mean the caller lost track of
160
- // which pin it was editing.
161
- if (pin.frame <= previousFrame) {
162
- return `field '${label}.pins3d' frames must be strictly ascending, got ${pin.frame} after ${previousFrame}`;
163
- }
164
- previousFrame = pin.frame;
165
- const position = pin.position;
166
- if (!Array.isArray(position) || position.length !== 3 || !position.every((value) => typeof value === "number" && Number.isFinite(value))) {
167
- return `field '${label}.pins3d[${index}].position' must be [x, y, z] finite numbers in world metres`;
168
- }
169
- }
170
- // The camera is a stroke's business. Refused rather than ignored, for the
171
- // same reason a replay entry's sourceMotion is: silently dropping a field
172
- // lets the caller believe it meant something.
173
- if (lineEdit.camera !== undefined) {
174
- return `field '${label}.camera' must be omitted for a pins3d edit — pins are world-space and need no lens`;
175
- }
176
- if (lineEdit.prompt !== undefined && typeof lineEdit.prompt !== "string") {
177
- return `field '${label}.prompt' must be a string when present`;
178
- }
179
- return null;
180
- }
181
-
182
- const points = lineEdit.points2d;
183
- if (!Array.isArray(points) || points.length < LINE_EDIT_POINTS_MIN) {
184
- return `field '${label}.points2d' needs at least ${LINE_EDIT_POINTS_MIN} points`;
185
- }
186
- // The cap is the app's own, and it is a solver budget: the box builds two
187
- // affine rows per point and factorises an m x m system every ODE step.
188
- if (points.length > LINE_EDIT_POINTS_MAX) {
189
- return `field '${label}.points2d' is capped at ${LINE_EDIT_POINTS_MAX} points, got ${points.length}`;
190
- }
191
- for (let index = 0; index < points.length; index += 1) {
192
- const point = points[index];
193
- if (!Array.isArray(point) || point.length !== 2 || !point.every((value) => typeof value === "number" && Number.isFinite(value))) {
194
- return `field '${label}.points2d[${index}]' must be [u, v] finite numbers`;
195
- }
196
- // Viewport-NORMALISED, and enforced rather than clamped: a pixel
197
- // coordinate here is the single most likely wire mistake and it produces a
198
- // plausible-looking line through the wrong ray.
199
- if (point[0] < 0 || point[0] > 1 || point[1] < 0 || point[1] > 1) {
200
- return `field '${label}.points2d[${index}]' must be viewport-normalized into 0..1, got ${JSON.stringify(point)}`;
201
- }
202
- }
203
- const camera = lineEdit.camera;
204
- if (!camera || typeof camera !== "object" || Array.isArray(camera)) {
205
- return `field '${label}.camera' must be an object { fx, fy, cx, cy, R, t }`;
206
- }
207
- for (const key of ["fx", "fy", "cx", "cy"]) {
208
- if (typeof camera[key] !== "number" || !Number.isFinite(camera[key])) {
209
- return `field '${label}.camera.${key}' must be a finite number`;
210
- }
211
- }
212
- // A non-positive focal length means the uv/NDC flip was applied twice or not
213
- // at all, and every solved position would be mirrored.
214
- if (camera.fx <= 0 || camera.fy <= 0) {
215
- return `field '${label}.camera' has a non-positive focal length — the uv convention is inverted`;
216
- }
217
- // The intrinsics live in the SAME units as points2d. A pixel focal length
218
- // beside normalised points solves cleanly and lands nowhere near the stroke.
219
- if (camera.fx > 50 || camera.fy > 50) {
220
- return `field '${label}.camera' has PIXEL focal lengths (${camera.fx}, ${camera.fy}); normalize them by the viewport size, like points2d`;
221
- }
222
- if (!Array.isArray(camera.R) || camera.R.length !== 9 || !camera.R.every((value) => typeof value === "number" && Number.isFinite(value))) {
223
- return `field '${label}.camera.R' must be 9 finite numbers (3x3 row-major, world-to-camera)`;
224
- }
225
- if (!Array.isArray(camera.t) || camera.t.length !== 3 || !camera.t.every((value) => typeof value === "number" && Number.isFinite(value))) {
226
- return `field '${label}.camera.t' must be 3 finite numbers`;
227
- }
228
- if (lineEdit.prompt !== undefined && typeof lineEdit.prompt !== "string") {
229
- return `field '${label}.prompt' must be a string when present`;
230
- }
231
- return null;
232
- }
233
-
234
- /* ------------------------------------------------------------------------- */
235
- /* the request field (contract C10) */
236
- /* ------------------------------------------------------------------------- */
237
-
238
- /**
239
- * `body.replay`: the line edits to re-apply to the take this request generates.
240
- *
241
- * Each entry is a C6 payload MINUS sourceMotion — the base is the take being
242
- * generated right now, so a stored source id would name a take that is about to
243
- * be superseded. Sending one anyway is refused rather than ignored: silently
244
- * dropping it would let a client believe it chose the base.
245
- *
246
- * Returns an error message naming the offending field, or null when valid.
247
- */
248
- export function validateReplay(body, clipFrames, { seedMax = DEFAULT_SEED_MAX } = {}) {
249
- const replay = body?.replay;
250
- if (!Array.isArray(replay)) {
251
- return "field 'replay' must be an array of line-edit payloads (contract C10)";
252
- }
253
- // Exclusivity first, for the same reason C6 checks it first: a body carrying
254
- // both is two run modes, and guessing which take the replay applies to is not
255
- // the bridge's call. Plain generation, a prompt schedule and preserve all
256
- // produce ONE fresh take, which is exactly what a replay needs.
257
- for (const field of REPLAY_EXCLUSIVE) {
258
- if (body[field] !== undefined) {
259
- return (
260
- `field 'replay' cannot be combined with ${field}: contract C10 replays onto the take this request ` +
261
- "generates, and a spliced or rewritten take leaves the replay base ambiguous"
262
- );
263
- }
264
- }
265
- if (replay.length > REPLAY_MAX) {
266
- return `field 'replay' is capped at ${REPLAY_MAX} entries, got ${replay.length} (contract C10)`;
267
- }
268
- for (let index = 0; index < replay.length; index += 1) {
269
- const entry = replay[index];
270
- const label = `replay[${index}]`;
271
- if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
272
- return `field '${label}' must be an object`;
273
- }
274
- if (entry.sourceMotion !== undefined) {
275
- return (
276
- `field '${label}.sourceMotion' must be omitted: contract C10 rebinds every replay entry to the take ` +
277
- "this request generates"
278
- );
279
- }
280
- // Preview is a 20-step draft for the interactive loop. A replay is the
281
- // take's history being rebuilt, so it always runs at full quality — an
282
- // entry asking otherwise is a client bug, not a preference.
283
- if (entry.preview !== undefined) {
284
- return `field '${label}.preview' must be omitted: contract C10 replays always run at full quality`;
285
- }
286
- if (entry.seed !== undefined && (!Number.isInteger(entry.seed) || entry.seed < 0 || entry.seed > seedMax)) {
287
- return `field '${label}.seed' must be an integer in 0..${seedMax}`;
288
- }
289
- const error = validateLineEditFields(entry, clipFrames, label);
290
- if (error) return error;
291
- }
292
- return null;
293
- }
294
-
295
- /* ------------------------------------------------------------------------- */
296
- /* the boundary rule */
297
- /* ------------------------------------------------------------------------- */
298
-
299
- /**
300
- * The INTERNAL block boundaries of a generated take: the frame where one
301
- * prompt block's rollout hands over to the next.
302
- *
303
- * Why they matter (the user's catch, verified in pf-chain): block N+1 is
304
- * conditioned on block N's PRE-edit tail. An edit that does not touch a block
305
- * tail leaves that tail byte-identical, so replaying it reproduces the same
306
- * chain exactly; an edit that DOES touch a tail changes what block N+1 was
307
- * conditioned on, and the replayed take can drift after the seam.
308
- *
309
- * The clip end is excluded — there is no block after it to condition — and a
310
- * request with no `segments` field (or with fewer than two blocks in it) is a
311
- * single rollout, so it has no internal boundaries at all.
312
- *
313
- * `segments` entries are contiguous by the time they get here (the bridge
314
- * refuses anything else), so their endFrames ARE the cumulative ones.
315
- */
316
- export function blockBoundaries(segments, clipFrames) {
317
- if (!Array.isArray(segments) || segments.length < 2) return [];
318
- const boundaries = [];
319
- for (const segment of segments) {
320
- const end = segment?.endFrame;
321
- if (!Number.isInteger(end) || end <= 0 || end >= clipFrames) continue;
322
- if (!boundaries.includes(end)) boundaries.push(end);
323
- }
324
- return boundaries;
325
- }
326
-
327
- /**
328
- * Does this half-open app-frame range come close enough to a block boundary to
329
- * be warned about?
330
- *
331
- * The window is [boundary - margin, boundary + margin] INCLUSIVE and the range
332
- * is [startFrame, endFrame) — so the last constrained frame is endFrame - 1 and
333
- * the two intersect iff `startFrame <= boundary + margin` and
334
- * `endFrame - 1 >= boundary - margin`.
335
- *
336
- * The margin is small (5 frames, ~0.2 s) on purpose: the conditioning window the
337
- * next block reads is the tail of the previous one, and a warning that fires on
338
- * half the clip would be ignored. NON-BLOCKING by contract — it is a note on the
339
- * report, never a refusal.
340
- */
341
- export function boundaryWarningFor(frameRange, boundaries, margin = BOUNDARY_MARGIN) {
342
- if (!frameRange || !Array.isArray(boundaries) || boundaries.length === 0) return false;
343
- const { startFrame, endFrame } = frameRange;
344
- if (!Number.isInteger(startFrame) || !Number.isInteger(endFrame)) return false;
345
- return boundaries.some(
346
- (boundary) => startFrame <= boundary + margin && endFrame - 1 >= boundary - margin
347
- );
348
- }
349
-
350
- /* ------------------------------------------------------------------------- */
351
- /* the sequential run */
352
- /* ------------------------------------------------------------------------- */
353
-
354
- /**
355
- * Re-apply a recipe's line edits to a freshly generated take, in order.
356
- *
357
- * @param {object} options
358
- * @param {object[]} options.entries validated C10 entries, application order.
359
- * @param {string} options.takePath the fresh take entry 0 edits.
360
- * @param {string} options.artifactDir where replay-<i>.npz files are written.
361
- * @param {number[]} [options.boundaries] internal block boundaries (app frames).
362
- * @param {number} [options.boundaryMargin=5]
363
- * @param {number} [options.appFps=24]
364
- * @param {Function} options.runJob runLineEditJob, injected so the sequencing
365
- * can be tested without a GPU — the chaining and the failure policy are the
366
- * only logic here, and they are where the bugs would be.
367
- * @param {(line: string) => void} [options.onStatus]
368
- * @returns {Promise<{entries: object[], takePath: string}>} the per-entry report
369
- * (C10 shape) and the path of the LAST GOOD take — what the caller registers.
370
- */
371
- export async function runReplay({
372
- entries,
373
- takePath,
374
- artifactDir,
375
- boundaries = [],
376
- boundaryMargin = BOUNDARY_MARGIN,
377
- appFps = 24,
378
- runJob,
379
- onStatus,
380
- } = {}) {
381
- if (!Array.isArray(entries)) throw new Error("runReplay: entries must be an array");
382
- if (!takePath) throw new Error("runReplay: takePath is required");
383
- if (!artifactDir) throw new Error("runReplay: artifactDir is required");
384
- if (typeof runJob !== "function") throw new Error("runReplay: runJob is required");
385
- const status = typeof onStatus === "function" ? onStatus : () => {};
386
- const report = [];
387
- // The chain cursor. It only ever moves onto a file a job actually wrote, so a
388
- // failed entry leaves it pointing at the last take that exists on disk.
389
- let current = takePath;
390
- for (let index = 0; index < entries.length; index += 1) {
391
- const entry = entries[index];
392
- const outputPath = join(artifactDir, `replay-${index}.npz`);
393
- const boundaryWarning = boundaryWarningFor(entry.frameRange, boundaries, boundaryMargin);
394
- status(
395
- `[bridge] replay ${index + 1}/${entries.length}: ${entry.track} over frames ` +
396
- `${entry.frameRange.startFrame}..${entry.frameRange.endFrame - 1}`
397
- );
398
- if (boundaryWarning) {
399
- status(
400
- `[bridge] replay ${index + 1}/${entries.length} WARNING: frames ` +
401
- `${entry.frameRange.startFrame}..${entry.frameRange.endFrame - 1} sit within ${boundaryMargin} frames of a ` +
402
- `block boundary (${boundaries.join(", ")}); the next block was conditioned on the UNEDITED tail, ` +
403
- "so this edit may not replay exactly"
404
- );
405
- }
406
- let ok = true;
407
- let error;
408
- let seamStartDelta = null;
409
- let seamEndDelta = null;
410
- try {
411
- const meta = await runJob({
412
- lineEdit: entry,
413
- takePath: current,
414
- outputPath,
415
- appFps,
416
- ...(entry.seed === undefined ? {} : { seed: entry.seed }),
417
- onStatus: status,
418
- });
419
- // The job's own output path, not the one we asked for: they are the same
420
- // file, and reading it back from the meta keeps this loop honest about
421
- // what actually got written.
422
- current = meta?.output || outputPath;
423
- seamStartDelta = Number.isFinite(meta?.seamStartDelta) ? meta.seamStartDelta : null;
424
- seamEndDelta = Number.isFinite(meta?.seamEndDelta) ? meta.seamEndDelta : null;
425
- status(
426
- `[bridge] replay ${index + 1}/${entries.length} applied: seams ` +
427
- `${seamStartDelta === null ? "n/a" : seamStartDelta.toFixed(4)} / ` +
428
- `${seamEndDelta === null ? "n/a" : seamEndDelta.toFixed(4)} m`
429
- );
430
- } catch (err) {
431
- // LOUD, and then on with the run: the take the artist asked for exists,
432
- // and losing one refinement is recoverable by drawing it again. Losing
433
- // the whole generation is not.
434
- ok = false;
435
- error = err?.message || String(err);
436
- status(
437
- `[bridge] replay ${index + 1}/${entries.length} FAILED (${entry.track}): ${error} — ` +
438
- "continuing from the last good take"
439
- );
440
- }
441
- report.push({
442
- index,
443
- track: entry.track,
444
- ok,
445
- boundaryWarning,
446
- seamStartDelta,
447
- seamEndDelta,
448
- ...(ok ? {} : { error }),
449
- });
450
- }
451
- return { entries: report, takePath: current };
452
- }