cozyclay 2.1.1 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (585) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/README.md +12 -30
  3. package/bin/agent/agent-routes.mjs +43 -116
  4. package/bin/agent/agent-runner.mjs +14 -15
  5. package/bin/agent/cliproxy-provider.mjs +135 -0
  6. package/bin/agent/credential-store.mjs +30 -7
  7. package/bin/agent/pi-tools.mjs +4 -1
  8. package/bin/agent/provider-keys.mjs +32 -0
  9. package/bin/agent/provider-login.mjs +110 -0
  10. package/bin/agent/provider-routes.mjs +99 -0
  11. package/bin/agent/providers.mjs +19 -97
  12. package/bin/agent/script-tool.mjs +17 -4
  13. package/bin/agent/session-store.mjs +2 -2
  14. package/bin/agent/studio-context-budget.mjs +93 -0
  15. package/bin/agent/studio-motion-reference.mjs +23 -0
  16. package/bin/agent/studio-prompt.mjs +1 -1
  17. package/bin/agent/studio-rebase-text.mjs +32 -0
  18. package/bin/agent/studio-rebase.mjs +26 -39
  19. package/bin/agent/studio-tools.mjs +9 -28
  20. package/bin/agent/tool-arguments.mjs +49 -0
  21. package/bin/agent/turn-frames.mjs +66 -0
  22. package/bin/codex-auth.mjs +9 -2
  23. package/bin/cozyclay.mjs +10 -116
  24. package/bin/live/cli.mjs +3 -3
  25. package/bin/mcp-runtime.mjs +2 -12
  26. package/bin/package-signature.mjs +0 -4
  27. package/dist/app/index.html +7 -6
  28. package/dist/assets/EXRLoader-CvVIvGVE.js +1 -0
  29. package/dist/assets/analytics-BWCqdgWA.js +1 -0
  30. package/dist/assets/app-DfTCxaKk.js +5087 -0
  31. package/dist/assets/app-f-fkV5uP.css +1 -0
  32. package/dist/assets/compile-worker-BxKXh07s.js +5 -0
  33. package/dist/assets/fflate.module-KbGj5-H6.js +5 -0
  34. package/dist/assets/{first-shot-handoff-msLI71g1.js → first-shot-handoff-DNO8kv_N.js} +1 -1
  35. package/dist/assets/{landing-ChUvB_zL.js → landing-yfZaJkWc.js} +1 -1
  36. package/dist/assets/scene-asset-sync-U3kS2rvK.js +20 -0
  37. package/dist/assets/scene-asset-sync-_S8NQl5B.css +1 -0
  38. package/dist/assets/workflow-D78eiKTa.js +179 -0
  39. package/dist/cozyclay-package.json +1 -1
  40. package/dist/index.html +3 -3
  41. package/dist/sw.js +3 -6
  42. package/dist/workflow/index.html +5 -4
  43. package/mcp/LIVE-PROTOCOL.md +7 -47
  44. package/mcp/README.md +16 -26
  45. package/mcp/live-hub.mjs +6 -10
  46. package/mcp/package.json +1 -3
  47. package/mcp/tool-handlers.mjs +4 -168
  48. package/package.json +3 -22
  49. package/src/App.jsx +151 -2625
  50. package/src/agent-progress.js +80 -0
  51. package/src/agent-turn-mark.js +30 -0
  52. package/src/analytics.js +1 -19
  53. package/src/app-context.js +12 -2
  54. package/src/app-stage.jsx +2 -34
  55. package/src/asset-pane.jsx +6 -5
  56. package/src/camera-block.js +21 -0
  57. package/src/camera-direction.js +120 -0
  58. package/src/character-kind.js +3 -9
  59. package/src/command-bus.js +10 -8
  60. package/src/commands/cast.js +5 -34
  61. package/src/commands/elements/character.js +1 -1
  62. package/src/commands/elements/motion.js +1 -6
  63. package/src/commands/elements/object.js +6 -2
  64. package/src/commands/elements/shot.js +4 -1
  65. package/src/commands/elements.js +38 -14
  66. package/src/commands/index.js +3 -1
  67. package/src/commands/kits.js +37 -0
  68. package/src/commands/motion-timing.js +57 -0
  69. package/src/commands/motion.js +9 -152
  70. package/src/commands/motion3-compile.js +65 -0
  71. package/src/commands/motion3.js +214 -0
  72. package/src/commands/objects.js +6 -4
  73. package/src/commands/shot.js +55 -13
  74. package/src/commands/stage.js +14 -1
  75. package/src/commands/view.js +4 -1
  76. package/src/document-mirrors.js +42 -0
  77. package/src/document-store.js +4 -0
  78. package/src/domains/cast.js +19 -194
  79. package/src/domains/motion/motion-range-pin-rules.js +1 -1
  80. package/src/domains/motion/take-source.js +53 -0
  81. package/src/domains/motion.js +60 -1838
  82. package/src/domains/scenes.js +12 -55
  83. package/src/domains/shots.js +2 -2
  84. package/src/domains/stage.js +4 -2
  85. package/src/draft-ghost.js +47 -0
  86. package/src/draft-ghost.jsx +129 -0
  87. package/src/dualview.jsx +4 -3
  88. package/src/effects/EffectsLayer.jsx +112 -0
  89. package/src/effects/effect-meshes.js +232 -0
  90. package/src/effects/effect-record.js +103 -0
  91. package/src/effects/emitter.js +59 -0
  92. package/src/effects/simulate.js +261 -0
  93. package/src/effects/state.js +59 -0
  94. package/src/execution-telemetry.js +2 -3
  95. package/src/first-success-guide.jsx +5 -5
  96. package/src/ghost-set.js +257 -0
  97. package/src/held-object-edit.jsx +113 -0
  98. package/src/held-object-pose.js +175 -0
  99. package/src/hierarchy-model.js +1 -1
  100. package/src/hierarchy-panel.jsx +1 -1
  101. package/src/imported-mesh.jsx +103 -0
  102. package/src/kits/held.js +84 -0
  103. package/src/kits/index.js +70 -0
  104. package/src/kits/smoking.js +57 -0
  105. package/src/light-blockers.js +93 -0
  106. package/src/motion/beat-board-model.js +84 -0
  107. package/src/motion/beat-board.css +124 -0
  108. package/src/motion/beat-board.jsx +60 -0
  109. package/src/motion/center-of-mass.js +66 -0
  110. package/src/motion/fix-collisions.js +850 -0
  111. package/src/motion/physics-review.js +118 -0
  112. package/src/{ardy → motion}/physics-support.js +1 -76
  113. package/src/motion/rig-snapshot.js +22 -0
  114. package/src/motion/take-segments.jsx +140 -0
  115. package/src/{ardy → motion}/timeline-coordinates.js +0 -21
  116. package/src/{ardy → motion}/timeline.css +1 -2
  117. package/src/{ardy → motion}/timeline.jsx +42 -413
  118. package/src/motion/waypoints.js +76 -0
  119. package/src/motion3/beats.js +83 -0
  120. package/src/motion3/body.js +158 -0
  121. package/src/motion3/bones.js +47 -0
  122. package/src/motion3/carried.js +47 -0
  123. package/src/motion3/compile-job.js +79 -0
  124. package/src/motion3/compile-worker.js +10 -0
  125. package/src/motion3/compiler/anchors.js +113 -0
  126. package/src/motion3/compiler/arm-orient.js +128 -0
  127. package/src/motion3/compiler/channels.js +125 -0
  128. package/src/motion3/compiler/compile.js +205 -0
  129. package/src/motion3/compiler/gait-rhythm.js +97 -0
  130. package/src/motion3/compiler/grip.js +171 -0
  131. package/src/motion3/compiler/head-steady.js +48 -0
  132. package/src/motion3/compiler/hold.js +134 -0
  133. package/src/motion3/compiler/idle.js +113 -0
  134. package/src/motion3/compiler/limbs.js +197 -0
  135. package/src/motion3/compiler/math.js +72 -0
  136. package/src/motion3/compiler/pose.js +177 -0
  137. package/src/motion3/compiler/quat.js +49 -0
  138. package/src/motion3/compiler/skeleton.js +74 -0
  139. package/src/motion3/compiler/validate-common.js +32 -0
  140. package/src/motion3/compiler/validate-goals.js +118 -0
  141. package/src/motion3/compiler/validate-objects.js +144 -0
  142. package/src/motion3/compiler/validate-parts.js +118 -0
  143. package/src/motion3/compiler/validate.js +122 -0
  144. package/src/motion3/compiler/vocab.js +74 -0
  145. package/src/motion3/compiler/walk-pelvis.js +275 -0
  146. package/src/motion3/compiler/walk.js +207 -0
  147. package/src/motion3/compiler/wrist-clamp.js +32 -0
  148. package/src/motion3/delta.js +47 -0
  149. package/src/motion3/describe.js +177 -0
  150. package/src/motion3/docs.js +27 -0
  151. package/src/motion3/edit-carry.js +21 -0
  152. package/src/motion3/fix/ease.js +16 -0
  153. package/src/motion3/fix/ground.js +37 -0
  154. package/src/motion3/fix/index.js +52 -0
  155. package/src/motion3/fix/push.js +71 -0
  156. package/src/motion3/fix/reach.js +46 -0
  157. package/src/motion3/fix/retime.js +79 -0
  158. package/src/motion3/fix/self.js +211 -0
  159. package/src/motion3/fix/slide.js +70 -0
  160. package/src/motion3/fix/turn.js +51 -0
  161. package/src/motion3/follow.js +39 -0
  162. package/src/motion3/free-motion.js +204 -0
  163. package/src/motion3/grip-choice.js +130 -0
  164. package/src/motion3/hold-plan.js +157 -0
  165. package/src/motion3/json-patch.js +77 -0
  166. package/src/motion3/key-aim.js +105 -0
  167. package/src/motion3/key-edit.js +82 -0
  168. package/src/motion3/lenient.js +127 -0
  169. package/src/motion3/object-keys.js +236 -0
  170. package/src/motion3/object-pose.js +182 -0
  171. package/src/motion3/object-tracks.js +12 -0
  172. package/src/motion3/people.js +82 -0
  173. package/src/motion3/pipeline.js +102 -0
  174. package/src/motion3/playback.js +51 -0
  175. package/src/motion3/probes.js +91 -0
  176. package/src/motion3/qa/body-checks.js +34 -0
  177. package/src/motion3/qa/check-names.js +14 -0
  178. package/src/motion3/qa/checks.js +125 -0
  179. package/src/motion3/qa/contact-checks.js +51 -0
  180. package/src/motion3/qa/explain.js +80 -0
  181. package/src/motion3/qa/fmt.js +5 -0
  182. package/src/motion3/qa/foot-checks.js +72 -0
  183. package/src/motion3/qa/geometry.js +77 -0
  184. package/src/motion3/qa/goals.js +191 -0
  185. package/src/motion3/qa/ground.js +60 -0
  186. package/src/motion3/qa/hints-contact.js +195 -0
  187. package/src/motion3/qa/hints-goal.js +160 -0
  188. package/src/motion3/qa/hints-knee.js +24 -0
  189. package/src/motion3/qa/hints-motion.js +209 -0
  190. package/src/motion3/qa/hints-object.js +91 -0
  191. package/src/motion3/qa/index.js +44 -0
  192. package/src/motion3/qa/key-checks.js +82 -0
  193. package/src/motion3/qa/near.js +96 -0
  194. package/src/motion3/qa/object-checks.js +135 -0
  195. package/src/motion3/qa/patch.js +161 -0
  196. package/src/motion3/qa/people-checks.js +37 -0
  197. package/src/motion3/qa/severity.js +38 -0
  198. package/src/motion3/qa/spec-checks.js +65 -0
  199. package/src/motion3/qa/style.js +228 -0
  200. package/src/motion3/qa/wrist-checks.js +69 -0
  201. package/src/motion3/qa/wrist-obs.js +60 -0
  202. package/src/motion3/refs.js +129 -0
  203. package/src/motion3/repair-tune.js +121 -0
  204. package/src/motion3/repair.js +83 -0
  205. package/src/motion3/report.js +67 -0
  206. package/src/motion3/rigs/index.js +14 -0
  207. package/src/motion3/rigs/x-bot-tpose-body.json +1 -0
  208. package/src/motion3/rigs/x-bot-tpose.json +1 -0
  209. package/src/motion3/rigs/y-bot-tpose-body.json +1 -0
  210. package/src/motion3/rigs/y-bot-tpose.json +1 -0
  211. package/src/motion3/scene.js +60 -0
  212. package/src/motion3/schema/brief.js +89 -0
  213. package/src/motion3/schema/fields.js +44 -0
  214. package/src/motion3/schema/spec-examples.js +46 -0
  215. package/src/motion3/schema/topics.js +113 -0
  216. package/src/motion3/schema/types.js +149 -0
  217. package/src/motion3/search.js +117 -0
  218. package/src/motion3/second-hand.js +112 -0
  219. package/src/motion3/settle.js +136 -0
  220. package/src/motion3/skills/basic.js +83 -0
  221. package/src/motion3/skills/book.js +76 -0
  222. package/src/motion3/skills/car.js +145 -0
  223. package/src/motion3/skills/flip.js +200 -0
  224. package/src/motion3/skills/frames-auto.js +29 -0
  225. package/src/motion3/skills/impact.js +109 -0
  226. package/src/motion3/skills/index.js +85 -0
  227. package/src/motion3/skills/info.js +80 -0
  228. package/src/motion3/skills/smoke-kit.js +109 -0
  229. package/src/motion3/skills/smoke.js +160 -0
  230. package/src/motion3/spec-store.js +59 -0
  231. package/src/motion3/take-edit.js +155 -0
  232. package/src/motion3/take.js +63 -0
  233. package/src/motion3/timeline.js +127 -0
  234. package/src/motion3/views.js +121 -0
  235. package/src/motion3/walk-plan.js +119 -0
  236. package/src/object-catalog.jsx +7 -1
  237. package/src/object-colors.js +98 -0
  238. package/src/object-gizmo-parts.js +115 -0
  239. package/src/object-gizmo.jsx +37 -122
  240. package/src/object-glow.js +139 -0
  241. package/src/object-hidden.js +33 -0
  242. package/src/object-look.js +196 -0
  243. package/src/object-size.js +10 -0
  244. package/src/object-travel.js +71 -6
  245. package/src/panels/CameraPanel.jsx +7 -7
  246. package/src/panels/HdrEnvironmentControls.jsx +59 -0
  247. package/src/panels/LightPanel.jsx +25 -4
  248. package/src/panels/ObjectLookPanel.jsx +88 -0
  249. package/src/panels/PosePanel.jsx +2 -17
  250. package/src/panels/RigControlPanel.jsx +5 -53
  251. package/src/panels/SubjectBox.jsx +4 -1
  252. package/src/panels/details.css +14 -0
  253. package/src/posestudio.jsx +4 -27
  254. package/src/project-browser.jsx +3 -4
  255. package/src/project-resources.js +18 -31
  256. package/src/project.js +13 -104
  257. package/src/props.jsx +25 -280
  258. package/src/qa-label.js +25 -0
  259. package/src/rail-dot-drag.js +23 -0
  260. package/src/render-passes.js +10 -0
  261. package/src/resource-status.jsx +1 -0
  262. package/src/result-modal.jsx +4 -19
  263. package/src/room.jsx +20 -12
  264. package/src/scene-assets.js +8 -6
  265. package/src/scene-light-place.js +72 -0
  266. package/src/scene-lights.jsx +148 -0
  267. package/src/scene-objects.js +31 -139
  268. package/src/scenes.js +15 -105
  269. package/src/semantic-edit.js +2 -5
  270. package/src/set-pieces.jsx +183 -0
  271. package/src/settings-menu.jsx +3 -11
  272. package/src/shell/BottomDock.jsx +9 -51
  273. package/src/shell/DetailsSlot.jsx +16 -200
  274. package/src/shell/GhostsMenuGroup.jsx +74 -0
  275. package/src/shell/LibrarySlot.jsx +1 -3
  276. package/src/shell/MenuBar.jsx +3 -3
  277. package/src/shell/PreferencesDialog.jsx +15 -264
  278. package/src/shell/PreferencesSlot.jsx +2 -8
  279. package/src/shell/TopBar.jsx +5 -84
  280. package/src/shell/ViewportToolbar.jsx +10 -70
  281. package/src/shell/agent-glass.css +19 -6
  282. package/src/shell/details-selection.js +42 -0
  283. package/src/shell/ghosts-menu.css +22 -0
  284. package/src/shell/glass-regions.css +29 -3
  285. package/src/shell/preferences.css +6 -1
  286. package/src/shot-authoring.js +4 -7
  287. package/src/soft-shadows.js +37 -0
  288. package/src/stage-attach-frames.js +12 -12
  289. package/src/stage-canvas-rigs.jsx +3 -3
  290. package/src/stage-capture.jsx +42 -8
  291. package/src/stage-characters.jsx +22 -216
  292. package/src/stage-editor-gizmos.jsx +6 -6
  293. package/src/stage-environment.js +93 -0
  294. package/src/stage-hdr-builtins.js +107 -0
  295. package/src/stage-hdr-image.js +116 -0
  296. package/src/stage-hdr-library.js +70 -0
  297. package/src/stage-hdr-map.js +94 -0
  298. package/src/stage-hdr.js +130 -0
  299. package/src/stage-lighting.js +149 -0
  300. package/src/stage-occlusion.js +210 -0
  301. package/src/stage-sky.jsx +159 -0
  302. package/src/stage-startup-defaults.js +5 -9
  303. package/src/studio-actions.js +16 -14
  304. package/src/studio-agent-commands.js +19 -16
  305. package/src/studio-agent-motion.js +9 -9
  306. package/src/studio-agent-protocol.js +47 -77
  307. package/src/studio-app-binding.js +27 -47
  308. package/src/studio-asset-list.js +17 -0
  309. package/src/studio-contact-sheet.js +37 -5
  310. package/src/studio-elements.js +27 -7
  311. package/src/studio-entity-bounds.js +18 -0
  312. package/src/studio-error-codes.js +15 -0
  313. package/src/studio-export-framing.js +152 -0
  314. package/src/studio-lighting-check.js +103 -0
  315. package/src/studio-motion-visual.js +150 -0
  316. package/src/studio-schema-errors.js +80 -0
  317. package/src/studio-support-gap.js +74 -0
  318. package/src/styles.css +1 -19
  319. package/src/timeline-defaults.js +5 -0
  320. package/src/timeline-extent.js +8 -20
  321. package/src/tutorial-walk.js +17 -0
  322. package/src/ui-scrub.js +1 -1
  323. package/src/use-shot-camera-tracks.js +12 -0
  324. package/src/view-ghosts.js +97 -0
  325. package/src/view-ghosts.jsx +49 -0
  326. package/src/workflow/AgentPanel.jsx +47 -166
  327. package/src/workflow/AgentProgressCard.jsx +109 -0
  328. package/src/workflow/AgentReplyText.jsx +37 -0
  329. package/src/workflow/AgentTurnSteps.jsx +84 -0
  330. package/src/workflow/LoginPanel.jsx +140 -0
  331. package/src/workflow/WorkflowBuilder.jsx +7 -23
  332. package/src/workflow/agent-client.js +54 -51
  333. package/src/workflow/agent-panel.css +71 -1
  334. package/src/workflow/agent-progress-card.css +167 -0
  335. package/src/workflow/agent-progress-view.js +47 -0
  336. package/src/workflow/agent-turn-steps.css +113 -0
  337. package/src/workflow/agent-turn.js +191 -0
  338. package/src/workflow/login-client.js +34 -0
  339. package/src/workflow/motion-input.js +18 -31
  340. package/src/workflow/node-schema.js +1 -1
  341. package/src/workflow/reply-markdown.js +69 -0
  342. package/src/workflow/scene-asset-sync.js +8 -8
  343. package/tools/agent-session-stats.mjs +61 -0
  344. package/tools/dev-full.mjs +5 -56
  345. package/tools/eslint-baseline.json +12 -13
  346. package/tools/knip-baseline.json +0 -179
  347. package/tools/loc-baseline.json +31 -81
  348. package/tools/motion3/extract-body.mjs +103 -0
  349. package/tools/motion3/extract-rig.mjs +85 -0
  350. package/tools/process-supervisor.mjs +0 -88
  351. package/tools/run-tests.mjs +9 -125
  352. package/bin/agent/motion-runtime.mjs +0 -76
  353. package/dist/ardy/cskel27-rest.json +0 -557
  354. package/dist/assets/analytics-C9pMpW7s.js +0 -1
  355. package/dist/assets/app-B_2XB9rw.css +0 -1
  356. package/dist/assets/app-DAuvKPP8.js +0 -4920
  357. package/dist/assets/config-Bx7pYsD9.js +0 -1
  358. package/dist/assets/config-DeoFTYzb.css +0 -1
  359. package/dist/assets/demo-CvKuBgy5.js +0 -1
  360. package/dist/assets/scene-asset-sync-DKkpjKBV.js +0 -19
  361. package/dist/assets/scene-asset-sync-DjJ_cJ2R.css +0 -1
  362. package/dist/assets/ticket-BPk2Ni95.js +0 -1
  363. package/dist/assets/workflow-jJRBR94Z.js +0 -179
  364. package/dist/demo/walk-then-stop.npz +0 -0
  365. package/mcp/ardy-prompts.mjs +0 -218
  366. package/src/ardy/auto-fix-panel.css +0 -108
  367. package/src/ardy/auto-physics.js +0 -1194
  368. package/src/ardy/client.js +0 -153
  369. package/src/ardy/convert.js +0 -313
  370. package/src/ardy/cskel27-neutral.js +0 -39
  371. package/src/ardy/cskel27.js +0 -77
  372. package/src/ardy/export.js +0 -165
  373. package/src/ardy/fix-collisions.js +0 -2257
  374. package/src/ardy/foot-lock.js +0 -230
  375. package/src/ardy/motion-calibration.js +0 -118
  376. package/src/ardy/motion-edit.js +0 -213
  377. package/src/ardy/motion-url.js +0 -55
  378. package/src/ardy/mp4-duration.js +0 -102
  379. package/src/ardy/npz.js +0 -595
  380. package/src/ardy/physics-panel.css +0 -21
  381. package/src/ardy/physics-panel.jsx +0 -89
  382. package/src/ardy/physics-refine.js +0 -111
  383. package/src/ardy/physics-review.js +0 -567
  384. package/src/ardy/physics-temporal.js +0 -54
  385. package/src/ardy/platform-fit-panel.css +0 -64
  386. package/src/ardy/platform-fit-panel.jsx +0 -41
  387. package/src/ardy/platform-fit.js +0 -326
  388. package/src/ardy/playback.js +0 -711
  389. package/src/ardy/pose-pin.js +0 -97
  390. package/src/ardy/prompt-clips.js +0 -30
  391. package/src/ardy/retime.js +0 -211
  392. package/src/ardy/root-drop.js +0 -369
  393. package/src/ardy/timeline-resize.js +0 -13
  394. package/src/ardy/to-cskel27.js +0 -225
  395. package/src/ardy/trim.js +0 -33
  396. package/src/ardy/waypoints.js +0 -369
  397. package/src/ardy-bridge-contract.js +0 -281
  398. package/src/domains/motion/motion-video-capture.js +0 -664
  399. package/src/i2v-motion-client.js +0 -77
  400. package/src/i2v-motion-studio.jsx +0 -180
  401. package/src/line-edit.js +0 -1348
  402. package/src/motion/generation.js +0 -105
  403. package/src/motion-readiness-ui.jsx +0 -67
  404. package/src/motion-readiness.js +0 -40
  405. package/src/motion-resources.js +0 -177
  406. package/src/motion-store.js +0 -65
  407. package/src/motion-trail.js +0 -601
  408. package/src/multimodel-ingest.js +0 -394
  409. package/src/panels/PromptBlocksPanel.jsx +0 -444
  410. package/src/panels/VideoCapturePanel.jsx +0 -181
  411. package/src/pose-extract/browser.js +0 -42
  412. package/src/pose-extract/detector.js +0 -100
  413. package/src/pose-extract/fit.js +0 -227
  414. package/src/pose-extract/image-frame.js +0 -51
  415. package/src/pose-extract/index.js +0 -58
  416. package/src/pose-extract/keyframes.js +0 -74
  417. package/src/pose-extract/landmarks.js +0 -162
  418. package/src/pose-extract/mirror.js +0 -153
  419. package/src/pose-extract/take.js +0 -131
  420. package/src/pose-extract/video-frames.js +0 -91
  421. package/src/take-recipe.js +0 -165
  422. package/src/trail-key-conflicts.js +0 -39
  423. package/src/trail-pick.js +0 -59
  424. package/src/use-i2v-capture-modal.js +0 -15
  425. package/tools/ardy/BRIDGE.md +0 -37
  426. package/tools/ardy/GVHMR-NPZ.md +0 -68
  427. package/tools/ardy/README.md +0 -124
  428. package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
  429. package/tools/ardy/artifacts.mjs +0 -34
  430. package/tools/ardy/bridge.mjs +0 -1732
  431. package/tools/ardy/bvh-cskel27.mjs +0 -1265
  432. package/tools/ardy/cclay_constrained_generate.py +0 -561
  433. package/tools/ardy/cclay_gvhmr_worker.py +0 -264
  434. package/tools/ardy/cclay_motion_edit.py +0 -510
  435. package/tools/ardy/cclay_sequence_generate.py +0 -705
  436. package/tools/ardy/cclay_worker.py +0 -304
  437. package/tools/ardy/dump-npz.py +0 -205
  438. package/tools/ardy/extract-rest.mjs +0 -299
  439. package/tools/ardy/extract.mjs +0 -507
  440. package/tools/ardy/footage.mjs +0 -467
  441. package/tools/ardy/gvhmr-floor.mjs +0 -102
  442. package/tools/ardy/gvhmr-to-cskel27.mjs +0 -21
  443. package/tools/ardy/gvhmr_fastpath.py +0 -168
  444. package/tools/ardy/gvhmr_trajectory.py +0 -240
  445. package/tools/ardy/merge-text-encoder.py +0 -160
  446. package/tools/ardy/mocap-metrics.mjs +0 -200
  447. package/tools/ardy/mocap-quality-gate.mjs +0 -110
  448. package/tools/ardy/motion-stabilize.mjs +0 -348
  449. package/tools/ardy/npz.mjs +0 -414
  450. package/tools/ardy/pose-to-npz.mjs +0 -106
  451. package/tools/ardy/prompt-limits.mjs +0 -5
  452. package/tools/ardy/run-edit-on-box.sh +0 -73
  453. package/tools/ardy/run-local.mjs +0 -233
  454. package/tools/ardy/run-on-box.sh +0 -593
  455. package/tools/ardy/run-sequence-on-box.sh +0 -177
  456. package/tools/ardy/runners/gvhmr-worker.mjs +0 -206
  457. package/tools/ardy/runners/index.mjs +0 -18
  458. package/tools/ardy/runners/local.mjs +0 -455
  459. package/tools/ardy/runners/proc.mjs +0 -125
  460. package/tools/ardy/runners/remote.mjs +0 -252
  461. package/tools/ardy/setup-local.mjs +0 -189
  462. package/tools/ardy/setup-text-encoder-on-box.sh +0 -93
  463. package/tools/ardy/setup-text-encoder.py +0 -306
  464. package/tools/ardy/smpl-cskel27.mjs +0 -178
  465. package/tools/ardy/visual-qa.mjs +0 -196
  466. package/tools/ardy/vq-car.mjs +0 -52
  467. package/tools/bench/EXP3.md +0 -61
  468. package/tools/bench/cclay_bench_extract_incam.py +0 -125
  469. package/tools/bench/cclay_bench_extract_obs.py +0 -204
  470. package/tools/bench/cclay_bench_runner.py +0 -42
  471. package/tools/bench/cube-contact.mjs +0 -162
  472. package/tools/bench/exp3.mjs +0 -152
  473. package/tools/bench/extract-bench-lib.mjs +0 -216
  474. package/tools/bench/extract-bench.mjs +0 -302
  475. package/tools/bench/fal-generate.mjs +0 -88
  476. package/tools/bench/fit/README.md +0 -189
  477. package/tools/bench/fit/camera.mjs +0 -59
  478. package/tools/bench/fit/contact.mjs +0 -419
  479. package/tools/bench/fit/footlock.mjs +0 -234
  480. package/tools/bench/fit/motion.mjs +0 -85
  481. package/tools/bench/fit/pin.mjs +0 -27
  482. package/tools/bench/fit/remote.mjs +0 -105
  483. package/tools/bench/fit-bench.mjs +0 -126
  484. package/tools/bench/fit-sanity.mjs +0 -45
  485. package/tools/bench/metrics.mjs +0 -313
  486. package/tools/bench/obs/depth.mjs +0 -258
  487. package/tools/bench/obs/extrinsics.mjs +0 -175
  488. package/tools/bench/obs/fit_mannequin_betas.py +0 -340
  489. package/tools/bench/obs/ground.mjs +0 -294
  490. package/tools/bench/obs/heading.mjs +0 -151
  491. package/tools/bench/obs/ladder.mjs +0 -555
  492. package/tools/bench/obs/mannequin-betas.json +0 -124
  493. package/tools/bench/obs/remote.mjs +0 -83
  494. package/tools/bench/obs/rest_joints.py +0 -52
  495. package/tools/bench/obs/ybot-targets.mjs +0 -49
  496. package/tools/bench/obs-bench.mjs +0 -520
  497. package/tools/bench/score.mjs +0 -343
  498. package/tools/bench/summarize.mjs +0 -86
  499. package/tools/demo-worker/README.md +0 -137
  500. package/tools/demo-worker/api-client.mjs +0 -233
  501. package/tools/demo-worker/generate.mjs +0 -301
  502. package/tools/demo-worker/index.mjs +0 -466
  503. package/tools/demo-worker/sign-core.mjs +0 -112
  504. package/tools/demo-worker/sign.mjs +0 -161
  505. package/tools/gt-render/browser.mjs +0 -182
  506. package/tools/gt-render/camera-math.mjs +0 -276
  507. package/tools/gt-render/page.mjs +0 -267
  508. package/tools/gt-render/render.mjs +0 -514
  509. package/tools/gt-render/scene-box.mjs +0 -31
  510. package/tools/gt-render/take-transform.mjs +0 -87
  511. package/tools/kimodo/compare-seam.mjs +0 -108
  512. package/tools/kimodo/constraints.mjs +0 -244
  513. package/tools/kimodo/edit.mjs +0 -259
  514. package/tools/kimodo/effector-constraints.mjs +0 -200
  515. package/tools/kimodo/generate.mjs +0 -646
  516. package/tools/kimodo/local-output.mjs +0 -265
  517. package/tools/kimodo/measure-pose.mjs +0 -65
  518. package/tools/kimodo/measure-preserve.mjs +0 -899
  519. package/tools/kimodo/measure-waypoints.mjs +0 -55
  520. package/tools/kimodo/mlx-generate.py +0 -111
  521. package/tools/kimodo/pose-constraints.mjs +0 -248
  522. package/tools/kimodo/preserve-mask.mjs +0 -543
  523. package/tools/kimodo/read-npz.mjs +0 -189
  524. package/tools/kimodo/run-edit-on-box.mjs +0 -155
  525. package/tools/kimodo/run-sequence-on-box.mjs +0 -178
  526. package/tools/kimodo/runner.mjs +0 -284
  527. package/tools/kimodo/seam-batch.mjs +0 -77
  528. package/tools/kimodo/setup-local.mjs +0 -30
  529. package/tools/kimodo/setup-on-box.sh +0 -339
  530. package/tools/kimodo/soma77-to-cskel27.mjs +0 -305
  531. package/tools/morphgs/assets/gen_truth.py +0 -39
  532. package/tools/morphgs/assets/mesh_ori_rig.txt +0 -27
  533. package/tools/morphgs/assets/playback-check.mjs +0 -30
  534. package/tools/morphgs/demo-gate.sh +0 -26
  535. package/tools/morphgs/fbx2morphgs.mjs +0 -68
  536. package/tools/morphgs/morphgs-to-cskel27.mjs +0 -105
  537. package/tools/morphgs/patches/preprocess_src-none-mode.patch +0 -76
  538. package/tools/morphgs/setup-on-cluster.sh +0 -126
  539. package/tools/projflow/driver.py +0 -1069
  540. package/tools/projflow/generate.mjs +0 -805
  541. package/tools/projflow/hml22-to-cskel27.mjs +0 -735
  542. package/tools/projflow/line-edit-job.mjs +0 -547
  543. package/tools/projflow/replay.mjs +0 -452
  544. package/tools/projflow/runner.mjs +0 -269
  545. package/tools/projflow/service.mjs +0 -742
  546. package/tools/track/DESIGN.md +0 -513
  547. package/tools/track/backfill-provenance.mjs +0 -116
  548. package/tools/track/budget.mjs +0 -8
  549. package/tools/track/check-rig.mjs +0 -209
  550. package/tools/track/diagnostics.schema.json +0 -60
  551. package/tools/track/export-rig.mjs +0 -256
  552. package/tools/track/fallback.mjs +0 -7
  553. package/tools/track/fk-parity-fixture.mjs +0 -161
  554. package/tools/track/gate.mjs +0 -349
  555. package/tools/track/masks.mjs +0 -318
  556. package/tools/track/metrics.mjs +0 -201
  557. package/tools/track/publish-obs.mjs +0 -108
  558. package/tools/track/py/check_env.py +0 -61
  559. package/tools/track/py/eval_lr.py +0 -229
  560. package/tools/track/py/lr_viterbi.py +0 -294
  561. package/tools/track/py/masks.py +0 -405
  562. package/tools/track/py/objective.py +0 -382
  563. package/tools/track/py/rig.py +0 -508
  564. package/tools/track/py/scene.py +0 -326
  565. package/tools/track/py/test_joint_indices.py +0 -123
  566. package/tools/track/py/test_lr_viterbi.py +0 -195
  567. package/tools/track/py/test_masks.py +0 -229
  568. package/tools/track/py/test_rig.py +0 -288
  569. package/tools/track/py/test_scene.py +0 -354
  570. package/tools/track/py/test_track.py +0 -328
  571. package/tools/track/py/track.py +0 -411
  572. package/tools/track/remote.mjs +0 -140
  573. package/tools/track/rig-dump.mjs +0 -227
  574. package/tools/track/run-box-tests.mjs +0 -14
  575. package/tools/track/run-box.mjs +0 -168
  576. package/tools/track/setup-box.sh +0 -59
  577. package/tools/track/study-2d.mjs +0 -728
  578. /package/src/{ardy → motion}/collision-blockers.js +0 -0
  579. /package/src/{ardy → motion}/ground.js +0 -0
  580. /package/src/{ardy → motion}/ik-drag.js +0 -0
  581. /package/src/{ardy → motion}/ik-key-json.js +0 -0
  582. /package/src/{ardy → motion}/ik.js +0 -0
  583. /package/src/{ardy → motion}/physics-surface.js +0 -0
  584. /package/src/{ardy → motion}/playback-clock.js +0 -0
  585. /package/src/{ardy → motion}/range-pin.js +0 -0
@@ -1,805 +0,0 @@
1
- /**
2
- * generate.mjs — drive the ProjFlow line-edit driver on the configured box and
3
- * bring the result back as raw hml22 joint positions.
4
- *
5
- * Shaped after tools/kimodo/generate.mjs (ssh/scp helpers, error style, one
6
- * mkdtemp per run that the `finally` always removes), with one structural
7
- * difference stated up front: Kimodo ships a CLI and we only pass it flags, so
8
- * that module has no python of its own. ProjFlow ships two samplers and NEITHER
9
- * CLI can express a CozyClay line edit — the projflow inpainting sampler
10
- * preserves source frames but has no camera, the linear-constraint sampler takes
11
- * a general operator but its demo builds no preserve rows. Contract C7 therefore
12
- * puts a driver of ours on the box, and this module SHIPS it per run
13
- * (tools/projflow/driver.py), the same way the Kimodo wrapper ships constraint
14
- * JSONs. Nothing is installed on the box; the run directory is the whole
15
- * footprint.
16
- *
17
- * THE NUMBERS THIS MODULE DOES NOT COMPUTE. Every constraint row — the affine
18
- * perspective rows for the drawn line, the identity rows that freeze the rest of
19
- * the take — is built inside driver.py, because that is where the model's
20
- * normalisation stats and column layout live. This side does exactly three
21
- * things: validate the request hard enough that a malformed line never costs a
22
- * GPU round trip, move files, and read the result back.
23
- *
24
- * SEAM: THIS MODULE RETURNS 22-JOINT POSITIONS, NOT cskel27. ACMDM's "Raw"
25
- * family generates absolute coordinates with no rotation channel at all, so the
26
- * conversion to cskel27 has to LIFT rotations (aim-based bone frames + inherited
27
- * twist) rather than re-express them. That is contract C8 and agent M2's module,
28
- * tools/projflow/hml22-to-cskel27.mjs, which is in flight in parallel with this
29
- * one. It is deliberately NOT imported here: wave-2 integration composes
30
- * `lineEditOnBox(...).positions` with `hml22ToCskel27Motion(...)`, so the two
31
- * modules can land independently and this one stays testable without a
32
- * converter. Do not add the import until that module exists.
33
- *
34
- * FRAME CLOCKS. `line.frameRange` is in GENERATION frames at 20 fps, which is
35
- * what the model samples and what driver.py indexes. The app's clip frames (24
36
- * fps) are converted upstream, in the bridge — the same split Kimodo uses, where
37
- * generate.mjs owns the generation clock and the bridge owns the app clock.
38
- * There is no conversion in this file, on purpose: two places computing the same
39
- * frame index is exactly how they drift.
40
- *
41
- * TWO ROUTES, ONE FUNCTION (contract C11). `lineEditOnBox` first offers the
42
- * request to the RESIDENT service (service.mjs): a warm `driver.py --serve`
43
- * child that already holds the checkpoint, which removes the 3.9 s model load
44
- * that gate GP3 measured as half of an 8 s round trip. If anything at all goes
45
- * wrong with that child — it is not up, it dies, it answers late, it answers
46
- * with the wrong id, it refuses the request — the request runs down the COLD
47
- * path below, unchanged, which is the contract of record. The cold path does
48
- * not know the resident exists and never will; that is what makes the fallback
49
- * trustworthy rather than a second implementation to keep in sync.
50
- */
51
-
52
- import { spawn } from "node:child_process";
53
- import { copyFile, mkdtemp, readFile, rm, stat, writeFile } from "node:fs/promises";
54
- import { readFileSync, writeFileSync } from "node:fs";
55
- import { tmpdir } from "node:os";
56
- // `resolve` is aliased: the promise executor in `run` below binds its own
57
- // `resolve`, and a path helper shadowed by a promise callback is a trap.
58
- import { dirname, join, resolve as resolvePath } from "node:path";
59
- import { fileURLToPath, pathToFileURL } from "node:url";
60
-
61
- import { getResidentService, residentEnabled, residentLineEdit } from "./service.mjs";
62
-
63
- const HERE = dirname(fileURLToPath(import.meta.url));
64
- const DRIVER = join(HERE, "driver.py");
65
-
66
- const SSH_OPTS = [
67
- "-o", "BatchMode=yes",
68
- "-o", "ConnectTimeout=10",
69
- "-o", "ServerAliveInterval=30",
70
- "-o", "ServerAliveCountMax=240",
71
- ];
72
-
73
- /** S1 measured the model at 20 fps, (T,22,3) world metres. Not configurable:
74
- * it is a property of the checkpoint, not a preference. */
75
- export const GEN_FPS = 20;
76
- export const NUM_JOINTS = 22;
77
-
78
- /** S1: 100 ODE steps for a final, 20 for a preview (0.95 s / 0.28 s measured on
79
- * a 196-frame take with ~3800 constraint rows). */
80
- export const DEFAULT_STEPS = 100;
81
- export const PREVIEW_STEPS = 20;
82
-
83
- /** S1: ridge_lambda is the EXACTNESS knob, not steps. The repo ships 1e-3 and
84
- * lands at 2.6e-5; 1e-6 gives 2.4e-7 at half the steps. */
85
- export const DEFAULT_RIDGE = 1e-6;
86
-
87
- /**
88
- * CozyClay IK track id -> hml22 joint index. THE authoring source; driver.py
89
- * carries the same table purely as a guard and errors if the two disagree, so a
90
- * wrapper that drifts fails loudly on the box instead of editing the wrong limb.
91
- *
92
- * Composed from two documented hops: track -> cskel27 target (src/ardy/ik.js's
93
- * own IK_TRACKS/MID_TRACKS/FK_TRACKS) -> hml22 source (S2's mapping table).
94
- * Three entries look like typos and are not:
95
- * - `leftFoot` is cskel27 LeftFoot, whose hml22 source is left_ANKLE (7).
96
- * hml22's `left_foot` (10) is the TOE BASE — S2's naming trap.
97
- * - `leftShoulder` is the CLAVICLE, so left_collar (13), not left_shoulder
98
- * (16, which is the upper-arm root and is cskel27's LeftArm).
99
- * - a MID-JOINT track constrains its OWN joint (leftElbow -> 18), not its
100
- * chain's effector. The artist dragged the elbow handle and drew the
101
- * elbow's path; pinning the wrist instead would follow the line with the
102
- * wrong joint and leave the elbow wherever the model liked.
103
- */
104
- export const TRACK_TO_HML22_JOINT = Object.freeze({
105
- leftHand: 20,
106
- rightHand: 21,
107
- leftFoot: 7,
108
- rightFoot: 8,
109
- leftElbow: 18,
110
- rightElbow: 19,
111
- leftKnee: 4,
112
- rightKnee: 5,
113
- hips: 0,
114
- spine: 6,
115
- neck: 12,
116
- head: 15,
117
- leftShoulder: 13,
118
- rightShoulder: 14,
119
- });
120
-
121
- /**
122
- * The line-edit tracks with no hml22 joint behind them, with the reason.
123
- *
124
- * `chest` is cskel27 Spine2 and Spine2 is one of S2's five FILLED joints:
125
- * cskel27 has three spine links between Hips and Spine3 where hml22 has two, so
126
- * there is literally no source joint to constrain. Refused by name rather than
127
- * retargeted onto a neighbouring spine joint, which would put the drawn line on
128
- * a curve the artist did not draw.
129
- *
130
- * TRACK_TO_HML22_JOINT + UNMAPPABLE_TRACKS covers all 15 pose-studio tracks;
131
- * test/verify-projflow-runner.mjs asserts that totality against ik.js itself.
132
- */
133
- export const UNMAPPABLE_TRACKS = Object.freeze({
134
- chest: "cskel27 Spine2 has no hml22 source joint (S2: a FILLED joint)",
135
- });
136
-
137
- function run(argv, { timeoutMs = 3_600_000, onLine } = {}) {
138
- return new Promise((resolve) => {
139
- const child = spawn(argv[0], argv.slice(1), { stdio: ["ignore", "pipe", "pipe"] });
140
- let stdout = "";
141
- let stderr = "";
142
- const timer = setTimeout(() => child.kill("SIGKILL"), timeoutMs);
143
- child.stdout.on("data", (chunk) => {
144
- stdout += chunk;
145
- if (onLine) for (const line of String(chunk).split("\n")) if (line.trim()) onLine(line);
146
- });
147
- child.stderr.on("data", (chunk) => {
148
- stderr += chunk;
149
- if (onLine) for (const line of String(chunk).split("\n")) if (line.trim()) onLine(line);
150
- });
151
- child.on("close", (code) => {
152
- clearTimeout(timer);
153
- resolve({ code, stdout, stderr });
154
- });
155
- });
156
- }
157
-
158
- /** hml22 joint for an IK track id, or a named refusal. */
159
- export function trackToJoint(track) {
160
- if (typeof track !== "string" || !track) {
161
- throw new Error("lineEdit: track must be a non-empty IK track id");
162
- }
163
- if (Object.hasOwn(UNMAPPABLE_TRACKS, track)) {
164
- throw new Error(`lineEdit: track "${track}" cannot be line-edited: ${UNMAPPABLE_TRACKS[track]}`);
165
- }
166
- const joint = TRACK_TO_HML22_JOINT[track];
167
- if (joint === undefined) {
168
- throw new Error(
169
- `lineEdit: unknown track "${track}"; known line-edit tracks: ${Object.keys(TRACK_TO_HML22_JOINT).sort().join(", ")}`
170
- );
171
- }
172
- return joint;
173
- }
174
-
175
- /**
176
- * Validate a C6 line-edit request and return the exact document driver.py reads.
177
- *
178
- * Every check here exists because the alternative is discovering it after a
179
- * 6-second ssh round trip and a model load. The geometry is NOT checked (whether
180
- * the drawn line is reachable is the model's problem — that is the whole point
181
- * of a zero-shot projection sampler), only the things that would make the
182
- * operator malformed or the request meaningless.
183
- */
184
- export function buildLineRequest(line) {
185
- if (!line || typeof line !== "object" || Array.isArray(line)) {
186
- throw new Error("lineEdit: line must be an object");
187
- }
188
- const jointId = trackToJoint(line.track);
189
- if (line.jointId !== undefined && Number(line.jointId) !== jointId) {
190
- throw new Error(
191
- `lineEdit: jointId ${line.jointId} disagrees with track "${line.track}" -> joint ${jointId}`
192
- );
193
- }
194
-
195
- const range = line.frameRange;
196
- if (!range || typeof range !== "object") {
197
- throw new Error("lineEdit: frameRange {start, end} is required");
198
- }
199
- const start = Number(range.start);
200
- const end = Number(range.end);
201
- for (const [label, value] of [["start", start], ["end", end]]) {
202
- if (!Number.isInteger(value) || value < 0) {
203
- throw new Error(`lineEdit: frameRange.${label} must be a non-negative integer, got ${JSON.stringify(range[label])}`);
204
- }
205
- }
206
- // A one-frame range is two rows against one pose, which the sampler happily
207
- // solves and no artist ever means; a line needs somewhere to go.
208
- if (end <= start) {
209
- throw new Error(`lineEdit: frameRange must span at least two frames, got ${start}..${end}`);
210
- }
211
-
212
- // PINS take the whole rest of this function's job away. A pins edit carries
213
- // three identity rows per pin and no camera at all, so every check below —
214
- // the normalised-uv range, the pixel-focal-length trap, the R/t shape — is
215
- // about a field it does not have. Branching here rather than threading
216
- // `if (pins)` through each check keeps both readings of the request
217
- // self-contained, which is what makes the serialised request diffable.
218
- if (line.pins3d !== undefined) {
219
- if (line.points2d !== undefined || line.camera !== undefined) {
220
- throw new Error("lineEdit: a pins edit carries pins3d alone — no points2d, no camera");
221
- }
222
- const pins = line.pins3d;
223
- if (!Array.isArray(pins) || pins.length < 1) {
224
- throw new Error(`lineEdit: pins3d needs at least 1 pin, got ${Array.isArray(pins) ? pins.length : typeof pins}`);
225
- }
226
- let previous = -1;
227
- const pins3d = pins.map((pin, index) => {
228
- if (!pin || typeof pin !== "object" || !Number.isInteger(pin.frame)) {
229
- throw new Error(`lineEdit: pins3d[${index}] must be { frame: integer, position: [x, y, z] }`);
230
- }
231
- // GENERATION-clock frames by the time they reach here (line-edit-job
232
- // scales them), so the range they must sit inside is this request's.
233
- if (pin.frame < start || pin.frame > end) {
234
- throw new Error(`lineEdit: pins3d[${index}].frame ${pin.frame} is outside frameRange ${start}..${end}`);
235
- }
236
- if (pin.frame <= previous) {
237
- throw new Error(`lineEdit: pins3d frames must be strictly ascending, got ${pin.frame} after ${previous}`);
238
- }
239
- previous = pin.frame;
240
- if (!Array.isArray(pin.position) || pin.position.length !== 3) {
241
- throw new Error(`lineEdit: pins3d[${index}].position must be [x, y, z] world metres`);
242
- }
243
- const position = pin.position.map((value, axis) => {
244
- const number = Number(value);
245
- if (!Number.isFinite(number)) throw new Error(`lineEdit: pins3d[${index}].position[${axis}] must be finite`);
246
- return number;
247
- });
248
- return { frame: pin.frame, position };
249
- });
250
- return {
251
- track: line.track,
252
- jointId,
253
- frameRange: { start, end },
254
- pins3d,
255
- prompt: line.prompt === undefined || line.prompt === null ? "" : String(line.prompt),
256
- };
257
- }
258
-
259
- const points = line.points2d;
260
- if (!Array.isArray(points) || points.length < 2) {
261
- throw new Error(`lineEdit: points2d needs at least 2 points, got ${Array.isArray(points) ? points.length : typeof points}`);
262
- }
263
- const points2d = points.map((point, index) => {
264
- if (!Array.isArray(point) || point.length !== 2) {
265
- throw new Error(`lineEdit: points2d[${index}] must be [u, v]`);
266
- }
267
- const [u, v] = point.map(Number);
268
- if (!Number.isFinite(u) || !Number.isFinite(v)) {
269
- throw new Error(`lineEdit: points2d[${index}] must be two finite numbers, got ${JSON.stringify(point)}`);
270
- }
271
- // Viewport-normalised, per C6. Enforced rather than clamped: a point
272
- // outside the viewport is either a unit mix-up (pixels sent as
273
- // normalised) or a stroke the artist could not have seen, and both are
274
- // worth a message. The tolerance covers a stroke that grazed the edge.
275
- if (u < -0.01 || u > 1.01 || v < -0.01 || v > 1.01) {
276
- throw new Error(
277
- `lineEdit: points2d[${index}] = [${u}, ${v}] is outside the 0..1 normalised viewport; ` +
278
- "points2d and the camera intrinsics must both be in normalised units"
279
- );
280
- }
281
- return [u, v];
282
- });
283
-
284
- const camera = line.camera;
285
- if (!camera || typeof camera !== "object") {
286
- throw new Error("lineEdit: camera {fx, fy, cx, cy, R, t} is required");
287
- }
288
- const intrinsics = {};
289
- for (const key of ["fx", "fy", "cx", "cy"]) {
290
- const value = Number(camera[key]);
291
- if (!Number.isFinite(value)) {
292
- throw new Error(`lineEdit: camera.${key} must be a finite number, got ${JSON.stringify(camera[key])}`);
293
- }
294
- intrinsics[key] = value;
295
- }
296
- // A zero focal length collapses both rows of that axis to the depth row and
297
- // the normal matrix goes singular; the Cholesky failure on the box would name
298
- // LAPACK, not the camera.
299
- for (const key of ["fx", "fy"]) {
300
- if (intrinsics[key] === 0) throw new Error(`lineEdit: camera.${key} must be non-zero`);
301
- }
302
- // Intrinsics live in the SAME units as points2d (normalised 0..1). A pixel
303
- // focal length beside normalised points is the single most likely wire
304
- // mistake and it produces a plausible-looking but wrong line, so it is caught
305
- // by magnitude here rather than debugged from a bad take later.
306
- if (Math.abs(intrinsics.fx) > 50 || Math.abs(intrinsics.fy) > 50) {
307
- throw new Error(
308
- `lineEdit: camera.fx/fy look like PIXEL focal lengths (${intrinsics.fx}, ${intrinsics.fy}); ` +
309
- "they must be normalised by the viewport size, like points2d"
310
- );
311
- }
312
- if (!Array.isArray(camera.R) || camera.R.length !== 3 || camera.R.some((row) => !Array.isArray(row) || row.length !== 3)) {
313
- throw new Error("lineEdit: camera.R must be a 3x3 world-to-camera rotation");
314
- }
315
- const R = camera.R.map((row, i) => row.map((value, j) => {
316
- const number = Number(value);
317
- if (!Number.isFinite(number)) throw new Error(`lineEdit: camera.R[${i}][${j}] must be finite`);
318
- return number;
319
- }));
320
- if (!Array.isArray(camera.t) || camera.t.length !== 3) {
321
- throw new Error("lineEdit: camera.t must be a 3-vector (camera-space translation)");
322
- }
323
- const t = camera.t.map((value, i) => {
324
- const number = Number(value);
325
- if (!Number.isFinite(number)) throw new Error(`lineEdit: camera.t[${i}] must be finite`);
326
- return number;
327
- });
328
-
329
- const prompt = line.prompt === undefined || line.prompt === null ? "" : String(line.prompt);
330
-
331
- // Field order is fixed so two identical requests serialise byte-identically
332
- // and a run directory can be diffed.
333
- return {
334
- track: line.track,
335
- jointId,
336
- frameRange: { start, end },
337
- points2d,
338
- camera: { ...intrinsics, R, t },
339
- prompt,
340
- };
341
- }
342
-
343
- /**
344
- * Minimal reader for a numpy .npy v1/v2 array of little-endian float32 in C
345
- * order — which is what `np.save` writes for the driver's (T,22,3) output.
346
- *
347
- * tools/kimodo/read-npz.mjs has a reader for the same payload format, but it is
348
- * bound to the ZIP container `np.savez` produces and returns members by name.
349
- * ProjFlow writes a bare .npy, so the container layer does not apply; the header
350
- * grammar below is the same one, restated for the one dtype this path can
351
- * produce. Widening or Fortran order are refused rather than guessed: a silently
352
- * transposed motion is indistinguishable from a bad take.
353
- */
354
- export function readNpyFloat32(path) {
355
- const buffer = readFileSync(path);
356
- if (buffer.toString("latin1", 0, 6) !== "\x93NUMPY") {
357
- throw new Error(`readNpyFloat32: ${path} is not a .npy file`);
358
- }
359
- const major = buffer.readUInt8(6);
360
- const headerLength = major === 1 ? buffer.readUInt16LE(8) : buffer.readUInt32LE(8);
361
- const headerStart = major === 1 ? 10 : 12;
362
- const header = buffer.toString("latin1", headerStart, headerStart + headerLength);
363
-
364
- const descr = /'descr'\s*:\s*'([^']+)'/.exec(header)?.[1];
365
- const fortran = /'fortran_order'\s*:\s*(True|False)/.exec(header)?.[1];
366
- const shapeText = /'shape'\s*:\s*\(([^)]*)\)/.exec(header)?.[1];
367
- if (!descr || !fortran || shapeText === undefined) {
368
- throw new Error(`readNpyFloat32: ${path} has an unreadable .npy header`);
369
- }
370
- if (descr !== "<f4") {
371
- throw new Error(`readNpyFloat32: ${path} has dtype ${descr}; the driver writes float32`);
372
- }
373
- if (fortran === "True") {
374
- throw new Error(`readNpyFloat32: ${path} is Fortran-ordered; only C order is supported`);
375
- }
376
- const shape = shapeText
377
- .split(",")
378
- .map((part) => part.trim())
379
- .filter((part) => part.length > 0)
380
- .map((part) => {
381
- const value = Number(part);
382
- if (!Number.isInteger(value) || value < 0) {
383
- throw new Error(`readNpyFloat32: ${path} has a bad shape entry ${part}`);
384
- }
385
- return value;
386
- });
387
-
388
- const count = shape.reduce((total, dim) => total * dim, 1);
389
- const body = buffer.subarray(headerStart + headerLength);
390
- if (body.length < count * 4) {
391
- throw new Error(`readNpyFloat32: ${path} is truncated (need ${count * 4} bytes, have ${body.length})`);
392
- }
393
- // A copy, not a view: `buffer` is a pooled Node allocation whose byteOffset
394
- // need not be 4-aligned, which a Float32Array view over it would reject.
395
- const data = new Float32Array(count);
396
- for (let index = 0; index < count; index += 1) data[index] = body.readFloatLE(index * 4);
397
- return { shape, data };
398
- }
399
-
400
- /**
401
- * Write an hml22 (T,22,3) float32 .npy — the inverse of readNpyFloat32, and the
402
- * ONLY way to produce this module's own `sourceMotionNpy` input.
403
- *
404
- * It lives here rather than in the wave-2 composition layer because the header
405
- * grammar above is the contract with driver.py: a writer that padded or spelled
406
- * the dict differently would be a second, drifting definition of the same file
407
- * format, and the round trip readNpyFloat32(writeNpyFloat32(x)) is the cheapest
408
- * possible test of both. (tools/ardy/npz.mjs has the same 10-byte prefix inside
409
- * its ZIP builder, but it only ever emits ARCHIVES; a bare .npy is not one of
410
- * its outputs.)
411
- *
412
- * @param {string} path
413
- * @param {Float32Array} data C-order payload, shape.reduce(*) long
414
- * @param {number[]} shape
415
- */
416
- export function writeNpyFloat32(path, data, shape) {
417
- if (!(data instanceof Float32Array)) {
418
- throw new Error("writeNpyFloat32: data must be a Float32Array");
419
- }
420
- if (!Array.isArray(shape) || shape.length === 0 || shape.some((dim) => !Number.isInteger(dim) || dim < 0)) {
421
- throw new Error(`writeNpyFloat32: shape must be non-negative integers, got ${JSON.stringify(shape)}`);
422
- }
423
- const count = shape.reduce((total, dim) => total * dim, 1);
424
- if (count !== data.length) {
425
- throw new Error(`writeNpyFloat32: shape (${shape.join(",")}) needs ${count} values, got ${data.length}`);
426
- }
427
- // A NaN reaching the box surfaces as a LAPACK failure inside the Cholesky,
428
- // which names neither the frame nor the joint; refuse it here instead.
429
- for (let index = 0; index < data.length; index += 1) {
430
- if (!Number.isFinite(data[index])) {
431
- throw new Error(`writeNpyFloat32: data[${index}] is ${data[index]}`);
432
- }
433
- }
434
- const tuple = `(${shape.join(", ")}${shape.length === 1 ? "," : ""})`;
435
- const dict = `{'descr': '<f4', 'fortran_order': False, 'shape': ${tuple}, }`;
436
- // numpy pads so that magic(6) + version(2) + length(2) + header is a multiple
437
- // of 64; the same convention tools/ardy/npz.mjs uses.
438
- const header = `${dict}${" ".repeat((53 - (dict.length % 64) + 64) % 64)}\n`;
439
- const prefix = Buffer.alloc(10);
440
- prefix.writeUInt8(0x93, 0);
441
- prefix.write("NUMPY", 1, "ascii");
442
- prefix.writeUInt8(1, 6);
443
- prefix.writeUInt8(0, 7);
444
- prefix.writeUInt16LE(header.length, 8);
445
- writeFileSync(
446
- path,
447
- Buffer.concat([prefix, Buffer.from(header, "ascii"), Buffer.from(data.buffer, data.byteOffset, data.byteLength)])
448
- );
449
- }
450
-
451
- /**
452
- * The warm route: hand the request to the resident child and shape its answer
453
- * like a cold run's.
454
- *
455
- * WHY IT MIRRORS THE COLD ARGV RATHER THAN IMPROVING ON IT. The cold path
456
- * resolves `preview` into a step count and passes `--steps 20`, never
457
- * `--preview`, so a cold preview's metadata says `preview: false`. This sends
458
- * exactly the same thing, because two routes that produce different metadata
459
- * for the same request would make every warm-vs-cold comparison (and gate GS1's
460
- * fallback proof) an argument about which one is right.
461
- *
462
- * `nativeOut` is written HERE rather than scp'd back, since the motion arrived
463
- * in memory. Same two files, same names, same contract: the raw hml22 .npy and
464
- * its .meta.json sidecar, which is what a LATER edit sources from.
465
- */
466
- async function warmLineEdit({
467
- sourceMotionNpy,
468
- request,
469
- steps,
470
- ridge,
471
- preserveStride,
472
- preserveMargin,
473
- seamPin,
474
- seed,
475
- cfg,
476
- host,
477
- python,
478
- repo,
479
- boxHome,
480
- nativeOut,
481
- onLine,
482
- service,
483
- }) {
484
- const { shape, data } = readNpyFloat32(sourceMotionNpy);
485
- if (shape.length !== 3 || shape[1] !== NUM_JOINTS || shape[2] !== 3) {
486
- throw new Error(`lineEditOnBox: sourceMotionNpy must be (T,22,3), got (${shape.join(",")})`);
487
- }
488
- const resident = service || getResidentService({ host, python, repo, boxHome });
489
- const warm = await residentLineEdit({
490
- service: resident,
491
- source: data,
492
- sourceShape: shape,
493
- line: request,
494
- steps,
495
- preview: false,
496
- ridge,
497
- preserveStride,
498
- preserveMargin,
499
- seamPin,
500
- seed,
501
- cfg,
502
- onLine,
503
- });
504
- if (warm.shape.length !== 3 || warm.shape[1] !== NUM_JOINTS || warm.shape[2] !== 3) {
505
- throw new Error(`lineEditOnBox: the resident returned a (${warm.shape.join(",")}) result, expected (T,22,3)`);
506
- }
507
- // The ONE key that is not in a cold run's metadata. It is worth the
508
- // asymmetry: without it a report cannot say whether an edit was warm, and
509
- // "how many edits fell back" is the number this contract is judged on.
510
- const meta = { ...warm.meta, resident: true };
511
- if (nativeOut) {
512
- writeNpyFloat32(nativeOut, warm.positions, warm.shape);
513
- writeFileSync(`${String(nativeOut).replace(/\.npy$/i, "")}.meta.json`, `${JSON.stringify(meta, null, 2)}\n`);
514
- }
515
- return {
516
- positions: warm.positions,
517
- frames: warm.shape[0],
518
- joints: warm.shape[1],
519
- fps: GEN_FPS,
520
- meta,
521
- line: request,
522
- nativeNpy: nativeOut || null,
523
- };
524
- }
525
-
526
- /**
527
- * Run one line edit on the box.
528
- *
529
- * @param {object} options
530
- * @param {string} options.sourceMotionNpy local path to the source take as an
531
- * hml22 (T,22,3) float32 .npy — the SAME format the driver writes, so the
532
- * output of one edit is the input of the next.
533
- * @param {object} options.line a C6 line-edit request (see buildLineRequest).
534
- * @returns {Promise<{positions: Float32Array, frames: number, joints: number,
535
- * fps: number, meta: object, line: object}>} `positions` is (frames*22*3) in
536
- * C order, world metres, hml22 joint order. Converting to cskel27 is M2's
537
- * module — see the seam note in the file header.
538
- */
539
- export async function lineEditOnBox({
540
- sourceMotionNpy,
541
- line,
542
- // 100 final / 20 preview (contract C7). `preview` wins over `steps` because
543
- // it is the interactive path's whole point.
544
- steps = Number(process.env.CCLAY_PROJFLOW_STEPS || DEFAULT_STEPS),
545
- preview = false,
546
- ridge = Number(process.env.CCLAY_PROJFLOW_RIDGE || DEFAULT_RIDGE),
547
- // Preserve-row subsampling. The policy and its reasoning live in driver.py's
548
- // build_preserve_rows; these are the knobs, not the decision.
549
- preserveStride = Number(process.env.CCLAY_PROJFLOW_PRESERVE_STRIDE || 2),
550
- preserveMargin = Number(process.env.CCLAY_PROJFLOW_PRESERVE_MARGIN || 20),
551
- // How many of the edit range's OWN outermost frames are pinned whole-body to
552
- // the source. Licensed by the app's guarantee that the curve's first and last
553
- // points already sit on the source trail; see build_preserve_rows rule 4 for
554
- // the measurement. 0 restores the pre-seam-pin behaviour.
555
- seamPin = Number(process.env.CCLAY_PROJFLOW_SEAM_PIN ?? 2),
556
- seed,
557
- cfg,
558
- // CCLAY_PROJFLOW_HOST falls back to the Kimodo host: one GPU box serves both
559
- // engines today, and making the operator set two variables to the same value
560
- // is how one of them ends up stale.
561
- host = process.env.CCLAY_PROJFLOW_HOST || process.env.CCLAY_KIMODO_HOST || "",
562
- repo = process.env.CCLAY_PROJFLOW_REPO || "/home/yun/projflow-scout/repo",
563
- python = process.env.CCLAY_PROJFLOW_PYTHON || "/home/yun/projflow-scout/venv/bin/python",
564
- // S1 pinned HOME inside the scout directory so CLIP's 338 MB ViT-B/32
565
- // download never lands in the box user's real cache. The model loads CLIP on
566
- // every run, so this is not optional — with the default HOME the first run
567
- // re-downloads it somewhere we do not clean up.
568
- boxHome = process.env.CCLAY_PROJFLOW_HOME || "/home/yun/projflow-scout/home",
569
- // Where to keep the RAW hml22 result (and its .meta.json) after the run
570
- // directory is removed. Same role as Kimodo's CCLAY_KIMODO_NATIVE_OUT: the
571
- // take a LATER edit sources from must be in the engine's own format, and the
572
- // cskel27 file downstream serves is a lossy conversion of it.
573
- nativeOut = process.env.CCLAY_PROJFLOW_NATIVE_OUT || "",
574
- onLine,
575
- } = {}) {
576
- if (!host) {
577
- throw new Error("lineEditOnBox: CCLAY_PROJFLOW_HOST (or CCLAY_KIMODO_HOST) is required");
578
- }
579
- if (typeof sourceMotionNpy !== "string" || !sourceMotionNpy) {
580
- throw new Error("lineEditOnBox: sourceMotionNpy is required (an hml22 (T,22,3) .npy)");
581
- }
582
- // Validated BEFORE anything touches the network: a malformed request must
583
- // cost milliseconds, not an ssh round trip plus a 3.9 s model load.
584
- const request = buildLineRequest(line);
585
- const effectiveSteps = preview ? PREVIEW_STEPS : Number(steps);
586
- if (!Number.isInteger(effectiveSteps) || effectiveSteps < 1) {
587
- throw new Error(`lineEditOnBox: steps must be a positive integer, got ${JSON.stringify(steps)}`);
588
- }
589
- if (!Number.isFinite(ridge) || ridge < 0) {
590
- throw new Error(`lineEditOnBox: ridge must be a non-negative number, got ${JSON.stringify(ridge)}`);
591
- }
592
-
593
- // --- the warm route (contract C11) --------------------------------------
594
- // Tried first, never retried, never allowed to fail an edit. Everything the
595
- // cold argv below carries travels in the request, so the two routes ask the
596
- // same driver for the same thing and the meta comes back identical.
597
- if (residentEnabled()) {
598
- try {
599
- return await warmLineEdit({
600
- sourceMotionNpy,
601
- request,
602
- steps: effectiveSteps,
603
- ridge,
604
- preserveStride: Number(preserveStride),
605
- preserveMargin: Number(preserveMargin),
606
- seamPin: Number(seamPin),
607
- seed,
608
- cfg,
609
- host,
610
- python,
611
- repo,
612
- boxHome,
613
- nativeOut,
614
- onLine,
615
- });
616
- } catch (error) {
617
- // Loud on the status stream, because a session that silently pays the
618
- // cold price every edit is the failure mode this contract exists to
619
- // prevent — the kill criterion is stated in edits-per-fallback.
620
- onLine?.(`run-projflow-line-edit: resident unavailable (${error.message}); running the cold path`);
621
- }
622
- }
623
-
624
- const remoteDir = `/tmp/cclay-projflow-${Date.now()}-${Math.floor(Math.random() * 1e6)}`;
625
- const localDir = await mkdtemp(join(tmpdir(), "cclay-projflow-"));
626
- try {
627
- const made = await run(["ssh", ...SSH_OPTS, host, `mkdir -p ${remoteDir}`]);
628
- if (made.code !== 0) {
629
- throw new Error(`could not create ${remoteDir} on ${host}: ${made.stderr.trim()}`);
630
- }
631
- // Local paths travel as scp ARGV entries, never inside a shell string;
632
- // every remote path is built from remoteDir, which this process generated.
633
- const push = async (localPath, remotePath, label) => {
634
- const pushed = await run(["scp", ...SSH_OPTS, localPath, `${host}:${remotePath}`]);
635
- if (pushed.code !== 0) {
636
- throw new Error(`could not copy ${label} to ${host} (exit ${pushed.code}): ${pushed.stderr.trim()}`);
637
- }
638
- };
639
-
640
- const localLine = join(localDir, "line.json");
641
- await writeFile(localLine, `${JSON.stringify(request, null, 2)}\n`);
642
- // The driver ships per run rather than being installed: the box keeps no
643
- // CozyClay state, so a driver change takes effect on the next request with
644
- // no deploy step and no chance of a stale copy on the box.
645
- await push(DRIVER, `${remoteDir}/driver.py`, "the line-edit driver");
646
- await push(localLine, `${remoteDir}/line.json`, "the line request");
647
- await push(sourceMotionNpy, `${remoteDir}/source.npy`, "the source motion");
648
-
649
- // One invocation. HOME is an env assignment on the same command word
650
- // list; `cd` would be a separate command and is not needed because the
651
- // driver chdirs to the repo itself (its checkpoint paths are repo-relative).
652
- const words = [
653
- `HOME=${boxHome}`,
654
- python,
655
- `${remoteDir}/driver.py`,
656
- `--source ${remoteDir}/source.npy`,
657
- `--line ${remoteDir}/line.json`,
658
- `--out ${remoteDir}/edited.npy`,
659
- `--repo ${JSON.stringify(repo)}`,
660
- `--steps ${effectiveSteps}`,
661
- `--ridge ${ridge}`,
662
- `--preserve-stride ${Number(preserveStride)}`,
663
- `--preserve-margin ${Number(preserveMargin)}`,
664
- `--seam-pin ${Number(seamPin)}`,
665
- seed === undefined ? "" : `--seed ${Number(seed)}`,
666
- cfg === undefined ? "" : `--cfg ${Number(cfg)}`,
667
- ].filter(Boolean).join(" ");
668
-
669
- const edited = await run(["ssh", ...SSH_OPTS, host, words], { onLine });
670
- if (edited.code !== 0) {
671
- throw new Error(
672
- `the ProjFlow line-edit driver on ${host} failed (exit ${edited.code}):\n` +
673
- edited.stderr.split("\n").slice(-25).join("\n")
674
- );
675
- }
676
-
677
- const localNpy = join(localDir, "edited.npy");
678
- const localMeta = join(localDir, "edited.meta.json");
679
- for (const [remoteName, localPath, label] of [
680
- ["edited.npy", localNpy, "the edited motion"],
681
- ["edited.meta.json", localMeta, "the run metadata"],
682
- ]) {
683
- const copied = await run(["scp", ...SSH_OPTS, `${host}:${remoteDir}/${remoteName}`, localPath]);
684
- if (copied.code !== 0) {
685
- throw new Error(`scp of ${label} failed (exit ${copied.code}): ${copied.stderr.trim()}`);
686
- }
687
- }
688
-
689
- const { shape, data } = readNpyFloat32(localNpy);
690
- if (shape.length !== 3 || shape[1] !== NUM_JOINTS || shape[2] !== 3) {
691
- throw new Error(`lineEditOnBox: expected a (T,22,3) result, got (${shape.join(",")})`);
692
- }
693
- // `resident: false` is the counterpart of the warm route's `true`: every
694
- // result says which route produced it, so a session that quietly stopped
695
- // being warm is visible in the report instead of only in the clock.
696
- const meta = { ...JSON.parse(await readFile(localMeta, "utf8")), resident: false };
697
- if (nativeOut) {
698
- // NOT best-effort, unlike Kimodo's equivalent: there the native npz is
699
- // a bonus beside a take the caller already holds in memory, here it is
700
- // the wrapper's ONLY deliverable to the bridge, and a silently missing
701
- // file would be reported as a successful run.
702
- await copyFile(localNpy, nativeOut);
703
- await copyFile(localMeta, `${String(nativeOut).replace(/\.npy$/i, "")}.meta.json`);
704
- }
705
- // The driver measures its own exactness (it is the only side that has both
706
- // the source and the result in one place) and the caller is entitled to
707
- // the numbers rather than a boolean: contract GP1 gates on the
708
- // reprojection error and GP2 on the preserved frames.
709
- return {
710
- positions: data,
711
- frames: shape[0],
712
- joints: shape[1],
713
- fps: GEN_FPS,
714
- meta,
715
- line: request,
716
- nativeNpy: nativeOut || null,
717
- };
718
- } finally {
719
- await rm(localDir, { recursive: true, force: true });
720
- await run(["ssh", ...SSH_OPTS, host, `rm -rf ${remoteDir}`]);
721
- }
722
- }
723
-
724
- /**
725
- * CLI entry point, so tools/projflow/runner.mjs has something spawnable.
726
- *
727
- * WHY THE WRAPPER LIVES IN THIS FILE. Kimodo puts its box wrappers in separate
728
- * scripts (run-sequence-on-box.mjs, run-edit-on-box.mjs) because two run modes
729
- * share one argv contract with the ARDY wrappers. ProjFlow v1 has exactly one
730
- * run mode and no legacy argv to match, so a second file would only be a place
731
- * for the two to drift.
732
- *
733
- * WHAT IT WRITES, AND WHAT IT DOES NOT. The output is the RAW hml22 (T,22,3)
734
- * .npy plus its .meta.json — NOT a cclay npz. Turning 22-joint positions into a
735
- * cskel27 take requires lifting rotations (contract C8, agent M2's
736
- * hml22-to-cskel27.mjs) and splicing the result back into the source take
737
- * (agent M5), and neither exists yet. Wave-2 integration composes those on top
738
- * of this file; until then the done line names the raw motion, which is exactly
739
- * what a later line edit sources from anyway.
740
- */
741
- async function main(argv) {
742
- const args = { steps: undefined, preview: false, seed: undefined };
743
- for (let index = 0; index < argv.length; index += 1) {
744
- const flag = argv[index];
745
- const next = () => {
746
- const value = argv[index + 1];
747
- if (value === undefined) throw new Error(`${flag} needs a value`);
748
- index += 1;
749
- return value;
750
- };
751
- if (flag === "--source") args.source = next();
752
- else if (flag === "--line") args.line = next();
753
- else if (flag === "--output") args.output = next();
754
- else if (flag === "--steps") args.steps = Number(next());
755
- else if (flag === "--preview") args.preview = true;
756
- else if (flag === "--ridge") args.ridge = Number(next());
757
- else if (flag === "--preserve-stride") args.preserveStride = Number(next());
758
- else if (flag === "--preserve-margin") args.preserveMargin = Number(next());
759
- else if (flag === "--seam-pin") args.seamPin = Number(next());
760
- else if (flag === "--seed") args.seed = Number(next());
761
- else throw new Error(`unknown flag ${flag}`);
762
- }
763
- for (const required of ["source", "line", "output"]) {
764
- if (!args[required]) throw new Error(`--${required} is required`);
765
- }
766
- // THE CLI IS A ONE-SHOT PROCESS, so a resident it would have to start, use
767
- // once and kill is pure overhead — the point of contract C11 is amortising a
768
- // model load across a SESSION, and this process is not one. The bridge does
769
- // not come through here (it imports line-edit-job.mjs in-process, where the
770
- // child survives between edits). An operator who wants to exercise the warm
771
- // route from the shell sets the variable explicitly.
772
- if (process.env.CCLAY_PROJFLOW_RESIDENT === undefined) process.env.CCLAY_PROJFLOW_RESIDENT = "0";
773
- const line = JSON.parse(await readFile(resolvePath(args.line), "utf8"));
774
- const result = await lineEditOnBox({
775
- sourceMotionNpy: resolvePath(args.source),
776
- line,
777
- nativeOut: resolvePath(args.output),
778
- ...(args.steps === undefined ? {} : { steps: args.steps }),
779
- ...(args.ridge === undefined ? {} : { ridge: args.ridge }),
780
- ...(args.preserveStride === undefined ? {} : { preserveStride: args.preserveStride }),
781
- ...(args.preserveMargin === undefined ? {} : { preserveMargin: args.preserveMargin }),
782
- ...(args.seamPin === undefined ? {} : { seamPin: args.seamPin }),
783
- ...(args.seed === undefined ? {} : { seed: args.seed }),
784
- preview: args.preview,
785
- onLine: (text) => console.error(text),
786
- });
787
- const { m, steps, sampling_seconds: samplingSeconds, checks } = result.meta;
788
- // The exactness numbers on the status stream, not buried in the sidecar:
789
- // gates GP1/GP2 read exactly these and an operator watching a run should see
790
- // a regression without opening a file.
791
- console.log(
792
- `run-projflow-line-edit: rows=${m} steps=${steps} sample=${samplingSeconds}s ` +
793
- `preserved=${checks.preservedMaxAbsDiffM} reproj=${checks.lineMaxReprojErr} ` +
794
- `resident=${result.meta.resident === true}`
795
- );
796
- const { size } = await stat(resolvePath(args.output));
797
- console.log(`run-projflow-line-edit: done - ${resolvePath(args.output)} (${size} bytes)`);
798
- }
799
-
800
- if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
801
- main(process.argv.slice(2)).catch((error) => {
802
- console.error(`run-projflow-line-edit: ${error.message}`);
803
- process.exit(1);
804
- });
805
- }