cozyclay 2.1.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (583) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +12 -30
  3. package/bin/agent/agent-routes.mjs +40 -113
  4. package/bin/agent/agent-runner.mjs +9 -10
  5. package/bin/agent/cliproxy-provider.mjs +135 -0
  6. package/bin/agent/credential-store.mjs +30 -7
  7. package/bin/agent/pi-tools.mjs +4 -1
  8. package/bin/agent/provider-keys.mjs +32 -0
  9. package/bin/agent/provider-login.mjs +110 -0
  10. package/bin/agent/provider-routes.mjs +99 -0
  11. package/bin/agent/providers.mjs +19 -97
  12. package/bin/agent/script-tool.mjs +17 -4
  13. package/bin/agent/studio-motion-reference.mjs +23 -0
  14. package/bin/agent/studio-prompt.mjs +1 -1
  15. package/bin/agent/studio-rebase-text.mjs +32 -0
  16. package/bin/agent/studio-rebase.mjs +26 -39
  17. package/bin/agent/studio-tools.mjs +9 -28
  18. package/bin/agent/tool-arguments.mjs +49 -0
  19. package/bin/agent/turn-frames.mjs +66 -0
  20. package/bin/codex-auth.mjs +9 -2
  21. package/bin/cozyclay.mjs +10 -116
  22. package/bin/live/cli.mjs +22 -6
  23. package/bin/mcp-runtime.mjs +2 -12
  24. package/bin/package-signature.mjs +0 -4
  25. package/dist/app/index.html +7 -6
  26. package/dist/assets/EXRLoader-CvVIvGVE.js +1 -0
  27. package/dist/assets/analytics-C9sUnndd.js +1 -0
  28. package/dist/assets/app-CCh--1Hc.js +5087 -0
  29. package/dist/assets/app-f-fkV5uP.css +1 -0
  30. package/dist/assets/compile-worker-CkxiD6lH.js +5 -0
  31. package/dist/assets/fflate.module-KbGj5-H6.js +5 -0
  32. package/dist/assets/{first-shot-handoff-BlT33CGf.js → first-shot-handoff-CC4xe4HM.js} +1 -1
  33. package/dist/assets/{landing-BSesrCpL.js → landing-DjHD8u7k.js} +1 -1
  34. package/dist/assets/scene-asset-sync-Hx_FzdOH.js +20 -0
  35. package/dist/assets/scene-asset-sync-_S8NQl5B.css +1 -0
  36. package/dist/assets/workflow-C4yKH1p4.js +179 -0
  37. package/dist/cozyclay-package.json +1 -1
  38. package/dist/index.html +3 -3
  39. package/dist/llms.txt +14 -0
  40. package/dist/sw.js +3 -6
  41. package/dist/workflow/index.html +5 -4
  42. package/mcp/LIVE-PROTOCOL.md +7 -47
  43. package/mcp/README.md +16 -26
  44. package/mcp/live-hub.mjs +6 -10
  45. package/mcp/package.json +1 -3
  46. package/mcp/tool-handlers.mjs +4 -168
  47. package/package.json +3 -22
  48. package/src/App.jsx +174 -2639
  49. package/src/agent-progress.js +79 -0
  50. package/src/agent-turn-mark.js +30 -0
  51. package/src/analytics.js +1 -19
  52. package/src/app-context.js +5 -0
  53. package/src/app-stage.jsx +2 -34
  54. package/src/asset-pane.jsx +6 -5
  55. package/src/camera-block.js +21 -0
  56. package/src/camera-direction.js +120 -0
  57. package/src/character-kind.js +3 -9
  58. package/src/command-bus.js +8 -6
  59. package/src/commands/cast.js +5 -34
  60. package/src/commands/elements/character.js +1 -1
  61. package/src/commands/elements/motion.js +1 -6
  62. package/src/commands/elements/object.js +6 -2
  63. package/src/commands/elements/shot.js +4 -1
  64. package/src/commands/elements.js +38 -14
  65. package/src/commands/index.js +3 -1
  66. package/src/commands/kits.js +37 -0
  67. package/src/commands/motion-timing.js +57 -0
  68. package/src/commands/motion.js +9 -152
  69. package/src/commands/motion3-compile.js +65 -0
  70. package/src/commands/motion3.js +207 -0
  71. package/src/commands/objects.js +6 -4
  72. package/src/commands/shot.js +49 -11
  73. package/src/commands/stage.js +14 -1
  74. package/src/commands/view.js +4 -1
  75. package/src/document-mirrors.js +42 -0
  76. package/src/document-store.js +4 -0
  77. package/src/domains/cast.js +19 -194
  78. package/src/domains/motion/motion-range-pin-rules.js +1 -1
  79. package/src/domains/motion/take-source.js +51 -0
  80. package/src/domains/motion.js +60 -1838
  81. package/src/domains/scenes.js +12 -55
  82. package/src/domains/shots.js +2 -2
  83. package/src/domains/stage.js +4 -2
  84. package/src/draft-ghost.js +46 -0
  85. package/src/draft-ghost.jsx +129 -0
  86. package/src/dualview.jsx +4 -3
  87. package/src/effects/EffectsLayer.jsx +112 -0
  88. package/src/effects/effect-meshes.js +232 -0
  89. package/src/effects/effect-record.js +103 -0
  90. package/src/effects/emitter.js +59 -0
  91. package/src/effects/simulate.js +261 -0
  92. package/src/effects/state.js +59 -0
  93. package/src/execution-telemetry.js +2 -3
  94. package/src/first-success-guide.jsx +5 -5
  95. package/src/ghost-set.js +257 -0
  96. package/src/held-object-edit.jsx +113 -0
  97. package/src/held-object-pose.js +175 -0
  98. package/src/hierarchy-model.js +1 -1
  99. package/src/hierarchy-panel.jsx +1 -1
  100. package/src/imported-mesh.jsx +103 -0
  101. package/src/kits/held.js +84 -0
  102. package/src/kits/index.js +70 -0
  103. package/src/kits/smoking.js +57 -0
  104. package/src/light-blockers.js +93 -0
  105. package/src/motion/beat-board-model.js +84 -0
  106. package/src/motion/beat-board.css +124 -0
  107. package/src/motion/beat-board.jsx +59 -0
  108. package/src/motion/center-of-mass.js +66 -0
  109. package/src/motion/fix-collisions.js +850 -0
  110. package/src/motion/physics-review.js +118 -0
  111. package/src/{ardy → motion}/physics-support.js +1 -76
  112. package/src/motion/rig-snapshot.js +22 -0
  113. package/src/motion/take-segments.jsx +140 -0
  114. package/src/{ardy → motion}/timeline-coordinates.js +0 -21
  115. package/src/{ardy → motion}/timeline.css +1 -2
  116. package/src/{ardy → motion}/timeline.jsx +48 -419
  117. package/src/motion/waypoints.js +76 -0
  118. package/src/motion3/beats.js +83 -0
  119. package/src/motion3/body.js +158 -0
  120. package/src/motion3/bones.js +47 -0
  121. package/src/motion3/carried.js +47 -0
  122. package/src/motion3/compile-job.js +75 -0
  123. package/src/motion3/compile-worker.js +10 -0
  124. package/src/motion3/compiler/anchors.js +105 -0
  125. package/src/motion3/compiler/arm-orient.js +128 -0
  126. package/src/motion3/compiler/channels.js +125 -0
  127. package/src/motion3/compiler/compile.js +205 -0
  128. package/src/motion3/compiler/gait-rhythm.js +97 -0
  129. package/src/motion3/compiler/grip.js +171 -0
  130. package/src/motion3/compiler/head-steady.js +48 -0
  131. package/src/motion3/compiler/hold.js +134 -0
  132. package/src/motion3/compiler/idle.js +113 -0
  133. package/src/motion3/compiler/limbs.js +170 -0
  134. package/src/motion3/compiler/math.js +72 -0
  135. package/src/motion3/compiler/pose.js +162 -0
  136. package/src/motion3/compiler/quat.js +49 -0
  137. package/src/motion3/compiler/skeleton.js +74 -0
  138. package/src/motion3/compiler/validate-common.js +32 -0
  139. package/src/motion3/compiler/validate-goals.js +118 -0
  140. package/src/motion3/compiler/validate-objects.js +143 -0
  141. package/src/motion3/compiler/validate-parts.js +116 -0
  142. package/src/motion3/compiler/validate.js +122 -0
  143. package/src/motion3/compiler/vocab.js +73 -0
  144. package/src/motion3/compiler/walk-pelvis.js +275 -0
  145. package/src/motion3/compiler/walk.js +207 -0
  146. package/src/motion3/compiler/wrist-clamp.js +32 -0
  147. package/src/motion3/delta.js +47 -0
  148. package/src/motion3/describe.js +174 -0
  149. package/src/motion3/docs.js +27 -0
  150. package/src/motion3/edit-carry.js +21 -0
  151. package/src/motion3/fix/ease.js +16 -0
  152. package/src/motion3/fix/ground.js +37 -0
  153. package/src/motion3/fix/index.js +52 -0
  154. package/src/motion3/fix/push.js +71 -0
  155. package/src/motion3/fix/reach.js +46 -0
  156. package/src/motion3/fix/retime.js +79 -0
  157. package/src/motion3/fix/self.js +211 -0
  158. package/src/motion3/fix/slide.js +70 -0
  159. package/src/motion3/fix/turn.js +51 -0
  160. package/src/motion3/follow.js +39 -0
  161. package/src/motion3/free-motion.js +173 -0
  162. package/src/motion3/grip-choice.js +130 -0
  163. package/src/motion3/hold-plan.js +157 -0
  164. package/src/motion3/json-patch.js +77 -0
  165. package/src/motion3/key-aim.js +105 -0
  166. package/src/motion3/key-edit.js +82 -0
  167. package/src/motion3/lenient.js +127 -0
  168. package/src/motion3/object-keys.js +236 -0
  169. package/src/motion3/object-pose.js +182 -0
  170. package/src/motion3/object-tracks.js +12 -0
  171. package/src/motion3/pipeline.js +102 -0
  172. package/src/motion3/playback.js +51 -0
  173. package/src/motion3/probes.js +91 -0
  174. package/src/motion3/qa/body-checks.js +34 -0
  175. package/src/motion3/qa/check-names.js +14 -0
  176. package/src/motion3/qa/checks.js +121 -0
  177. package/src/motion3/qa/contact-checks.js +51 -0
  178. package/src/motion3/qa/explain.js +80 -0
  179. package/src/motion3/qa/fmt.js +5 -0
  180. package/src/motion3/qa/foot-checks.js +72 -0
  181. package/src/motion3/qa/geometry.js +77 -0
  182. package/src/motion3/qa/goals.js +191 -0
  183. package/src/motion3/qa/ground.js +60 -0
  184. package/src/motion3/qa/hints-contact.js +179 -0
  185. package/src/motion3/qa/hints-goal.js +160 -0
  186. package/src/motion3/qa/hints-knee.js +24 -0
  187. package/src/motion3/qa/hints-motion.js +209 -0
  188. package/src/motion3/qa/hints-object.js +91 -0
  189. package/src/motion3/qa/index.js +42 -0
  190. package/src/motion3/qa/key-checks.js +82 -0
  191. package/src/motion3/qa/near.js +96 -0
  192. package/src/motion3/qa/object-checks.js +135 -0
  193. package/src/motion3/qa/patch.js +161 -0
  194. package/src/motion3/qa/severity.js +38 -0
  195. package/src/motion3/qa/spec-checks.js +65 -0
  196. package/src/motion3/qa/style.js +218 -0
  197. package/src/motion3/qa/wrist-checks.js +69 -0
  198. package/src/motion3/qa/wrist-obs.js +60 -0
  199. package/src/motion3/refs.js +120 -0
  200. package/src/motion3/repair-tune.js +121 -0
  201. package/src/motion3/repair.js +83 -0
  202. package/src/motion3/report.js +67 -0
  203. package/src/motion3/rigs/index.js +14 -0
  204. package/src/motion3/rigs/x-bot-tpose-body.json +1 -0
  205. package/src/motion3/rigs/x-bot-tpose.json +1 -0
  206. package/src/motion3/rigs/y-bot-tpose-body.json +1 -0
  207. package/src/motion3/rigs/y-bot-tpose.json +1 -0
  208. package/src/motion3/scene.js +60 -0
  209. package/src/motion3/schema/brief.js +89 -0
  210. package/src/motion3/schema/fields.js +44 -0
  211. package/src/motion3/schema/spec-examples.js +46 -0
  212. package/src/motion3/schema/topics.js +111 -0
  213. package/src/motion3/schema/types.js +147 -0
  214. package/src/motion3/search.js +117 -0
  215. package/src/motion3/second-hand.js +112 -0
  216. package/src/motion3/skills/basic.js +82 -0
  217. package/src/motion3/skills/book.js +76 -0
  218. package/src/motion3/skills/car.js +145 -0
  219. package/src/motion3/skills/frames-auto.js +29 -0
  220. package/src/motion3/skills/index.js +83 -0
  221. package/src/motion3/skills/info.js +65 -0
  222. package/src/motion3/skills/smoke-kit.js +109 -0
  223. package/src/motion3/skills/smoke.js +160 -0
  224. package/src/motion3/spec-store.js +59 -0
  225. package/src/motion3/take-edit.js +155 -0
  226. package/src/motion3/take.js +63 -0
  227. package/src/motion3/timeline.js +127 -0
  228. package/src/motion3/views.js +121 -0
  229. package/src/motion3/walk-plan.js +119 -0
  230. package/src/object-catalog.jsx +7 -1
  231. package/src/object-colors.js +98 -0
  232. package/src/object-gizmo-parts.js +115 -0
  233. package/src/object-gizmo.jsx +37 -122
  234. package/src/object-glow.js +139 -0
  235. package/src/object-hidden.js +33 -0
  236. package/src/object-look.js +196 -0
  237. package/src/object-size.js +10 -0
  238. package/src/object-travel.js +71 -6
  239. package/src/panels/CameraPanel.jsx +7 -7
  240. package/src/panels/HdrEnvironmentControls.jsx +59 -0
  241. package/src/panels/LightPanel.jsx +25 -4
  242. package/src/panels/ObjectLookPanel.jsx +88 -0
  243. package/src/panels/PoseImagePanel.jsx +1 -1
  244. package/src/panels/PosePanel.jsx +2 -17
  245. package/src/panels/RigControlPanel.jsx +5 -53
  246. package/src/panels/SubjectBox.jsx +4 -1
  247. package/src/panels/details.css +14 -0
  248. package/src/posestudio.jsx +4 -27
  249. package/src/project-browser.jsx +3 -4
  250. package/src/project-resources.js +18 -31
  251. package/src/project.js +13 -104
  252. package/src/props.jsx +25 -280
  253. package/src/rail-dot-drag.js +23 -0
  254. package/src/render-passes.js +10 -0
  255. package/src/resource-status.jsx +1 -0
  256. package/src/result-modal.jsx +4 -19
  257. package/src/room.jsx +20 -12
  258. package/src/scene-assets.js +8 -6
  259. package/src/scene-light-place.js +72 -0
  260. package/src/scene-lights.jsx +148 -0
  261. package/src/scene-objects.js +31 -139
  262. package/src/scenes.js +15 -105
  263. package/src/semantic-edit.js +2 -5
  264. package/src/set-pieces.jsx +183 -0
  265. package/src/settings-menu.jsx +3 -11
  266. package/src/shell/BottomDock.jsx +11 -53
  267. package/src/shell/CompositionGuideButton.jsx +20 -0
  268. package/src/shell/DetailsSlot.jsx +16 -200
  269. package/src/shell/GhostsMenuGroup.jsx +74 -0
  270. package/src/shell/LibrarySlot.jsx +1 -3
  271. package/src/shell/MenuBar.jsx +3 -3
  272. package/src/shell/PreferencesDialog.jsx +15 -264
  273. package/src/shell/PreferencesSlot.jsx +2 -8
  274. package/src/shell/ShotPreviewHeader.jsx +17 -0
  275. package/src/shell/TopBar.jsx +5 -83
  276. package/src/shell/ViewportToolbar.jsx +23 -93
  277. package/src/shell/agent-glass.css +19 -6
  278. package/src/shell/details-selection.js +42 -0
  279. package/src/shell/ghosts-menu.css +22 -0
  280. package/src/shell/glass-regions.css +29 -3
  281. package/src/shell/preferences.css +6 -1
  282. package/src/shell/viewport.css +12 -0
  283. package/src/shot-authoring.js +4 -7
  284. package/src/soft-shadows.js +37 -0
  285. package/src/stage-attach-frames.js +12 -12
  286. package/src/stage-canvas-rigs.jsx +3 -3
  287. package/src/stage-capture.jsx +42 -8
  288. package/src/stage-characters.jsx +22 -216
  289. package/src/stage-editor-gizmos.jsx +6 -6
  290. package/src/stage-environment.js +93 -0
  291. package/src/stage-hdr-builtins.js +107 -0
  292. package/src/stage-hdr-image.js +116 -0
  293. package/src/stage-hdr-library.js +70 -0
  294. package/src/stage-hdr-map.js +94 -0
  295. package/src/stage-hdr.js +130 -0
  296. package/src/stage-lighting.js +149 -0
  297. package/src/stage-occlusion.js +210 -0
  298. package/src/stage-sky.jsx +159 -0
  299. package/src/stage-startup-defaults.js +5 -9
  300. package/src/studio-actions.js +12 -14
  301. package/src/studio-agent-commands.js +46 -16
  302. package/src/studio-agent-motion.js +9 -9
  303. package/src/studio-agent-protocol.js +46 -73
  304. package/src/studio-app-binding.js +29 -45
  305. package/src/studio-asset-list.js +17 -0
  306. package/src/studio-contact-sheet.js +37 -5
  307. package/src/studio-elements.js +27 -7
  308. package/src/studio-entity-bounds.js +18 -0
  309. package/src/studio-error-codes.js +8 -0
  310. package/src/studio-export-framing.js +152 -0
  311. package/src/studio-lighting-check.js +103 -0
  312. package/src/studio-motion-visual.js +150 -0
  313. package/src/studio-schema-errors.js +80 -0
  314. package/src/studio-support-gap.js +74 -0
  315. package/src/styles.css +1 -19
  316. package/src/timeline-defaults.js +5 -0
  317. package/src/timeline-extent.js +8 -20
  318. package/src/tutorial-walk.js +17 -0
  319. package/src/ui-scrub.js +1 -1
  320. package/src/use-shot-camera-tracks.js +12 -0
  321. package/src/view-ghosts.js +97 -0
  322. package/src/view-ghosts.jsx +49 -0
  323. package/src/workflow/AgentPanel.jsx +47 -166
  324. package/src/workflow/AgentProgressCard.jsx +107 -0
  325. package/src/workflow/AgentReplyText.jsx +37 -0
  326. package/src/workflow/AgentTurnSteps.jsx +84 -0
  327. package/src/workflow/LoginPanel.jsx +140 -0
  328. package/src/workflow/WorkflowBuilder.jsx +7 -23
  329. package/src/workflow/agent-client.js +54 -51
  330. package/src/workflow/agent-panel.css +71 -1
  331. package/src/workflow/agent-progress-card.css +167 -0
  332. package/src/workflow/agent-progress-view.js +47 -0
  333. package/src/workflow/agent-turn-steps.css +113 -0
  334. package/src/workflow/agent-turn.js +191 -0
  335. package/src/workflow/login-client.js +34 -0
  336. package/src/workflow/motion-input.js +18 -31
  337. package/src/workflow/node-schema.js +1 -1
  338. package/src/workflow/reply-markdown.js +69 -0
  339. package/src/workflow/scene-asset-sync.js +8 -8
  340. package/tools/agent-session-stats.mjs +61 -0
  341. package/tools/dev-full.mjs +5 -56
  342. package/tools/eslint-baseline.json +12 -13
  343. package/tools/headless-studio.mjs +85 -0
  344. package/tools/knip-baseline.json +0 -179
  345. package/tools/loc-baseline.json +33 -83
  346. package/tools/motion3/extract-body.mjs +103 -0
  347. package/tools/motion3/extract-rig.mjs +85 -0
  348. package/tools/process-supervisor.mjs +0 -88
  349. package/tools/run-tests.mjs +9 -124
  350. package/bin/agent/motion-runtime.mjs +0 -76
  351. package/dist/ardy/cskel27-rest.json +0 -557
  352. package/dist/assets/analytics-B41FmPYI.js +0 -1
  353. package/dist/assets/app-Do-Og5Pu.css +0 -1
  354. package/dist/assets/app-DwP-tl2x.js +0 -4920
  355. package/dist/assets/config-Bx7pYsD9.js +0 -1
  356. package/dist/assets/config-DeoFTYzb.css +0 -1
  357. package/dist/assets/demo-Dt0qZfS7.js +0 -1
  358. package/dist/assets/scene-asset-sync-BCWDJB1d.js +0 -19
  359. package/dist/assets/scene-asset-sync-DjJ_cJ2R.css +0 -1
  360. package/dist/assets/ticket-jqMhqL88.js +0 -1
  361. package/dist/assets/workflow-Br-K7Srs.js +0 -179
  362. package/dist/demo/walk-then-stop.npz +0 -0
  363. package/mcp/ardy-prompts.mjs +0 -218
  364. package/src/ardy/auto-fix-panel.css +0 -108
  365. package/src/ardy/auto-physics.js +0 -1194
  366. package/src/ardy/client.js +0 -153
  367. package/src/ardy/convert.js +0 -313
  368. package/src/ardy/cskel27-neutral.js +0 -39
  369. package/src/ardy/cskel27.js +0 -77
  370. package/src/ardy/export.js +0 -165
  371. package/src/ardy/fix-collisions.js +0 -2257
  372. package/src/ardy/foot-lock.js +0 -230
  373. package/src/ardy/motion-calibration.js +0 -118
  374. package/src/ardy/motion-edit.js +0 -213
  375. package/src/ardy/motion-url.js +0 -55
  376. package/src/ardy/mp4-duration.js +0 -102
  377. package/src/ardy/npz.js +0 -595
  378. package/src/ardy/physics-panel.css +0 -21
  379. package/src/ardy/physics-panel.jsx +0 -89
  380. package/src/ardy/physics-refine.js +0 -111
  381. package/src/ardy/physics-review.js +0 -567
  382. package/src/ardy/physics-temporal.js +0 -54
  383. package/src/ardy/platform-fit-panel.css +0 -64
  384. package/src/ardy/platform-fit-panel.jsx +0 -41
  385. package/src/ardy/platform-fit.js +0 -326
  386. package/src/ardy/playback.js +0 -711
  387. package/src/ardy/pose-pin.js +0 -97
  388. package/src/ardy/prompt-clips.js +0 -30
  389. package/src/ardy/retime.js +0 -211
  390. package/src/ardy/root-drop.js +0 -369
  391. package/src/ardy/timeline-resize.js +0 -13
  392. package/src/ardy/to-cskel27.js +0 -225
  393. package/src/ardy/trim.js +0 -33
  394. package/src/ardy/waypoints.js +0 -369
  395. package/src/ardy-bridge-contract.js +0 -281
  396. package/src/domains/motion/motion-video-capture.js +0 -664
  397. package/src/i2v-motion-client.js +0 -77
  398. package/src/i2v-motion-studio.jsx +0 -180
  399. package/src/line-edit.js +0 -1348
  400. package/src/motion/generation.js +0 -105
  401. package/src/motion-readiness-ui.jsx +0 -67
  402. package/src/motion-readiness.js +0 -40
  403. package/src/motion-resources.js +0 -177
  404. package/src/motion-store.js +0 -65
  405. package/src/motion-trail.js +0 -601
  406. package/src/multimodel-ingest.js +0 -394
  407. package/src/panels/PromptBlocksPanel.jsx +0 -444
  408. package/src/panels/VideoCapturePanel.jsx +0 -181
  409. package/src/pose-extract/browser.js +0 -42
  410. package/src/pose-extract/detector.js +0 -100
  411. package/src/pose-extract/fit.js +0 -227
  412. package/src/pose-extract/image-frame.js +0 -51
  413. package/src/pose-extract/index.js +0 -58
  414. package/src/pose-extract/keyframes.js +0 -74
  415. package/src/pose-extract/landmarks.js +0 -162
  416. package/src/pose-extract/mirror.js +0 -153
  417. package/src/pose-extract/take.js +0 -131
  418. package/src/pose-extract/video-frames.js +0 -91
  419. package/src/take-recipe.js +0 -165
  420. package/src/trail-key-conflicts.js +0 -39
  421. package/src/trail-pick.js +0 -59
  422. package/src/use-i2v-capture-modal.js +0 -15
  423. package/tools/ardy/BRIDGE.md +0 -37
  424. package/tools/ardy/GVHMR-NPZ.md +0 -68
  425. package/tools/ardy/README.md +0 -124
  426. package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
  427. package/tools/ardy/artifacts.mjs +0 -34
  428. package/tools/ardy/bridge.mjs +0 -1732
  429. package/tools/ardy/bvh-cskel27.mjs +0 -1265
  430. package/tools/ardy/cclay_constrained_generate.py +0 -561
  431. package/tools/ardy/cclay_gvhmr_worker.py +0 -264
  432. package/tools/ardy/cclay_motion_edit.py +0 -510
  433. package/tools/ardy/cclay_sequence_generate.py +0 -705
  434. package/tools/ardy/cclay_worker.py +0 -304
  435. package/tools/ardy/dump-npz.py +0 -205
  436. package/tools/ardy/extract-rest.mjs +0 -299
  437. package/tools/ardy/extract.mjs +0 -507
  438. package/tools/ardy/footage.mjs +0 -467
  439. package/tools/ardy/gvhmr-floor.mjs +0 -102
  440. package/tools/ardy/gvhmr-to-cskel27.mjs +0 -21
  441. package/tools/ardy/gvhmr_fastpath.py +0 -168
  442. package/tools/ardy/gvhmr_trajectory.py +0 -240
  443. package/tools/ardy/merge-text-encoder.py +0 -160
  444. package/tools/ardy/mocap-metrics.mjs +0 -200
  445. package/tools/ardy/mocap-quality-gate.mjs +0 -110
  446. package/tools/ardy/motion-stabilize.mjs +0 -348
  447. package/tools/ardy/npz.mjs +0 -414
  448. package/tools/ardy/pose-to-npz.mjs +0 -106
  449. package/tools/ardy/prompt-limits.mjs +0 -5
  450. package/tools/ardy/run-edit-on-box.sh +0 -73
  451. package/tools/ardy/run-local.mjs +0 -233
  452. package/tools/ardy/run-on-box.sh +0 -593
  453. package/tools/ardy/run-sequence-on-box.sh +0 -177
  454. package/tools/ardy/runners/gvhmr-worker.mjs +0 -206
  455. package/tools/ardy/runners/index.mjs +0 -18
  456. package/tools/ardy/runners/local.mjs +0 -455
  457. package/tools/ardy/runners/proc.mjs +0 -125
  458. package/tools/ardy/runners/remote.mjs +0 -252
  459. package/tools/ardy/setup-local.mjs +0 -189
  460. package/tools/ardy/setup-text-encoder-on-box.sh +0 -93
  461. package/tools/ardy/setup-text-encoder.py +0 -306
  462. package/tools/ardy/smpl-cskel27.mjs +0 -178
  463. package/tools/ardy/visual-qa.mjs +0 -196
  464. package/tools/ardy/vq-car.mjs +0 -52
  465. package/tools/bench/EXP3.md +0 -61
  466. package/tools/bench/cclay_bench_extract_incam.py +0 -125
  467. package/tools/bench/cclay_bench_extract_obs.py +0 -204
  468. package/tools/bench/cclay_bench_runner.py +0 -42
  469. package/tools/bench/cube-contact.mjs +0 -162
  470. package/tools/bench/exp3.mjs +0 -152
  471. package/tools/bench/extract-bench-lib.mjs +0 -216
  472. package/tools/bench/extract-bench.mjs +0 -302
  473. package/tools/bench/fal-generate.mjs +0 -88
  474. package/tools/bench/fit/README.md +0 -189
  475. package/tools/bench/fit/camera.mjs +0 -59
  476. package/tools/bench/fit/contact.mjs +0 -419
  477. package/tools/bench/fit/footlock.mjs +0 -234
  478. package/tools/bench/fit/motion.mjs +0 -85
  479. package/tools/bench/fit/pin.mjs +0 -27
  480. package/tools/bench/fit/remote.mjs +0 -105
  481. package/tools/bench/fit-bench.mjs +0 -126
  482. package/tools/bench/fit-sanity.mjs +0 -45
  483. package/tools/bench/metrics.mjs +0 -313
  484. package/tools/bench/obs/depth.mjs +0 -258
  485. package/tools/bench/obs/extrinsics.mjs +0 -175
  486. package/tools/bench/obs/fit_mannequin_betas.py +0 -340
  487. package/tools/bench/obs/ground.mjs +0 -294
  488. package/tools/bench/obs/heading.mjs +0 -151
  489. package/tools/bench/obs/ladder.mjs +0 -555
  490. package/tools/bench/obs/mannequin-betas.json +0 -124
  491. package/tools/bench/obs/remote.mjs +0 -83
  492. package/tools/bench/obs/rest_joints.py +0 -52
  493. package/tools/bench/obs/ybot-targets.mjs +0 -49
  494. package/tools/bench/obs-bench.mjs +0 -520
  495. package/tools/bench/score.mjs +0 -343
  496. package/tools/bench/summarize.mjs +0 -86
  497. package/tools/demo-worker/README.md +0 -137
  498. package/tools/demo-worker/api-client.mjs +0 -233
  499. package/tools/demo-worker/generate.mjs +0 -301
  500. package/tools/demo-worker/index.mjs +0 -466
  501. package/tools/demo-worker/sign-core.mjs +0 -112
  502. package/tools/demo-worker/sign.mjs +0 -161
  503. package/tools/gt-render/browser.mjs +0 -159
  504. package/tools/gt-render/camera-math.mjs +0 -276
  505. package/tools/gt-render/page.mjs +0 -267
  506. package/tools/gt-render/render.mjs +0 -514
  507. package/tools/gt-render/scene-box.mjs +0 -31
  508. package/tools/gt-render/take-transform.mjs +0 -87
  509. package/tools/kimodo/compare-seam.mjs +0 -108
  510. package/tools/kimodo/constraints.mjs +0 -244
  511. package/tools/kimodo/edit.mjs +0 -259
  512. package/tools/kimodo/effector-constraints.mjs +0 -200
  513. package/tools/kimodo/generate.mjs +0 -646
  514. package/tools/kimodo/local-output.mjs +0 -265
  515. package/tools/kimodo/measure-pose.mjs +0 -65
  516. package/tools/kimodo/measure-preserve.mjs +0 -899
  517. package/tools/kimodo/measure-waypoints.mjs +0 -55
  518. package/tools/kimodo/mlx-generate.py +0 -111
  519. package/tools/kimodo/pose-constraints.mjs +0 -248
  520. package/tools/kimodo/preserve-mask.mjs +0 -543
  521. package/tools/kimodo/read-npz.mjs +0 -189
  522. package/tools/kimodo/run-edit-on-box.mjs +0 -155
  523. package/tools/kimodo/run-sequence-on-box.mjs +0 -178
  524. package/tools/kimodo/runner.mjs +0 -284
  525. package/tools/kimodo/seam-batch.mjs +0 -77
  526. package/tools/kimodo/setup-local.mjs +0 -30
  527. package/tools/kimodo/setup-on-box.sh +0 -339
  528. package/tools/kimodo/soma77-to-cskel27.mjs +0 -305
  529. package/tools/morphgs/assets/gen_truth.py +0 -39
  530. package/tools/morphgs/assets/mesh_ori_rig.txt +0 -27
  531. package/tools/morphgs/assets/playback-check.mjs +0 -30
  532. package/tools/morphgs/demo-gate.sh +0 -26
  533. package/tools/morphgs/fbx2morphgs.mjs +0 -68
  534. package/tools/morphgs/morphgs-to-cskel27.mjs +0 -105
  535. package/tools/morphgs/patches/preprocess_src-none-mode.patch +0 -76
  536. package/tools/morphgs/setup-on-cluster.sh +0 -126
  537. package/tools/projflow/driver.py +0 -1069
  538. package/tools/projflow/generate.mjs +0 -805
  539. package/tools/projflow/hml22-to-cskel27.mjs +0 -735
  540. package/tools/projflow/line-edit-job.mjs +0 -547
  541. package/tools/projflow/replay.mjs +0 -452
  542. package/tools/projflow/runner.mjs +0 -269
  543. package/tools/projflow/service.mjs +0 -742
  544. package/tools/track/DESIGN.md +0 -513
  545. package/tools/track/backfill-provenance.mjs +0 -116
  546. package/tools/track/budget.mjs +0 -8
  547. package/tools/track/check-rig.mjs +0 -209
  548. package/tools/track/diagnostics.schema.json +0 -60
  549. package/tools/track/export-rig.mjs +0 -256
  550. package/tools/track/fallback.mjs +0 -7
  551. package/tools/track/fk-parity-fixture.mjs +0 -161
  552. package/tools/track/gate.mjs +0 -349
  553. package/tools/track/masks.mjs +0 -318
  554. package/tools/track/metrics.mjs +0 -201
  555. package/tools/track/publish-obs.mjs +0 -108
  556. package/tools/track/py/check_env.py +0 -61
  557. package/tools/track/py/eval_lr.py +0 -229
  558. package/tools/track/py/lr_viterbi.py +0 -294
  559. package/tools/track/py/masks.py +0 -405
  560. package/tools/track/py/objective.py +0 -382
  561. package/tools/track/py/rig.py +0 -508
  562. package/tools/track/py/scene.py +0 -326
  563. package/tools/track/py/test_joint_indices.py +0 -123
  564. package/tools/track/py/test_lr_viterbi.py +0 -195
  565. package/tools/track/py/test_masks.py +0 -229
  566. package/tools/track/py/test_rig.py +0 -288
  567. package/tools/track/py/test_scene.py +0 -354
  568. package/tools/track/py/test_track.py +0 -328
  569. package/tools/track/py/track.py +0 -411
  570. package/tools/track/remote.mjs +0 -140
  571. package/tools/track/rig-dump.mjs +0 -227
  572. package/tools/track/run-box-tests.mjs +0 -14
  573. package/tools/track/run-box.mjs +0 -168
  574. package/tools/track/setup-box.sh +0 -59
  575. package/tools/track/study-2d.mjs +0 -728
  576. /package/src/{ardy → motion}/collision-blockers.js +0 -0
  577. /package/src/{ardy → motion}/ground.js +0 -0
  578. /package/src/{ardy → motion}/ik-drag.js +0 -0
  579. /package/src/{ardy → motion}/ik-key-json.js +0 -0
  580. /package/src/{ardy → motion}/ik.js +0 -0
  581. /package/src/{ardy → motion}/physics-surface.js +0 -0
  582. /package/src/{ardy → motion}/playback-clock.js +0 -0
  583. /package/src/{ardy → motion}/range-pin.js +0 -0
@@ -0,0 +1,174 @@
1
+ // C9. motion.describe { topic, name? }: the spec's schema, a valid example and notes. The field
2
+ // lists come from schema/fields.js, their types from schema/types.js, channels from
3
+ // compiler/vocab.js, FIX_KEYS from fix/index.js, the skills from skills/index.js and
4
+ // skills/info.js; examples and notes from schema/topics.js. Every answer fits DESCRIBE_BUDGET.
5
+ // topic "essentials" { topic, types: { <topic or shared shape>: type line }, skills: [{ name, args (type line), needs, frames, adds }], notes }
6
+ // (the lines of MEANT parts carry each field's meaning; SHALLOW topics name their parts only)
7
+ // other topic { topic, schema (JSON Schema 2020-12 fragment), example, notes }
8
+ // topic "skills" { topic, skills: [{ name, args, needs, frames, adds }] }
9
+ // topic "skills", name { topic, name, schema (the skill's args), example (a use entry), notes }
10
+ // topic "examples" { topic, examples: [{ name, scene, spec }] (whole specs, schema/spec-examples.js), notes }
11
+ // motion.describe { topics: [{ topic, name? }] } (describeMany): { topics: [those answers, in order], more?: [the asks that
12
+ // did not fit] }, so a request's topics cost one call; the Studio prompt already carries essentials and goals.
13
+ import { SPEC_FIELDS, KEY_FIELDS, USE_FIELDS, ANCHOR_FIELDS, PROP_FIELDS, FIT_FIELDS, OBJECT_FIELDS, WALK_FIELDS, CHECK_FIELDS, CHECK_ENTRY_FIELDS,
14
+ GOAL_FIELDS, PROBE_FIELDS, REF_KINDS, DESCRIBE_TOPICS } from "./schema/fields.js";
15
+ import { DEFS, SPEC_TYPES, KEY_TYPES, USE_TYPES, WALK_TYPES, ANCHOR_TYPES, ANCHOR_REQUIRED, PROP_TYPES, FIT_TYPES, OBJECT_TYPES, FREE_TYPES, CHECK_ENTRY_TYPES, CHECK_REQUIRED, CHECKS_TYPES,
16
+ GOAL_TYPES, PROBE_TYPES, FIX_TYPES, REF_DOCS } from "./schema/types.js";
17
+ import { EXAMPLES, NOTES } from "./schema/topics.js";
18
+ import { SPEC_EXAMPLES, EXAMPLE_NOTES } from "./schema/spec-examples.js";
19
+ import { briefType } from "./schema/brief.js";
20
+ import { VEC_CHANNELS, NUM_CHANNELS, MAP_CHANNELS, SKILL_CHANNELS, FINGER_PRESETS, CHANNEL_DOCS, channelFamily, rangeOf, didYouMean } from "./compiler/vocab.js";
21
+ import { FIX_KEYS } from "./fix/index.js";
22
+ import { SKILL_NAMES } from "./skills/index.js";
23
+ import { SKILL_INFO } from "./skills/info.js";
24
+
25
+ /** Bytes of JSON a describe answer may take: the 8 KiB receipt minus 768 B for a describe receipt's
26
+ * head (569 B in a live Studio receipt: ids, host, revision, summary; no readback, it changes nothing). */
27
+ export const DESCRIBE_BUDGET = 8192 - 768;
28
+
29
+ const bad = msg => { throw new Error(`MOTION_SPEC_INVALID: ${msg}`); };
30
+ const typeOf = (types, f, table) => types[f] ?? bad(`describe: ${table}.${f} has no type in schema/types.js`);
31
+
32
+ /** { type: object } with exactly `fields`, each typed from `types`. */
33
+ const objectOf = (fields, types, table, required = []) => ({ type: "object", properties: Object.fromEntries(fields.map(f => [f, typeOf(types, f, table)])),
34
+ ...(required.length ? { required } : {}), additionalProperties: false });
35
+ const listOf = (items, extra) => ({ type: "array", items, ...extra });
36
+ const oneOfRequired = names => ({ oneOf: names.map(n => ({ required: [n] })) });
37
+
38
+ const LOCAL_DEFS = {
39
+ key: () => objectOf(KEY_FIELDS, KEY_TYPES, "key", ["f", "set"]),
40
+ use: () => { const o = objectOf(USE_FIELDS, USE_TYPES, "use", ["skill", "f0"]); o.properties.skill = { enum: SKILL_NAMES }; return o; },
41
+ walk: () => objectOf(WALK_FIELDS, WALK_TYPES, "generators[i]", ["type", "f0"]),
42
+ };
43
+
44
+ /** The schema with $schema and the $defs it (transitively) refers to. */
45
+ function rooted(schema) {
46
+ const defs = {}, all = { ...DEFS, ...LOCAL_DEFS };
47
+ for (let more = true; more;) {
48
+ more = false;
49
+ const json = JSON.stringify([schema, defs]);
50
+ for (const [name, d] of Object.entries(all)) if (!defs[name] && json.includes(`"#/$defs/${name}"`)) { defs[name] = typeof d === "function" ? d() : d; more = true; }
51
+ }
52
+ return { $schema: "https://json-schema.org/draft/2020-12/schema", ...schema, ...(Object.keys(defs).length ? { $defs: defs } : {}) };
53
+ }
54
+
55
+ function channelSchema(ch) {
56
+ const twin = ch.replace(/_r$/, "_l");
57
+ if (twin !== ch && (VEC_CHANNELS.has(twin) || NUM_CHANNELS.has(twin) || MAP_CHANNELS.has(twin))) return { $ref: `#/properties/${twin}` };
58
+ const r = rangeOf(ch), d = CHANNEL_DOCS[channelFamily(ch)] ?? bad(`describe: channel ${ch} has no entry in CHANNEL_DOCS`);
59
+ if (VEC_CHANNELS.has(ch)) return { $ref: "#/$defs/vec3", description: d };
60
+ if (MAP_CHANNELS.has(ch)) return { type: "object", propertyNames: { enum: FINGER_PRESETS }, additionalProperties: { type: "number", minimum: 0, maximum: 1 } };
61
+ return r ? { type: "number", minimum: r[1], maximum: r[2], description: r[3] } : { type: "number", description: d };
62
+ }
63
+
64
+ const SCHEMAS = {
65
+ spec: () => objectOf(SPEC_FIELDS, SPEC_TYPES, "spec", ["schema", "frames"]),
66
+ anchors: () => ({ type: "object", description: "name -> anchor", additionalProperties: { oneOf: Object.entries(ANCHOR_FIELDS).map(([kind, fields]) =>
67
+ objectOf(["kind", ...fields], { ...ANCHOR_TYPES, kind: { const: kind } }, `anchors (${kind})`, ["kind", ...(ANCHOR_REQUIRED[kind] ?? [])])) } }),
68
+ props: () => ({ type: "object", properties: {
69
+ props: { type: "object", description: "name -> prop", additionalProperties: objectOf(PROP_FIELDS, PROP_TYPES, "props", ["hand", "origin"]) },
70
+ fits: listOf(objectOf(FIT_FIELDS, FIT_TYPES, "fits", ["side", "anchor", "effector"])) } }),
71
+ objects: () => ({ type: "object", description: "object or group id -> hinge, carry, switch, follow or free", additionalProperties: { oneOf: [
72
+ { title: "hinge", ...objectOf(OBJECT_FIELDS.hinge, OBJECT_TYPES, "objects (hinge)", ["pivot", "channel"]) },
73
+ { title: "carry", ...objectOf(OBJECT_FIELDS.carry, OBJECT_TYPES, "objects (carry)", ["prop"]) },
74
+ { title: "switch", ...objectOf(OBJECT_FIELDS.switch, OBJECT_TYPES, "objects (switch)") },
75
+ { title: "follow", ...objectOf(OBJECT_FIELDS.follow, OBJECT_TYPES, "objects (follow)", ["follow"]) },
76
+ { title: "free", ...objectOf(OBJECT_FIELDS.free, { ...OBJECT_TYPES, ...FREE_TYPES }, "objects (free)") }] } }),
77
+ checks: () => ({ type: "object", properties: Object.fromEntries(CHECK_FIELDS.map(f => [f, CHECK_ENTRY_FIELDS[f]
78
+ ? listOf(objectOf(CHECK_ENTRY_FIELDS[f], CHECK_ENTRY_TYPES, `checks.${f}`, CHECK_REQUIRED[f]))
79
+ : typeOf(CHECKS_TYPES, f, "checks")])), additionalProperties: false }),
80
+ goals: () => listOf({ ...objectOf(GOAL_FIELDS, GOAL_TYPES, "goals", ["name", "at"]), ...oneOfRequired(["near", "above", "moved", "still", "speed"]) }),
81
+ probes: () => listOf({ ...objectOf(PROBE_FIELDS, PROBE_TYPES, "probes", ["name"]), ...oneOfRequired(["distance", "position"]) }, { maxItems: 16 }),
82
+ channels: () => ({ type: "object", description: "key.set: channel -> value",
83
+ properties: Object.fromEntries([...VEC_CHANNELS, ...NUM_CHANNELS, ...MAP_CHANNELS].filter(ch => !SKILL_CHANNELS.has(ch)).map(ch => [ch, channelSchema(ch)])),
84
+ patternProperties: { "^w_.+_[lr]$": { type: "number", minimum: 0, maximum: 1, description: rangeOf("w_a_l")[3] }, "_vis$": { type: "number", description: "shown above 0.5" } },
85
+ additionalProperties: { type: "number", description: "spec.channels, an object's or a check's when" } }),
86
+ refs: () => ({ type: "string", oneOf: REF_KINDS.map(k => ({ pattern: `^${k}:`, description: typeOf(REF_DOCS, k, "refs") })) }),
87
+ fix: () => ({ oneOf: [{ const: false }, objectOf(FIX_KEYS, FIX_TYPES, "fix")] }),
88
+ };
89
+
90
+ function skillArgs(name) {
91
+ const { args, required = [] } = SKILL_INFO[name] ?? bad(`describe: skill ${name} has no entry in skills/info.js`);
92
+ return Object.entries(args).map(([a, s]) => `${a}${required.includes(a) ? "*" : ""}${s.default !== undefined ? `=${JSON.stringify(s.default)}` : ""}`).join(", ");
93
+ }
94
+
95
+ function describeSkill(name) {
96
+ const info = SKILL_INFO[name] ?? bad(`describe: skill ${name} has no entry in skills/info.js`);
97
+ return { topic: "skills", name, schema: rooted({ type: "object", properties: info.args, ...(info.required ? { required: info.required } : {}), additionalProperties: false }),
98
+ example: { skill: name, ...info.example }, notes: [`needs: ${info.needs}`, `frames from f0: ${info.frames}`, `adds: ${info.adds.join("; ")}`] };
99
+ }
100
+
101
+ /** The parts round-2 sessions read again right after essentials (topics objects and channels, skill
102
+ * reachGrip): in essentials their fields also say what they mean, so a first spec needs no second read. */
103
+ const MEANT = { topics: ["objects", "channels"], skills: ["reachGrip"] };
104
+ /** Topics no round-2 session needed for a first spec: essentials names their parts, the full topic has the fields. */
105
+ const SHALLOW = ["checks"];
106
+
107
+ /** A shared shape whose fields are mostly a skill's args (walk, walkTo): its own fields and `...<skill> args`. */
108
+ function shapeLine(d) {
109
+ for (const [skill, { args }] of Object.entries(SKILL_INFO)) {
110
+ const own = Object.entries(d.properties ?? {}).filter(([k, v]) => JSON.stringify(args[k]) !== JSON.stringify(v)), shared = Object.keys(d.properties ?? {}).length - own.length;
111
+ if (shared <= own.length) continue;
112
+ const not = Object.keys(args).filter(a => !d.properties[a]);
113
+ return briefType({ ...d, properties: Object.fromEntries(own) }).replace(/\}$/, `, ...${skill} args${not.length ? ` but ${not.join(", ")}` : ""}}`);
114
+ }
115
+ return briefType(d) + (d.description ? ` (${d.description})` : "");
116
+ }
117
+
118
+ /** The spec schema with each field a topic spells ("topic anchors") named by that topic's type line: `anchors?: anchors`, `fits?: props.fits`. */
119
+ function pointed(spec) {
120
+ const at = (f, t) => (t === f && !SCHEMAS[t]().properties?.[f] ? t : `${t}.${f}`);
121
+ return { ...spec, properties: Object.fromEntries(Object.entries(spec.properties).map(([f, s]) => {
122
+ const t = s.description?.match(/\btopic (\w+)\)?$/)?.[1];
123
+ return [f, t && SCHEMAS[t] && t !== "skills" ? { $ref: `#/types/${at(f, t)}` } : s];
124
+ })) };
125
+ }
126
+
127
+ /** Every topic's schema and every skill's args as one type line each, and the shared shapes they name. */
128
+ function essentials() {
129
+ const types = {}, shared = {};
130
+ for (const topic of DESCRIBE_TOPICS.filter(t => SCHEMAS[t])) {
131
+ const { $defs = {}, ...schema } = rooted(topic === "spec" ? pointed(SCHEMAS.spec()) : SCHEMAS[topic]());
132
+ types[topic] = briefType(schema, { means: MEANT.topics.includes(topic), depth: SHALLOW.includes(topic) ? 1 : undefined });
133
+ Object.assign(shared, $defs);
134
+ }
135
+ // use.skill without the enum: the skills list below names them
136
+ if (shared.use) shared.use = objectOf(USE_FIELDS, USE_TYPES, "use", ["skill", "f0"]);
137
+ const skills = SKILL_NAMES.map(n => {
138
+ const { args, required = [], needs, frames, adds } = SKILL_INFO[n] ?? bad(`describe: skill ${n} has no entry in skills/info.js`);
139
+ return { name: n, args: briefType({ type: "object", properties: args, required }, { means: MEANT.skills.includes(n) }), needs, frames, adds };
140
+ });
141
+ // the shared shapes a line names as a type (": vec3", "| frameSpan", "[ref, ref]"); a shallow topic names fewer than its schema has
142
+ const named = name => new RegExp(`(?:[:|] |[[(])${name}\\b`).test(JSON.stringify([types, skills.map(k => k.args)]));
143
+ for (let more = true; more;) {
144
+ more = false;
145
+ for (const [name, d] of Object.entries(shared)) if (!types[name] && named(name)) { types[name] = shapeLine(d); more = true; }
146
+ }
147
+ return { topic: "essentials", types, skills, notes: NOTES.essentials };
148
+ }
149
+
150
+ /** describe(topic, name?) -> the answer for one topic (see the table above). */
151
+ export function describe(topic, name) {
152
+ if (!DESCRIBE_TOPICS.includes(topic)) bad(`topic "${topic}" is not a describe topic${didYouMean(String(topic), DESCRIBE_TOPICS)} (topics: ${DESCRIBE_TOPICS.join(", ")})`);
153
+ if (topic === "essentials") return essentials();
154
+ if (topic === "examples") return { topic, examples: SPEC_EXAMPLES, notes: EXAMPLE_NOTES };
155
+ if (topic !== "skills") return { topic, schema: rooted(SCHEMAS[topic]()), example: EXAMPLES[topic], notes: NOTES[topic] };
156
+ if (name === undefined) return { topic, skills: SKILL_NAMES.map(n => ({ name: n, args: skillArgs(n), needs: SKILL_INFO[n].needs, frames: SKILL_INFO[n].frames, adds: SKILL_INFO[n].adds })) };
157
+ if (!SKILL_NAMES.includes(name)) bad(`skill "${name}" is not a skill${didYouMean(String(name), SKILL_NAMES)} (skills: ${SKILL_NAMES.join(", ")})`);
158
+ return describeSkill(name);
159
+ }
160
+
161
+ const bytes = value => new TextEncoder().encode(JSON.stringify(value)).length;
162
+
163
+ /** describeMany([{ topic, name? }]) -> { topics, more? }: the answers in order while they fit DESCRIBE_BUDGET (the first
164
+ * always does), each ask once; the rest are named in `more` for another call. */
165
+ export function describeMany(asks) {
166
+ const seen = new Set(), wanted = asks.filter(({ topic, name }) => !seen.has(`${topic} ${name ?? ""}`) && seen.add(`${topic} ${name ?? ""}`));
167
+ const topics = [];
168
+ for (const [i, { topic, name }] of wanted.entries()) {
169
+ const answer = describe(topic, name), rest = wanted.slice(i + 1);
170
+ if (topics.length && bytes({ topics: [...topics, answer], more: rest }) > DESCRIBE_BUDGET) return { topics, more: wanted.slice(i) };
171
+ topics.push(answer);
172
+ }
173
+ return { topics };
174
+ }
@@ -0,0 +1,27 @@
1
+ // The agent-facing words of the motion actions (src/commands/motion3.js): each action's description.
2
+ // Lane P owns the wording; the code only reads these constants. The procedure lives in the Studio
3
+ // system prompt and the field tables in motion.describe, so these strings say only what each action takes and answers.
4
+ export const MOTION_ACTION_HELP = {
5
+ compile: "Bake a spec on the character's rig and run QA. install: true installs (default), false or \"never\" changes nothing, \"ifClean\" only if the final qa.ok; output.installed tells. "
6
+ + "Send spec, or base (an answered specId) + optional patch (JSON Patch); base alone or patch: [] re-measures (new probes). "
7
+ + "spec = { schema: \"cozyclay.motion-spec.v1\", fps: 24, frames (number or \"auto\"), ... } (parts: motion.describe); any number may be { range: [lo, hi] } (searched). "
8
+ + "probes (at most 16, request level; spec.probes: measured, not stored): { name, distance: [ref, ref] | position: ref, frames, every? }. "
9
+ + "Refs need a prefix (joint: prop: anchor: landmark: object:; goals[].moved: a bare id too) and take loose bone names (any case); spec.checks need exact ones. "
10
+ + "repair: { rounds } (1-8, default 4) applies group patches while severity drops. "
11
+ + "qa judges only what is broken; speed, acceleration, balance, knee bend are observations unless checks.limits names them. "
12
+ + "Answers frames, installed, specId, qa { ok, total, goals, groups (GOAL first, then severity; cause, hint, patch?) }, observations, delta (with base), repair, probes, timeline, fixed, search, skills. output.more: motion.report pages the rest.",
13
+ qa: "Measure the character's installed take again against the scene as it is now (objects may have moved). Optional checks replace the spec's checks and its skills' checks. "
14
+ + "Answers specId and qa as motion.compile does (goals, ranked groups with cause, hint and patch); output.more pages with motion.report. Changes nothing.",
15
+ sample: "World positions (metres) of named bones of the installed take at up to 64 frames. Bone names may be loose (any case, a mixamorig: prefix, aliases such as hips or hand_l); an unknown one answers a did-you-mean. "
16
+ + "Before a take is installed, use motion.compile probes instead. Changes nothing.",
17
+ describe: "The motion spec reference. topic essentials: all a first spec needs, { types (every field of every topic, one line each; objects, channels and reachGrip "
18
+ + "say what fields mean), skills (typed args, needs, frames, adds), notes }. topic spec, skills, anchors, props, objects, checks, goals, probes, channels, refs or fix: "
19
+ + "{ topic, schema, example, notes }; skills lists the skills, with name one skill's args and a use example. topic examples answers whole specs that compile. "
20
+ + "topics: [topic | { topic, name? }] answers several in one call: { topics, more (the asks that did not fit) }. Changes nothing.",
21
+ keyObject: "Key an object the installed take moves (objectId) at frame, then compile and install it whatever QA says (a user edit, one undo). "
22
+ + "A carried object takes pos [x, y, z] and rot [x, y, z, w], or aim [x, y, z] (its +Y) and roll?: its base's world pose (a group: its root), grab..release only (objects.<id>.keys); the hand follows. "
23
+ + "A hinge takes angle (degrees) or rot (its turn about the axis). remove: true deletes the key at frame. Refused where the take neither holds nor hinges it (drag it then). "
24
+ + "Answers keyed and the motion.compile output; REACH \"<id>: n cm off its keys\": what the hand missed.",
25
+ report: "Page through the parts of a motion.compile or motion.qa answer that did not fit (output.more): part is groups (the default), timeline or probes, from the item index to start at; "
26
+ + "answers { specId, part, from, items, next } (next = the index to ask for next, null at the end). Changes nothing.",
27
+ };
@@ -0,0 +1,21 @@
1
+ // A take's timing edit (take-edit.js: cuts, segment speeds, removed segments, a trim) carried onto the
2
+ // next take compiled for the same character, so a recompile does not throw away timing the user (or
3
+ // the agent) set on the take it replaces and nobody has to cut and slow it again by hand.
4
+ // The segments keep their source frames (a recompile mostly keeps its beats where they were): the
5
+ // same frame count keeps the edit as it is; on another count a segment that ran to the old last frame
6
+ // runs to the new one, segments past the new end drop out and one across it is clipped.
7
+ import { isIdentityEdit, normalizeTakeEdit } from "./take-edit.js";
8
+
9
+ const RESET = "motion.resetTiming plays the take as compiled";
10
+
11
+ /** carryEdit(edit, oldFrames, newFrames) -> null (nothing to carry) | { edit (null: dropped), note }. */
12
+ export function carryEdit(edit, oldFrames, newFrames) {
13
+ const previous = normalizeTakeEdit(edit, oldFrames);
14
+ if (!previous || isIdentityEdit(previous, oldFrames)) return null;
15
+ if (oldFrames === newFrames) return { edit: previous, note: `kept the timing edit of the take it replaced (${previous.length} segments); ${RESET}` };
16
+ const last = newFrames - 1, final = previous.length - 1;
17
+ const next = previous.map((segment, i) => ({ ...segment, sourceEnd: i === final && segment.sourceEnd === oldFrames - 1 ? last : Math.min(segment.sourceEnd, last) }))
18
+ .filter(segment => segment.sourceStart <= segment.sourceEnd);
19
+ if (!next.length || isIdentityEdit(next, newFrames)) return { edit: null, note: `dropped the timing edit of the take it replaced: nothing of it is left on the new ${newFrames} frames (it had ${oldFrames})` };
20
+ return { edit: next, note: `kept the timing edit of the take it replaced (${next.length} segments) on the new ${newFrames} frames (it had ${oldFrames}); ${RESET}` };
21
+ }
@@ -0,0 +1,16 @@
1
+ // Small helpers the auto-fixes share.
2
+
3
+ /** Eased dilation of a per-frame amount: every frame gets at least amount * ease over +-w frames. */
4
+ export function easeAround(values, w) {
5
+ const n = values.length, out = new Float64Array(n);
6
+ for (let i = 0; i < n; i++) {
7
+ if (!(values[i] > 0)) continue;
8
+ for (let j = Math.max(0, i - w - 1); j <= Math.min(n - 1, i + w + 1); j++) {
9
+ const t = 1 - Math.abs(i - j) / (w + 1), e = t * t * (3 - 2 * t);
10
+ out[j] = Math.max(out[j], values[i] * e);
11
+ }
12
+ }
13
+ return out;
14
+ }
15
+
16
+ export const round4 = x => Math.round(x * 1e4) / 1e4;
@@ -0,0 +1,37 @@
1
+ // Soles on the floor: where a foot's lowest sole point (qa/geometry.js solePoints: heel, ball, tip,
2
+ // on the skin's sole, where verify_result measures the floor) is under the floor, its ankle target
3
+ // goes up until that point is on the floor. A squat on the balls of the feet (fpitch +) stands on
4
+ // the balls instead of sinking them. The sole's shape under the ankle depends only on fyaw and
5
+ // fpitch, so the lift is measured at the target, not where an out-of-reach leg left the ankle.
6
+ // The lift eases in and out over EASE frames, so a foot keyed into the floor lands, not stops dead.
7
+ import { solePoints } from "../qa/geometry.js";
8
+ import { SIDES } from "../compiler/pose.js";
9
+ import { easeAround, round4 } from "./ease.js";
10
+
11
+ /** Depth (m) a sole point may keep under the floor; lifts past MAX_LIFT stop there for QA (GROUND). */
12
+ const GROUND_EPS = 0.001, MAX_LIFT = 0.15, EASE = 2;
13
+
14
+ export function liftSoles(out, engine) {
15
+ const { sk } = out.ctx, n = out.frames.length, touched = new Set();
16
+ let worst = 0;
17
+ for (const [k, S] of Object.entries(SIDES)) {
18
+ const ch = out.baked[`foot_${k}`], ankle = sk.index[`${S}Foot`];
19
+ if (!ch) continue;
20
+ const need = out.frames.map(({ s }, i) => {
21
+ const sp = solePoints(sk, s, S), low = Math.min(sp.heel[1], sp.ball[1], sp.tip[1]) - s.P[ankle][1] + ch[out.f0 + i][1];
22
+ return Math.min(MAX_LIFT, -low - GROUND_EPS);
23
+ });
24
+ if (!need.some(d => d > 0)) continue;
25
+ const lift = easeAround(need, EASE);
26
+ for (let i = 0; i < n; i++) {
27
+ if (!(lift[i] > 0)) continue;
28
+ const f = out.f0 + i;
29
+ ch[f] = [ch[f][0], ch[f][1] + lift[i], ch[f][2]];
30
+ worst = Math.max(worst, need[i]);
31
+ touched.add(f);
32
+ }
33
+ }
34
+ if (!touched.size) return null;
35
+ engine.solve([...touched]);
36
+ return { frames: touched.size, max: round4(worst), how: "soles lifted onto the floor" };
37
+ }
@@ -0,0 +1,52 @@
1
+ // Deterministic auto-fixes the compiler applies after the first solve (spec.fix). Each fix edits
2
+ // the baked channels or the per-frame IK offsets and re-solves only the frames it touched, so QA
3
+ // still measures the final frames; every fix reports what it changed (counts, worst amount).
4
+ //
5
+ // spec.fix = { reach: true, slide: true, penetration: 0.02, self: true, retime: false } (false = all off)
6
+ // reach wrist targets past the arm's reach go onto it through a soft knee (no snap);
7
+ // ankle targets past the leg's reach lower the hips just enough, eased in and out
8
+ // slide a sole under the floor lifts its ankle onto it (ground.js); a planted heel or ball
9
+ // that drifts less than 1 cm stays put, and a foot turning on its planted ball (heel
10
+ // up) or heel pivots about it whatever the drift; a flat planted foot (heel and ball
11
+ // down) keeps its heading until one of them lifts (turn.js)
12
+ // penetration hands pushed out of scene boxes along the box normal when the depth past the
13
+ // allowance is at most this many metres (true = 0.02), eased in and out
14
+ // self arms out of the rest of the body on its fitted skin: the elbow turns about the
15
+ // shoulder-wrist line, the hand is pushed out along the skin (self.js)
16
+ // retime speed spikes get more frames, borrowed from the neighbouring keys
17
+ import { lowerHips, armReach } from "./reach.js";
18
+ import { lockFeet } from "./slide.js";
19
+ import { holdTurns } from "./turn.js";
20
+ import { liftSoles } from "./ground.js";
21
+ import { pushOut } from "./push.js";
22
+ import { retime } from "./retime.js";
23
+ import { selfClear } from "./self.js";
24
+
25
+ const FIX_KEYS = ["reach", "slide", "penetration", "self", "retime"];
26
+ export { FIX_KEYS };
27
+
28
+ export function fixOptions(fix) {
29
+ if (fix === false) return { reach: false, slide: false, penetration: 0, self: false, retime: false };
30
+ const f = fix ?? {};
31
+ const pen = f.penetration === true ? 0.02 : Math.min(0.05, Math.max(0, Number(f.penetration) || 0));
32
+ return { reach: f.reach !== false, slide: f.slide !== false, penetration: pen, self: f.self !== false, retime: f.retime === true };
33
+ }
34
+
35
+ /** The slide report with the flat feet whose turn waited (turn.js): turns { frames, max deg }. */
36
+ const withTurns = (turns, slide) => (turns ? { ...(slide ?? { frames: 0, how: "planted heel/ball held in place" }), turns, how: `${slide?.how ?? "planted heel/ball held in place"}; a flat planted foot keeps its heading until it lifts` } : slide);
37
+ const mergeReach = (a, b) => (a && b ? { frames: Math.max(a.frames, b.frames), eased: Math.max(a.eased, b.eased), max: Math.max(a.max, b.max), how: a.how } : a ?? b);
38
+
39
+ /** Runs the enabled fixes in order (retime first: it moves keys) and returns their reports. */
40
+ export function applyFixes(out, fix, engine, { whole = true } = {}) {
41
+ const fixed = {}, put = (name, report) => { if (report) fixed[name] = report; };
42
+ if (fix.retime && whole) put("retime", retime(out, engine));
43
+ if (fix.slide) put("ground", liftSoles(out, engine));
44
+ if (fix.reach) put("reachLegs", lowerHips(out, engine));
45
+ if (fix.slide) put("slide", withTurns(holdTurns(out, engine), lockFeet(out, engine)));
46
+ // a pivot can hold a foot past the leg's reach: the hips go down for those frames too
47
+ if (fix.reach && fixed.slide?.pivots) put("reachLegs", mergeReach(fixed.reachLegs, lowerHips(out, engine)));
48
+ if (fix.penetration > 0) put("penetration", pushOut(out, engine, fix.penetration));
49
+ if (fix.self) put("self", selfClear(out, engine));
50
+ if (fix.reach) put("reachArms", armReach(out));
51
+ return fixed;
52
+ }
@@ -0,0 +1,71 @@
1
+ // Shallow penetration push-out (spec.fix.penetration = max depth in metres past the allowance):
2
+ // a hand or forearm capsule inside a scene box moves its wrist IK target out along the box
3
+ // normal, eased over neighbouring frames. Deeper hits are staging problems and stay for QA.
4
+ // Frames where that arm is FK (armw 0) cannot be pushed and are counted as skipped.
5
+ import { add, scale, len } from "../compiler/math.js";
6
+ import { QA_LIMITS, allowance } from "../qa/checks.js";
7
+ import { CAPSULES, segPoints, sdBox, boxNormal } from "../qa/geometry.js";
8
+ import { round4 } from "./ease.js";
9
+
10
+ const EASE = 4, MARGIN = 0.002, PASSES = 3;
11
+ const ARM_PARTS = { l: CAPSULES.filter(c => c[0] === "hand_l" || c[0] === "farm_l"), r: CAPSULES.filter(c => c[0] === "hand_r" || c[0] === "farm_r") };
12
+
13
+ /** Per-frame push vectors for side k: the needed exit (normal * excess) summed over boxes. */
14
+ function needs(out, k, maxDepth, allow, stats) {
15
+ const { sk } = out.ctx, I = sk.index, n = out.frames.length, v = Array.from({ length: n }, () => null);
16
+ for (let i = 0; i < n; i++) {
17
+ const { s, c } = out.frames[i], objs = out.objectsAt(i);
18
+ let sum = null;
19
+ for (const [id, box] of Object.entries(objs)) {
20
+ if (!box.half || box.ignore) continue;
21
+ let best = null;
22
+ for (const [part, a, b, r] of ARM_PARTS[k]) {
23
+ const lim = allowance(allow, part, id) ?? QA_LIMITS.allow;
24
+ const pts = segPoints(s.P[I[a]], s.P[I[b]]), ds = pts.map(p => sdBox(p, box)), m = Math.min(...ds);
25
+ const excess = r - m - lim;
26
+ if (excess <= 0) continue;
27
+ if (excess > maxDepth) { stats.deep++; continue; }
28
+ if (!best || excess > best[0]) best = [excess, pts[ds.indexOf(m)]];
29
+ }
30
+ if (best) sum = add(sum ?? [0, 0, 0], scale(boxNormal(best[1], box), best[0] + MARGIN));
31
+ }
32
+ if (!sum) continue;
33
+ if (!(c[`armw_${k}`] > 1e-4)) { stats.skipped++; continue; }
34
+ v[i] = sum;
35
+ }
36
+ return v;
37
+ }
38
+
39
+ export function pushOut(out, engine, maxDepth) {
40
+ const allow = out.ctx.spec.checks?.allow, n = out.frames.length, stats = { deep: 0, skipped: 0 };
41
+ const hit = new Set();
42
+ let max = 0;
43
+ for (let pass = 0; pass < PASSES; pass++) {
44
+ stats.deep = 0; stats.skipped = 0;
45
+ const touched = new Set();
46
+ for (const k of ["l", "r"]) {
47
+ const v = needs(out, k, maxDepth, allow, stats), push = out.ctx.push[k];
48
+ // eased dilation of the vectors: neighbours get the strongest nearby push, scaled down
49
+ const eased = Array.from({ length: n }, () => null);
50
+ for (let i = 0; i < n; i++) {
51
+ if (!v[i]) continue;
52
+ hit.add(`${k}${i}`);
53
+ for (let j = Math.max(0, i - EASE - 1); j <= Math.min(n - 1, i + EASE + 1); j++) {
54
+ const t = 1 - Math.abs(i - j) / (EASE + 1), w = scale(v[i], t * t * (3 - 2 * t));
55
+ if (!eased[j] || len(w) > len(eased[j])) eased[j] = w;
56
+ }
57
+ }
58
+ for (let i = 0; i < n; i++) {
59
+ if (!eased[i]) continue;
60
+ const f = out.f0 + i, next = add(push.get(f) ?? [0, 0, 0], eased[i]);
61
+ push.set(f, next);
62
+ max = Math.max(max, len(next));
63
+ touched.add(f);
64
+ }
65
+ }
66
+ if (!touched.size) break;
67
+ engine.solve([...touched]);
68
+ }
69
+ if (!hit.size && !stats.skipped) return null;
70
+ return { frames: hit.size, max: round4(max), skipped: stats.skipped, tooDeep: stats.deep, how: "hand pushed out along the box normal" };
71
+ }
@@ -0,0 +1,46 @@
1
+ // Reach fixes: the legs lower the hips (eased over neighbouring frames) until every ankle target
2
+ // is reachable; the arms are clamped inline by pose.js (hooks.softReach), counted here. Both only
3
+ // take misses up to 5 cm: anything farther is a staging error that QA must keep reporting.
4
+ import { sub, len, dot } from "../compiler/math.js";
5
+ import { SIDES, REACH_FIX_MAX } from "../compiler/pose.js";
6
+ import { easeAround, round4 } from "./ease.js";
7
+
8
+ const EASE = 6;
9
+
10
+ export function lowerHips(out, engine) {
11
+ const { sk } = out.ctx, I = sk.index, P = sk.restWorldPos, root = out.baked.root;
12
+ if (!root) return null;
13
+ const n = out.frames.length, need = new Float64Array(n);
14
+ for (let i = 0; i < n; i++) {
15
+ const { s, diag, c } = out.frames[i];
16
+ for (const [k, side] of Object.entries(SIDES)) {
17
+ if (!(diag[`leg_miss_${k}`] > 5e-4) || !Array.isArray(c[`foot_${k}`])) continue;
18
+ const R = 0.999 * (len(sub(P[I[`${side}Leg`]], P[I[`${side}UpLeg`]])) + len(sub(P[I[`${side}Foot`]], P[I[`${side}Leg`]])));
19
+ const v = sub(c[`foot_${k}`], s.P[I[`${side}UpLeg`]]), disc = v[1] * v[1] - dot(v, v) + R * R;
20
+ // hips go down by d so that |v + d*up| = R (the smaller root); sideways misses only get closer
21
+ const d = disc >= 0 ? -v[1] - Math.sqrt(disc) : -v[1];
22
+ if (d > 0 && d <= REACH_FIX_MAX) need[i] = Math.max(need[i], d + 5e-4);
23
+ }
24
+ }
25
+ const frames = need.filter(d => d > 0).length;
26
+ if (!frames) return null;
27
+ const drop = easeAround(need, EASE), touched = [];
28
+ for (let i = 0; i < n; i++) {
29
+ if (!(drop[i] > 0)) continue;
30
+ const f = out.f0 + i, r = root[f];
31
+ root[f] = [r[0], r[1] - drop[i], r[2]];
32
+ touched.push(f);
33
+ }
34
+ engine.solve(touched);
35
+ return { frames, eased: touched.length, max: round4(Math.max(...need)), how: "hips lowered" };
36
+ }
37
+
38
+ /** Frames whose wrist target was past reach and got the soft clamp. */
39
+ export function armReach(out) {
40
+ let frames = 0, max = 0;
41
+ for (const fr of out.frames) for (const k of ["l", "r"]) {
42
+ const over = fr.diag[`reach_over_${k}`] ?? 0;
43
+ if (over > 1e-3) { frames++; max = Math.max(max, over); }
44
+ }
45
+ return frames ? { frames, max: round4(max), how: "wrist target clamped to reach" } : null;
46
+ }
@@ -0,0 +1,79 @@
1
+ // Retime (spec.fix.retime): where QA reports the motion too fast, the key intervals around it
2
+ // get longer, borrowing the frames from the neighbouring key intervals of the same window
3
+ // (slower or held stretches get shorter). Too fast means a one-frame pop, or a speed or
4
+ // acceleration limit the spec named in checks.limits (qa/style.js speedExcess); a fast but smooth
5
+ // motion is style and keeps its speed. Interpolation between keys scales with their spacing, so
6
+ // each interval is stretched by its own peak excess. The frame count and every key outside the
7
+ // windows stay where they are; the time map is kept on out.timeMap so frame-range checks follow.
8
+ import { QA_LIMITS } from "../qa/checks.js";
9
+ import { speedExcess } from "../qa/style.js";
10
+
11
+ const HEADROOM = 1.1, MIN_RATE = 0.35, PASSES = 3;
12
+
13
+ /** e[i] = how many times too fast frame i is (speed ratio, or sqrt of the acceleration ratio). */
14
+ const excess = out => {
15
+ const own = out.ctx.spec?.checks?.limits;
16
+ return speedExcess(out.ctx.sk, out, own, { ...QA_LIMITS, ...own });
17
+ };
18
+
19
+ /** Windows [a, b] (key frames) around each run of too-fast frames, merged where they overlap. */
20
+ function windows(e, keyFrames, n) {
21
+ const out = [];
22
+ for (let i = 1; i < n; i++) {
23
+ if (e[i] <= 1) continue;
24
+ let j = i;
25
+ while (j + 1 < n && e[j + 1] > 1) j++;
26
+ const w = Math.max(6, 2 * (j - i + 1));
27
+ const a = Math.max(0, ...keyFrames.filter(f => f <= i - 1 - w)), b = Math.min(n - 1, ...keyFrames.filter(f => f >= j + w));
28
+ if (out.length && a <= out[out.length - 1][1]) out[out.length - 1][1] = Math.max(out[out.length - 1][1], b);
29
+ else out.push([a, b]);
30
+ i = j;
31
+ }
32
+ return out;
33
+ }
34
+
35
+ /** New times for the breakpoints of window [a, b]: each interval scaled by max(peak excess, alpha). */
36
+ function spread(e, cuts) {
37
+ const total = cuts[cuts.length - 1] - cuts[0];
38
+ const segs = cuts.slice(1).map((k, i) => { let m = 0; for (let f = Math.floor(cuts[i]) + 1; f <= Math.ceil(k); f++) m = Math.max(m, e[f] ?? 0); return { len: k - cuts[i], need: HEADROOM * m }; });
39
+ const sum = alpha => segs.reduce((acc, s) => acc + s.len * Math.max(s.need, alpha), 0);
40
+ let lo = MIN_RATE, hi = 1;
41
+ for (let it = 0; it < 40; it++) { const mid = (lo + hi) / 2; if (sum(mid) > total) hi = mid; else lo = mid; }
42
+ const dur = segs.map(s => s.len * Math.max(s.need, lo)), scale = total / dur.reduce((acc, d) => acc + d, 0);
43
+ const T = [cuts[0]];
44
+ dur.forEach((d, i) => T.push(T[i] + d * scale));
45
+ T[T.length - 1] = cuts[cuts.length - 1];
46
+ return { T, stretch: Math.max(...dur.map((d, i) => (d * scale) / segs[i].len)) };
47
+ }
48
+
49
+ export function retime(out, engine) {
50
+ const n = out.frames.length, maps = [];
51
+ let segments = 0, stretch = 1;
52
+ for (let pass = 0; pass < PASSES; pass++) {
53
+ const e = excess(out), keyFrames = [...new Set(out.keys.map(k => k.f))].sort((p, q) => p - q);
54
+ const wins = windows(e, keyFrames, n);
55
+ if (!wins.length) break;
56
+ const pieces = wins.map(([a, b]) => {
57
+ const cuts = [...new Set([a, ...keyFrames.filter(f => f > a && f < b), b])].sort((p, q) => p - q), r = spread(e, cuts);
58
+ stretch = Math.max(stretch, r.stretch);
59
+ return { cuts, T: r.T };
60
+ });
61
+ segments += wins.length;
62
+ const map = f => {
63
+ for (const { cuts, T } of pieces) {
64
+ if (f < cuts[0] || f > cuts[cuts.length - 1]) continue;
65
+ let i = 0;
66
+ while (i < cuts.length - 2 && f > cuts[i + 1]) i++;
67
+ return T[i] + (T[i + 1] - T[i]) * (f - cuts[i]) / (cuts[i + 1] - cuts[i]);
68
+ }
69
+ return f;
70
+ };
71
+ maps.push(map);
72
+ engine.rebake(out.keys.map(k => ({ ...k, f: map(k.f) })));
73
+ engine.solve(engine.all());
74
+ }
75
+ if (!maps.length) return null;
76
+ out.timeMap = f => maps.reduce((x, m) => m(x), f);
77
+ const left = excess(out).filter(x => x > 1).length;
78
+ return { windows: segments, maxStretch: Math.round(stretch * 100) / 100, stillFast: left, how: "key intervals around speed spikes stretched" };
79
+ }