cozyclay 2.1.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (577) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +12 -30
  3. package/bin/agent/agent-routes.mjs +40 -113
  4. package/bin/agent/agent-runner.mjs +9 -10
  5. package/bin/agent/cliproxy-provider.mjs +135 -0
  6. package/bin/agent/credential-store.mjs +30 -7
  7. package/bin/agent/pi-tools.mjs +4 -1
  8. package/bin/agent/provider-keys.mjs +32 -0
  9. package/bin/agent/provider-login.mjs +110 -0
  10. package/bin/agent/provider-routes.mjs +99 -0
  11. package/bin/agent/providers.mjs +19 -97
  12. package/bin/agent/script-tool.mjs +17 -4
  13. package/bin/agent/studio-motion-reference.mjs +23 -0
  14. package/bin/agent/studio-prompt.mjs +1 -1
  15. package/bin/agent/studio-rebase-text.mjs +32 -0
  16. package/bin/agent/studio-rebase.mjs +26 -39
  17. package/bin/agent/studio-tools.mjs +9 -28
  18. package/bin/agent/tool-arguments.mjs +49 -0
  19. package/bin/agent/turn-frames.mjs +66 -0
  20. package/bin/codex-auth.mjs +9 -2
  21. package/bin/cozyclay.mjs +10 -116
  22. package/bin/live/cli.mjs +3 -3
  23. package/bin/mcp-runtime.mjs +2 -12
  24. package/bin/package-signature.mjs +0 -4
  25. package/dist/app/index.html +7 -6
  26. package/dist/assets/EXRLoader-CvVIvGVE.js +1 -0
  27. package/dist/assets/analytics-C9sUnndd.js +1 -0
  28. package/dist/assets/app-CCh--1Hc.js +5087 -0
  29. package/dist/assets/app-f-fkV5uP.css +1 -0
  30. package/dist/assets/compile-worker-CkxiD6lH.js +5 -0
  31. package/dist/assets/fflate.module-KbGj5-H6.js +5 -0
  32. package/dist/assets/{first-shot-handoff-msLI71g1.js → first-shot-handoff-CC4xe4HM.js} +1 -1
  33. package/dist/assets/{landing-ChUvB_zL.js → landing-DjHD8u7k.js} +1 -1
  34. package/dist/assets/scene-asset-sync-Hx_FzdOH.js +20 -0
  35. package/dist/assets/scene-asset-sync-_S8NQl5B.css +1 -0
  36. package/dist/assets/workflow-C4yKH1p4.js +179 -0
  37. package/dist/cozyclay-package.json +1 -1
  38. package/dist/index.html +3 -3
  39. package/dist/sw.js +3 -6
  40. package/dist/workflow/index.html +5 -4
  41. package/mcp/LIVE-PROTOCOL.md +7 -47
  42. package/mcp/README.md +16 -26
  43. package/mcp/live-hub.mjs +6 -10
  44. package/mcp/package.json +1 -3
  45. package/mcp/tool-handlers.mjs +4 -168
  46. package/package.json +3 -22
  47. package/src/App.jsx +150 -2624
  48. package/src/agent-progress.js +79 -0
  49. package/src/agent-turn-mark.js +30 -0
  50. package/src/analytics.js +1 -19
  51. package/src/app-context.js +5 -0
  52. package/src/app-stage.jsx +2 -34
  53. package/src/asset-pane.jsx +6 -5
  54. package/src/camera-block.js +21 -0
  55. package/src/camera-direction.js +120 -0
  56. package/src/character-kind.js +3 -9
  57. package/src/command-bus.js +8 -6
  58. package/src/commands/cast.js +5 -34
  59. package/src/commands/elements/character.js +1 -1
  60. package/src/commands/elements/motion.js +1 -6
  61. package/src/commands/elements/object.js +6 -2
  62. package/src/commands/elements/shot.js +4 -1
  63. package/src/commands/elements.js +38 -14
  64. package/src/commands/index.js +3 -1
  65. package/src/commands/kits.js +37 -0
  66. package/src/commands/motion-timing.js +57 -0
  67. package/src/commands/motion.js +9 -152
  68. package/src/commands/motion3-compile.js +65 -0
  69. package/src/commands/motion3.js +207 -0
  70. package/src/commands/objects.js +6 -4
  71. package/src/commands/shot.js +49 -11
  72. package/src/commands/stage.js +14 -1
  73. package/src/commands/view.js +4 -1
  74. package/src/document-mirrors.js +42 -0
  75. package/src/document-store.js +4 -0
  76. package/src/domains/cast.js +19 -194
  77. package/src/domains/motion/motion-range-pin-rules.js +1 -1
  78. package/src/domains/motion/take-source.js +51 -0
  79. package/src/domains/motion.js +60 -1838
  80. package/src/domains/scenes.js +12 -55
  81. package/src/domains/shots.js +2 -2
  82. package/src/domains/stage.js +4 -2
  83. package/src/draft-ghost.js +46 -0
  84. package/src/draft-ghost.jsx +129 -0
  85. package/src/dualview.jsx +4 -3
  86. package/src/effects/EffectsLayer.jsx +112 -0
  87. package/src/effects/effect-meshes.js +232 -0
  88. package/src/effects/effect-record.js +103 -0
  89. package/src/effects/emitter.js +59 -0
  90. package/src/effects/simulate.js +261 -0
  91. package/src/effects/state.js +59 -0
  92. package/src/execution-telemetry.js +2 -3
  93. package/src/first-success-guide.jsx +5 -5
  94. package/src/ghost-set.js +257 -0
  95. package/src/held-object-edit.jsx +113 -0
  96. package/src/held-object-pose.js +175 -0
  97. package/src/hierarchy-model.js +1 -1
  98. package/src/hierarchy-panel.jsx +1 -1
  99. package/src/imported-mesh.jsx +103 -0
  100. package/src/kits/held.js +84 -0
  101. package/src/kits/index.js +70 -0
  102. package/src/kits/smoking.js +57 -0
  103. package/src/light-blockers.js +93 -0
  104. package/src/motion/beat-board-model.js +84 -0
  105. package/src/motion/beat-board.css +124 -0
  106. package/src/motion/beat-board.jsx +59 -0
  107. package/src/motion/center-of-mass.js +66 -0
  108. package/src/motion/fix-collisions.js +850 -0
  109. package/src/motion/physics-review.js +118 -0
  110. package/src/{ardy → motion}/physics-support.js +1 -76
  111. package/src/motion/rig-snapshot.js +22 -0
  112. package/src/motion/take-segments.jsx +140 -0
  113. package/src/{ardy → motion}/timeline-coordinates.js +0 -21
  114. package/src/{ardy → motion}/timeline.css +1 -2
  115. package/src/{ardy → motion}/timeline.jsx +42 -413
  116. package/src/motion/waypoints.js +76 -0
  117. package/src/motion3/beats.js +83 -0
  118. package/src/motion3/body.js +158 -0
  119. package/src/motion3/bones.js +47 -0
  120. package/src/motion3/carried.js +47 -0
  121. package/src/motion3/compile-job.js +75 -0
  122. package/src/motion3/compile-worker.js +10 -0
  123. package/src/motion3/compiler/anchors.js +105 -0
  124. package/src/motion3/compiler/arm-orient.js +128 -0
  125. package/src/motion3/compiler/channels.js +125 -0
  126. package/src/motion3/compiler/compile.js +205 -0
  127. package/src/motion3/compiler/gait-rhythm.js +97 -0
  128. package/src/motion3/compiler/grip.js +171 -0
  129. package/src/motion3/compiler/head-steady.js +48 -0
  130. package/src/motion3/compiler/hold.js +134 -0
  131. package/src/motion3/compiler/idle.js +113 -0
  132. package/src/motion3/compiler/limbs.js +170 -0
  133. package/src/motion3/compiler/math.js +72 -0
  134. package/src/motion3/compiler/pose.js +162 -0
  135. package/src/motion3/compiler/quat.js +49 -0
  136. package/src/motion3/compiler/skeleton.js +74 -0
  137. package/src/motion3/compiler/validate-common.js +32 -0
  138. package/src/motion3/compiler/validate-goals.js +118 -0
  139. package/src/motion3/compiler/validate-objects.js +143 -0
  140. package/src/motion3/compiler/validate-parts.js +116 -0
  141. package/src/motion3/compiler/validate.js +122 -0
  142. package/src/motion3/compiler/vocab.js +73 -0
  143. package/src/motion3/compiler/walk-pelvis.js +275 -0
  144. package/src/motion3/compiler/walk.js +207 -0
  145. package/src/motion3/compiler/wrist-clamp.js +32 -0
  146. package/src/motion3/delta.js +47 -0
  147. package/src/motion3/describe.js +174 -0
  148. package/src/motion3/docs.js +27 -0
  149. package/src/motion3/edit-carry.js +21 -0
  150. package/src/motion3/fix/ease.js +16 -0
  151. package/src/motion3/fix/ground.js +37 -0
  152. package/src/motion3/fix/index.js +52 -0
  153. package/src/motion3/fix/push.js +71 -0
  154. package/src/motion3/fix/reach.js +46 -0
  155. package/src/motion3/fix/retime.js +79 -0
  156. package/src/motion3/fix/self.js +211 -0
  157. package/src/motion3/fix/slide.js +70 -0
  158. package/src/motion3/fix/turn.js +51 -0
  159. package/src/motion3/follow.js +39 -0
  160. package/src/motion3/free-motion.js +173 -0
  161. package/src/motion3/grip-choice.js +130 -0
  162. package/src/motion3/hold-plan.js +157 -0
  163. package/src/motion3/json-patch.js +77 -0
  164. package/src/motion3/key-aim.js +105 -0
  165. package/src/motion3/key-edit.js +82 -0
  166. package/src/motion3/lenient.js +127 -0
  167. package/src/motion3/object-keys.js +236 -0
  168. package/src/motion3/object-pose.js +182 -0
  169. package/src/motion3/object-tracks.js +12 -0
  170. package/src/motion3/pipeline.js +102 -0
  171. package/src/motion3/playback.js +51 -0
  172. package/src/motion3/probes.js +91 -0
  173. package/src/motion3/qa/body-checks.js +34 -0
  174. package/src/motion3/qa/check-names.js +14 -0
  175. package/src/motion3/qa/checks.js +121 -0
  176. package/src/motion3/qa/contact-checks.js +51 -0
  177. package/src/motion3/qa/explain.js +80 -0
  178. package/src/motion3/qa/fmt.js +5 -0
  179. package/src/motion3/qa/foot-checks.js +72 -0
  180. package/src/motion3/qa/geometry.js +77 -0
  181. package/src/motion3/qa/goals.js +191 -0
  182. package/src/motion3/qa/ground.js +60 -0
  183. package/src/motion3/qa/hints-contact.js +179 -0
  184. package/src/motion3/qa/hints-goal.js +160 -0
  185. package/src/motion3/qa/hints-knee.js +24 -0
  186. package/src/motion3/qa/hints-motion.js +209 -0
  187. package/src/motion3/qa/hints-object.js +91 -0
  188. package/src/motion3/qa/index.js +42 -0
  189. package/src/motion3/qa/key-checks.js +82 -0
  190. package/src/motion3/qa/near.js +96 -0
  191. package/src/motion3/qa/object-checks.js +135 -0
  192. package/src/motion3/qa/patch.js +161 -0
  193. package/src/motion3/qa/severity.js +38 -0
  194. package/src/motion3/qa/spec-checks.js +65 -0
  195. package/src/motion3/qa/style.js +218 -0
  196. package/src/motion3/qa/wrist-checks.js +69 -0
  197. package/src/motion3/qa/wrist-obs.js +60 -0
  198. package/src/motion3/refs.js +120 -0
  199. package/src/motion3/repair-tune.js +121 -0
  200. package/src/motion3/repair.js +83 -0
  201. package/src/motion3/report.js +67 -0
  202. package/src/motion3/rigs/index.js +14 -0
  203. package/src/motion3/rigs/x-bot-tpose-body.json +1 -0
  204. package/src/motion3/rigs/x-bot-tpose.json +1 -0
  205. package/src/motion3/rigs/y-bot-tpose-body.json +1 -0
  206. package/src/motion3/rigs/y-bot-tpose.json +1 -0
  207. package/src/motion3/scene.js +60 -0
  208. package/src/motion3/schema/brief.js +89 -0
  209. package/src/motion3/schema/fields.js +44 -0
  210. package/src/motion3/schema/spec-examples.js +46 -0
  211. package/src/motion3/schema/topics.js +111 -0
  212. package/src/motion3/schema/types.js +147 -0
  213. package/src/motion3/search.js +117 -0
  214. package/src/motion3/second-hand.js +112 -0
  215. package/src/motion3/skills/basic.js +82 -0
  216. package/src/motion3/skills/book.js +76 -0
  217. package/src/motion3/skills/car.js +145 -0
  218. package/src/motion3/skills/frames-auto.js +29 -0
  219. package/src/motion3/skills/index.js +83 -0
  220. package/src/motion3/skills/info.js +65 -0
  221. package/src/motion3/skills/smoke-kit.js +109 -0
  222. package/src/motion3/skills/smoke.js +160 -0
  223. package/src/motion3/spec-store.js +59 -0
  224. package/src/motion3/take-edit.js +155 -0
  225. package/src/motion3/take.js +63 -0
  226. package/src/motion3/timeline.js +127 -0
  227. package/src/motion3/views.js +121 -0
  228. package/src/motion3/walk-plan.js +119 -0
  229. package/src/object-catalog.jsx +7 -1
  230. package/src/object-colors.js +98 -0
  231. package/src/object-gizmo-parts.js +115 -0
  232. package/src/object-gizmo.jsx +37 -122
  233. package/src/object-glow.js +139 -0
  234. package/src/object-hidden.js +33 -0
  235. package/src/object-look.js +196 -0
  236. package/src/object-size.js +10 -0
  237. package/src/object-travel.js +71 -6
  238. package/src/panels/CameraPanel.jsx +7 -7
  239. package/src/panels/HdrEnvironmentControls.jsx +59 -0
  240. package/src/panels/LightPanel.jsx +25 -4
  241. package/src/panels/ObjectLookPanel.jsx +88 -0
  242. package/src/panels/PosePanel.jsx +2 -17
  243. package/src/panels/RigControlPanel.jsx +5 -53
  244. package/src/panels/SubjectBox.jsx +4 -1
  245. package/src/panels/details.css +14 -0
  246. package/src/posestudio.jsx +4 -27
  247. package/src/project-browser.jsx +3 -4
  248. package/src/project-resources.js +18 -31
  249. package/src/project.js +13 -104
  250. package/src/props.jsx +25 -280
  251. package/src/rail-dot-drag.js +23 -0
  252. package/src/render-passes.js +10 -0
  253. package/src/resource-status.jsx +1 -0
  254. package/src/result-modal.jsx +4 -19
  255. package/src/room.jsx +20 -12
  256. package/src/scene-assets.js +8 -6
  257. package/src/scene-light-place.js +72 -0
  258. package/src/scene-lights.jsx +148 -0
  259. package/src/scene-objects.js +31 -139
  260. package/src/scenes.js +15 -105
  261. package/src/semantic-edit.js +2 -5
  262. package/src/set-pieces.jsx +183 -0
  263. package/src/settings-menu.jsx +3 -11
  264. package/src/shell/BottomDock.jsx +9 -51
  265. package/src/shell/DetailsSlot.jsx +16 -200
  266. package/src/shell/GhostsMenuGroup.jsx +74 -0
  267. package/src/shell/LibrarySlot.jsx +1 -3
  268. package/src/shell/MenuBar.jsx +3 -3
  269. package/src/shell/PreferencesDialog.jsx +15 -264
  270. package/src/shell/PreferencesSlot.jsx +2 -8
  271. package/src/shell/TopBar.jsx +5 -84
  272. package/src/shell/ViewportToolbar.jsx +10 -70
  273. package/src/shell/agent-glass.css +19 -6
  274. package/src/shell/details-selection.js +42 -0
  275. package/src/shell/ghosts-menu.css +22 -0
  276. package/src/shell/glass-regions.css +29 -3
  277. package/src/shell/preferences.css +6 -1
  278. package/src/shot-authoring.js +4 -7
  279. package/src/soft-shadows.js +37 -0
  280. package/src/stage-attach-frames.js +12 -12
  281. package/src/stage-canvas-rigs.jsx +3 -3
  282. package/src/stage-capture.jsx +42 -8
  283. package/src/stage-characters.jsx +22 -216
  284. package/src/stage-editor-gizmos.jsx +6 -6
  285. package/src/stage-environment.js +93 -0
  286. package/src/stage-hdr-builtins.js +107 -0
  287. package/src/stage-hdr-image.js +116 -0
  288. package/src/stage-hdr-library.js +70 -0
  289. package/src/stage-hdr-map.js +94 -0
  290. package/src/stage-hdr.js +130 -0
  291. package/src/stage-lighting.js +149 -0
  292. package/src/stage-occlusion.js +210 -0
  293. package/src/stage-sky.jsx +159 -0
  294. package/src/stage-startup-defaults.js +5 -9
  295. package/src/studio-actions.js +12 -14
  296. package/src/studio-agent-commands.js +19 -16
  297. package/src/studio-agent-motion.js +9 -9
  298. package/src/studio-agent-protocol.js +41 -71
  299. package/src/studio-app-binding.js +25 -45
  300. package/src/studio-asset-list.js +17 -0
  301. package/src/studio-contact-sheet.js +37 -5
  302. package/src/studio-elements.js +27 -7
  303. package/src/studio-entity-bounds.js +18 -0
  304. package/src/studio-error-codes.js +8 -0
  305. package/src/studio-export-framing.js +152 -0
  306. package/src/studio-lighting-check.js +103 -0
  307. package/src/studio-motion-visual.js +150 -0
  308. package/src/studio-schema-errors.js +80 -0
  309. package/src/studio-support-gap.js +74 -0
  310. package/src/styles.css +1 -19
  311. package/src/timeline-defaults.js +5 -0
  312. package/src/timeline-extent.js +8 -20
  313. package/src/tutorial-walk.js +17 -0
  314. package/src/ui-scrub.js +1 -1
  315. package/src/use-shot-camera-tracks.js +12 -0
  316. package/src/view-ghosts.js +97 -0
  317. package/src/view-ghosts.jsx +49 -0
  318. package/src/workflow/AgentPanel.jsx +47 -166
  319. package/src/workflow/AgentProgressCard.jsx +107 -0
  320. package/src/workflow/AgentReplyText.jsx +37 -0
  321. package/src/workflow/AgentTurnSteps.jsx +84 -0
  322. package/src/workflow/LoginPanel.jsx +140 -0
  323. package/src/workflow/WorkflowBuilder.jsx +7 -23
  324. package/src/workflow/agent-client.js +54 -51
  325. package/src/workflow/agent-panel.css +71 -1
  326. package/src/workflow/agent-progress-card.css +167 -0
  327. package/src/workflow/agent-progress-view.js +47 -0
  328. package/src/workflow/agent-turn-steps.css +113 -0
  329. package/src/workflow/agent-turn.js +191 -0
  330. package/src/workflow/login-client.js +34 -0
  331. package/src/workflow/motion-input.js +18 -31
  332. package/src/workflow/node-schema.js +1 -1
  333. package/src/workflow/reply-markdown.js +69 -0
  334. package/src/workflow/scene-asset-sync.js +8 -8
  335. package/tools/agent-session-stats.mjs +61 -0
  336. package/tools/dev-full.mjs +5 -56
  337. package/tools/eslint-baseline.json +12 -13
  338. package/tools/knip-baseline.json +0 -179
  339. package/tools/loc-baseline.json +31 -81
  340. package/tools/motion3/extract-body.mjs +103 -0
  341. package/tools/motion3/extract-rig.mjs +85 -0
  342. package/tools/process-supervisor.mjs +0 -88
  343. package/tools/run-tests.mjs +8 -124
  344. package/bin/agent/motion-runtime.mjs +0 -76
  345. package/dist/ardy/cskel27-rest.json +0 -557
  346. package/dist/assets/analytics-C9pMpW7s.js +0 -1
  347. package/dist/assets/app-B_2XB9rw.css +0 -1
  348. package/dist/assets/app-DAuvKPP8.js +0 -4920
  349. package/dist/assets/config-Bx7pYsD9.js +0 -1
  350. package/dist/assets/config-DeoFTYzb.css +0 -1
  351. package/dist/assets/demo-CvKuBgy5.js +0 -1
  352. package/dist/assets/scene-asset-sync-DKkpjKBV.js +0 -19
  353. package/dist/assets/scene-asset-sync-DjJ_cJ2R.css +0 -1
  354. package/dist/assets/ticket-BPk2Ni95.js +0 -1
  355. package/dist/assets/workflow-jJRBR94Z.js +0 -179
  356. package/dist/demo/walk-then-stop.npz +0 -0
  357. package/mcp/ardy-prompts.mjs +0 -218
  358. package/src/ardy/auto-fix-panel.css +0 -108
  359. package/src/ardy/auto-physics.js +0 -1194
  360. package/src/ardy/client.js +0 -153
  361. package/src/ardy/convert.js +0 -313
  362. package/src/ardy/cskel27-neutral.js +0 -39
  363. package/src/ardy/cskel27.js +0 -77
  364. package/src/ardy/export.js +0 -165
  365. package/src/ardy/fix-collisions.js +0 -2257
  366. package/src/ardy/foot-lock.js +0 -230
  367. package/src/ardy/motion-calibration.js +0 -118
  368. package/src/ardy/motion-edit.js +0 -213
  369. package/src/ardy/motion-url.js +0 -55
  370. package/src/ardy/mp4-duration.js +0 -102
  371. package/src/ardy/npz.js +0 -595
  372. package/src/ardy/physics-panel.css +0 -21
  373. package/src/ardy/physics-panel.jsx +0 -89
  374. package/src/ardy/physics-refine.js +0 -111
  375. package/src/ardy/physics-review.js +0 -567
  376. package/src/ardy/physics-temporal.js +0 -54
  377. package/src/ardy/platform-fit-panel.css +0 -64
  378. package/src/ardy/platform-fit-panel.jsx +0 -41
  379. package/src/ardy/platform-fit.js +0 -326
  380. package/src/ardy/playback.js +0 -711
  381. package/src/ardy/pose-pin.js +0 -97
  382. package/src/ardy/prompt-clips.js +0 -30
  383. package/src/ardy/retime.js +0 -211
  384. package/src/ardy/root-drop.js +0 -369
  385. package/src/ardy/timeline-resize.js +0 -13
  386. package/src/ardy/to-cskel27.js +0 -225
  387. package/src/ardy/trim.js +0 -33
  388. package/src/ardy/waypoints.js +0 -369
  389. package/src/ardy-bridge-contract.js +0 -281
  390. package/src/domains/motion/motion-video-capture.js +0 -664
  391. package/src/i2v-motion-client.js +0 -77
  392. package/src/i2v-motion-studio.jsx +0 -180
  393. package/src/line-edit.js +0 -1348
  394. package/src/motion/generation.js +0 -105
  395. package/src/motion-readiness-ui.jsx +0 -67
  396. package/src/motion-readiness.js +0 -40
  397. package/src/motion-resources.js +0 -177
  398. package/src/motion-store.js +0 -65
  399. package/src/motion-trail.js +0 -601
  400. package/src/multimodel-ingest.js +0 -394
  401. package/src/panels/PromptBlocksPanel.jsx +0 -444
  402. package/src/panels/VideoCapturePanel.jsx +0 -181
  403. package/src/pose-extract/browser.js +0 -42
  404. package/src/pose-extract/detector.js +0 -100
  405. package/src/pose-extract/fit.js +0 -227
  406. package/src/pose-extract/image-frame.js +0 -51
  407. package/src/pose-extract/index.js +0 -58
  408. package/src/pose-extract/keyframes.js +0 -74
  409. package/src/pose-extract/landmarks.js +0 -162
  410. package/src/pose-extract/mirror.js +0 -153
  411. package/src/pose-extract/take.js +0 -131
  412. package/src/pose-extract/video-frames.js +0 -91
  413. package/src/take-recipe.js +0 -165
  414. package/src/trail-key-conflicts.js +0 -39
  415. package/src/trail-pick.js +0 -59
  416. package/src/use-i2v-capture-modal.js +0 -15
  417. package/tools/ardy/BRIDGE.md +0 -37
  418. package/tools/ardy/GVHMR-NPZ.md +0 -68
  419. package/tools/ardy/README.md +0 -124
  420. package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
  421. package/tools/ardy/artifacts.mjs +0 -34
  422. package/tools/ardy/bridge.mjs +0 -1732
  423. package/tools/ardy/bvh-cskel27.mjs +0 -1265
  424. package/tools/ardy/cclay_constrained_generate.py +0 -561
  425. package/tools/ardy/cclay_gvhmr_worker.py +0 -264
  426. package/tools/ardy/cclay_motion_edit.py +0 -510
  427. package/tools/ardy/cclay_sequence_generate.py +0 -705
  428. package/tools/ardy/cclay_worker.py +0 -304
  429. package/tools/ardy/dump-npz.py +0 -205
  430. package/tools/ardy/extract-rest.mjs +0 -299
  431. package/tools/ardy/extract.mjs +0 -507
  432. package/tools/ardy/footage.mjs +0 -467
  433. package/tools/ardy/gvhmr-floor.mjs +0 -102
  434. package/tools/ardy/gvhmr-to-cskel27.mjs +0 -21
  435. package/tools/ardy/gvhmr_fastpath.py +0 -168
  436. package/tools/ardy/gvhmr_trajectory.py +0 -240
  437. package/tools/ardy/merge-text-encoder.py +0 -160
  438. package/tools/ardy/mocap-metrics.mjs +0 -200
  439. package/tools/ardy/mocap-quality-gate.mjs +0 -110
  440. package/tools/ardy/motion-stabilize.mjs +0 -348
  441. package/tools/ardy/npz.mjs +0 -414
  442. package/tools/ardy/pose-to-npz.mjs +0 -106
  443. package/tools/ardy/prompt-limits.mjs +0 -5
  444. package/tools/ardy/run-edit-on-box.sh +0 -73
  445. package/tools/ardy/run-local.mjs +0 -233
  446. package/tools/ardy/run-on-box.sh +0 -593
  447. package/tools/ardy/run-sequence-on-box.sh +0 -177
  448. package/tools/ardy/runners/gvhmr-worker.mjs +0 -206
  449. package/tools/ardy/runners/index.mjs +0 -18
  450. package/tools/ardy/runners/local.mjs +0 -455
  451. package/tools/ardy/runners/proc.mjs +0 -125
  452. package/tools/ardy/runners/remote.mjs +0 -252
  453. package/tools/ardy/setup-local.mjs +0 -189
  454. package/tools/ardy/setup-text-encoder-on-box.sh +0 -93
  455. package/tools/ardy/setup-text-encoder.py +0 -306
  456. package/tools/ardy/smpl-cskel27.mjs +0 -178
  457. package/tools/ardy/visual-qa.mjs +0 -196
  458. package/tools/ardy/vq-car.mjs +0 -52
  459. package/tools/bench/EXP3.md +0 -61
  460. package/tools/bench/cclay_bench_extract_incam.py +0 -125
  461. package/tools/bench/cclay_bench_extract_obs.py +0 -204
  462. package/tools/bench/cclay_bench_runner.py +0 -42
  463. package/tools/bench/cube-contact.mjs +0 -162
  464. package/tools/bench/exp3.mjs +0 -152
  465. package/tools/bench/extract-bench-lib.mjs +0 -216
  466. package/tools/bench/extract-bench.mjs +0 -302
  467. package/tools/bench/fal-generate.mjs +0 -88
  468. package/tools/bench/fit/README.md +0 -189
  469. package/tools/bench/fit/camera.mjs +0 -59
  470. package/tools/bench/fit/contact.mjs +0 -419
  471. package/tools/bench/fit/footlock.mjs +0 -234
  472. package/tools/bench/fit/motion.mjs +0 -85
  473. package/tools/bench/fit/pin.mjs +0 -27
  474. package/tools/bench/fit/remote.mjs +0 -105
  475. package/tools/bench/fit-bench.mjs +0 -126
  476. package/tools/bench/fit-sanity.mjs +0 -45
  477. package/tools/bench/metrics.mjs +0 -313
  478. package/tools/bench/obs/depth.mjs +0 -258
  479. package/tools/bench/obs/extrinsics.mjs +0 -175
  480. package/tools/bench/obs/fit_mannequin_betas.py +0 -340
  481. package/tools/bench/obs/ground.mjs +0 -294
  482. package/tools/bench/obs/heading.mjs +0 -151
  483. package/tools/bench/obs/ladder.mjs +0 -555
  484. package/tools/bench/obs/mannequin-betas.json +0 -124
  485. package/tools/bench/obs/remote.mjs +0 -83
  486. package/tools/bench/obs/rest_joints.py +0 -52
  487. package/tools/bench/obs/ybot-targets.mjs +0 -49
  488. package/tools/bench/obs-bench.mjs +0 -520
  489. package/tools/bench/score.mjs +0 -343
  490. package/tools/bench/summarize.mjs +0 -86
  491. package/tools/demo-worker/README.md +0 -137
  492. package/tools/demo-worker/api-client.mjs +0 -233
  493. package/tools/demo-worker/generate.mjs +0 -301
  494. package/tools/demo-worker/index.mjs +0 -466
  495. package/tools/demo-worker/sign-core.mjs +0 -112
  496. package/tools/demo-worker/sign.mjs +0 -161
  497. package/tools/gt-render/browser.mjs +0 -182
  498. package/tools/gt-render/camera-math.mjs +0 -276
  499. package/tools/gt-render/page.mjs +0 -267
  500. package/tools/gt-render/render.mjs +0 -514
  501. package/tools/gt-render/scene-box.mjs +0 -31
  502. package/tools/gt-render/take-transform.mjs +0 -87
  503. package/tools/kimodo/compare-seam.mjs +0 -108
  504. package/tools/kimodo/constraints.mjs +0 -244
  505. package/tools/kimodo/edit.mjs +0 -259
  506. package/tools/kimodo/effector-constraints.mjs +0 -200
  507. package/tools/kimodo/generate.mjs +0 -646
  508. package/tools/kimodo/local-output.mjs +0 -265
  509. package/tools/kimodo/measure-pose.mjs +0 -65
  510. package/tools/kimodo/measure-preserve.mjs +0 -899
  511. package/tools/kimodo/measure-waypoints.mjs +0 -55
  512. package/tools/kimodo/mlx-generate.py +0 -111
  513. package/tools/kimodo/pose-constraints.mjs +0 -248
  514. package/tools/kimodo/preserve-mask.mjs +0 -543
  515. package/tools/kimodo/read-npz.mjs +0 -189
  516. package/tools/kimodo/run-edit-on-box.mjs +0 -155
  517. package/tools/kimodo/run-sequence-on-box.mjs +0 -178
  518. package/tools/kimodo/runner.mjs +0 -284
  519. package/tools/kimodo/seam-batch.mjs +0 -77
  520. package/tools/kimodo/setup-local.mjs +0 -30
  521. package/tools/kimodo/setup-on-box.sh +0 -339
  522. package/tools/kimodo/soma77-to-cskel27.mjs +0 -305
  523. package/tools/morphgs/assets/gen_truth.py +0 -39
  524. package/tools/morphgs/assets/mesh_ori_rig.txt +0 -27
  525. package/tools/morphgs/assets/playback-check.mjs +0 -30
  526. package/tools/morphgs/demo-gate.sh +0 -26
  527. package/tools/morphgs/fbx2morphgs.mjs +0 -68
  528. package/tools/morphgs/morphgs-to-cskel27.mjs +0 -105
  529. package/tools/morphgs/patches/preprocess_src-none-mode.patch +0 -76
  530. package/tools/morphgs/setup-on-cluster.sh +0 -126
  531. package/tools/projflow/driver.py +0 -1069
  532. package/tools/projflow/generate.mjs +0 -805
  533. package/tools/projflow/hml22-to-cskel27.mjs +0 -735
  534. package/tools/projflow/line-edit-job.mjs +0 -547
  535. package/tools/projflow/replay.mjs +0 -452
  536. package/tools/projflow/runner.mjs +0 -269
  537. package/tools/projflow/service.mjs +0 -742
  538. package/tools/track/DESIGN.md +0 -513
  539. package/tools/track/backfill-provenance.mjs +0 -116
  540. package/tools/track/budget.mjs +0 -8
  541. package/tools/track/check-rig.mjs +0 -209
  542. package/tools/track/diagnostics.schema.json +0 -60
  543. package/tools/track/export-rig.mjs +0 -256
  544. package/tools/track/fallback.mjs +0 -7
  545. package/tools/track/fk-parity-fixture.mjs +0 -161
  546. package/tools/track/gate.mjs +0 -349
  547. package/tools/track/masks.mjs +0 -318
  548. package/tools/track/metrics.mjs +0 -201
  549. package/tools/track/publish-obs.mjs +0 -108
  550. package/tools/track/py/check_env.py +0 -61
  551. package/tools/track/py/eval_lr.py +0 -229
  552. package/tools/track/py/lr_viterbi.py +0 -294
  553. package/tools/track/py/masks.py +0 -405
  554. package/tools/track/py/objective.py +0 -382
  555. package/tools/track/py/rig.py +0 -508
  556. package/tools/track/py/scene.py +0 -326
  557. package/tools/track/py/test_joint_indices.py +0 -123
  558. package/tools/track/py/test_lr_viterbi.py +0 -195
  559. package/tools/track/py/test_masks.py +0 -229
  560. package/tools/track/py/test_rig.py +0 -288
  561. package/tools/track/py/test_scene.py +0 -354
  562. package/tools/track/py/test_track.py +0 -328
  563. package/tools/track/py/track.py +0 -411
  564. package/tools/track/remote.mjs +0 -140
  565. package/tools/track/rig-dump.mjs +0 -227
  566. package/tools/track/run-box-tests.mjs +0 -14
  567. package/tools/track/run-box.mjs +0 -168
  568. package/tools/track/setup-box.sh +0 -59
  569. package/tools/track/study-2d.mjs +0 -728
  570. /package/src/{ardy → motion}/collision-blockers.js +0 -0
  571. /package/src/{ardy → motion}/ground.js +0 -0
  572. /package/src/{ardy → motion}/ik-drag.js +0 -0
  573. /package/src/{ardy → motion}/ik-key-json.js +0 -0
  574. /package/src/{ardy → motion}/ik.js +0 -0
  575. /package/src/{ardy → motion}/physics-surface.js +0 -0
  576. /package/src/{ardy → motion}/playback-clock.js +0 -0
  577. /package/src/{ardy → motion}/range-pin.js +0 -0
@@ -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
+ }