cozyclay 2.1.1 → 3.0.1

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