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,127 @@
1
+ // C4. The compiled motion as beats, for the agent (output.timeline) and the beats contact sheet.
2
+ // At most 12 entries, ordered by f[0]:
3
+ // { beat: skill name | "walk" | "keys", src: "use[i]" | "generators[i]" | "keys",
4
+ // f: [first, last] (inclusive, absolute frames),
5
+ // end: { root: [x,y,z], yaw, hand_l: [x,y,z], hand_r: [x,y,z], on_l: ref | null, on_r: ref | null },
6
+ // planted: { l: [[f0, f1], ...], r: [...] }, // at most 4 per foot
7
+ // events: [{ f, object, event: "grab" | "release" | "show" | "hide" }] }
8
+ // Metres to 3 decimals, degrees to 1. Beats come from the skills expandUse ran (used), the
9
+ // generators, and the runs of keys no skill or generator covers; a retime (out.timeMap) moves
10
+ // them with the keys. end is the pose at f[1]: root = the Hips joint, hand_* = the wrists,
11
+ // on_* = the "object:<id>" that hand holds there (object-pose.js inHandAt), else the
12
+ // "anchor:<a>" whose w_<a>_<k> > 0.5 (the largest). planted = the heel or the ball below
13
+ // QA_LIMITS.plantH over the ground (the floor or an object's top, qa/ground.js) and still (a foot on its ball, heel up, is planted), for 3 frames or more (the first, the last and the longest others
14
+ // when there are more than 4). Events: grab/release when an object enters or leaves a hand,
15
+ // show/hide when a visibility channel (a carry's or a switch's `visible`, or any *_vis channel)
16
+ // crosses 0.5; `object` is the object id (one event per object the channel shows), or the
17
+ // channel's name when no object owns it.
18
+ import { QA_LIMITS } from "./qa/checks.js";
19
+ import { groundSole } from "./qa/ground.js";
20
+ import { inHandAt } from "./object-pose.js";
21
+
22
+ const MAX_BEATS = 12, PER_FOOT = 4, MIN_PLANT = 3, STILL_Y = 0.0005;
23
+ const r3 = v => Math.round(v * 1000) / 1000, r1 = v => Math.round(v * 10) / 10;
24
+ const vec = p => p.map(r3);
25
+
26
+ /** The beats' frame ranges before their poses are read: [{ beat, src, f }]. */
27
+ function beatsOf(out, used, spec) {
28
+ const last = out.frameCount - 1, map = out.timeMap ?? (f => f);
29
+ const beats = used.map((u, i) => ({ beat: u.skill, src: `use[${i}]`, f: [u.f0, u.end ?? u.f0] }));
30
+ (spec.generators ?? []).forEach((g, i) => beats.push({ beat: g.type, src: `generators[${i}]`, f: [g.f0, g.f0 + 16 + (g.steps ?? 8) * (g.period ?? 20) + 18] }));
31
+ const covered = f => beats.some(b => f >= b.f[0] && f <= b.f[1]);
32
+ const loose = [...new Set((spec.keys ?? []).map(k => k.f))].filter(f => Number.isFinite(f) && !covered(f)).sort((a, b) => a - b);
33
+ let run = null;
34
+ for (const f of loose) {
35
+ if (run && !beats.some(b => b.src !== "keys" && b.f[1] > run.f[1] && b.f[0] < f)) run.f[1] = f;
36
+ else beats.push(run = { beat: "keys", src: "keys", f: [f, f] });
37
+ }
38
+ const clamp = f => Math.max(0, Math.min(last, Math.round(map(f))));
39
+ for (const b of beats) b.f = [clamp(b.f[0]), clamp(b.f[1])];
40
+ return beats.sort((a, b) => a.f[0] - b.f[0]).slice(0, MAX_BEATS);
41
+ }
42
+
43
+ /** planted[side][f]: the heel or the ball low and still at absolute frame f (false outside the window). */
44
+ function plantedFlags(out) {
45
+ const n = out.frameCount, a = out.f0 ?? 0, flags = { l: new Uint8Array(n), r: new Uint8Array(n) };
46
+ for (const [k, S] of [["l", "Left"], ["r", "Right"]]) {
47
+ const sole = out.frames.map((fr, i) => (fr ? groundSole(out, i, S) : null));
48
+ sole.forEach((p, i) => {
49
+ const q = sole[i > 0 ? i - 1 : i + 1];
50
+ if (!p || !q) return;
51
+ const ok = ["heel", "ball"].some(key => p[key][1] < QA_LIMITS.plantH && Math.abs(p[key][1] - q[key][1]) < STILL_Y
52
+ && Math.hypot(p[key][0] - q[key][0], p[key][2] - q[key][2]) <= QA_LIMITS.slide);
53
+ flags[k][a + i] = ok ? 1 : 0;
54
+ });
55
+ }
56
+ return flags;
57
+ }
58
+
59
+ function plantedIn(flags, [f0, f1]) {
60
+ const runs = [];
61
+ let start = null;
62
+ for (let f = f0; f <= f1 + 1; f++) {
63
+ const on = f <= f1 && flags[f] === 1;
64
+ if (on && start === null) start = f;
65
+ if (!on && start !== null) { if (f - start >= MIN_PLANT) runs.push([start, f - 1]); start = null; }
66
+ }
67
+ if (runs.length <= PER_FOOT) return runs;
68
+ const inner = runs.slice(1, -1).sort((x, y) => (y[1] - y[0]) - (x[1] - x[0]) || x[0] - y[0]).slice(0, PER_FOOT - 2);
69
+ return [runs[0], ...inner, runs[runs.length - 1]].sort((x, y) => x[0] - y[0]);
70
+ }
71
+
72
+ /** Every grab/release/show/hide of the take, by absolute frame. */
73
+ function eventsOf(out, spec) {
74
+ const n = out.frameCount, events = [], objects = Object.entries(spec.objects ?? {});
75
+ for (const [id, o] of objects) for (let f = 1; f < n; f++) {
76
+ const was = inHandAt(o, f - 1), is = inHandAt(o, f);
77
+ if (is && !was) events.push({ f, object: id, event: "grab" });
78
+ if (was && !is) events.push({ f: f - 1, object: id, event: "release" });
79
+ }
80
+ // every object a channel shows (a carry's or a switch's visible) gets its own event
81
+ const owners = {};
82
+ for (const [id, o] of objects) if (typeof o?.visible === "string") (owners[o.visible] ??= []).push(id);
83
+ for (const ch of new Set([...Object.keys(owners), ...Object.keys(out.baked ?? {}).filter(c => c.endsWith("_vis"))])) {
84
+ const v = out.baked?.[ch];
85
+ if (!v) continue;
86
+ for (let f = 1; f < n; f++) {
87
+ const was = v[f - 1] > 0.5, is = v[f] > 0.5;
88
+ if (was !== is) for (const object of owners[ch] ?? [ch]) events.push({ f, object, event: is ? "show" : "hide" });
89
+ }
90
+ }
91
+ return events.sort((x, y) => x.f - y.f);
92
+ }
93
+
94
+ /** "object:<id>" held by hand k at f, else the "anchor:<a>" with the largest weight > 0.5, else null. */
95
+ function onOf(spec, c, k, f) {
96
+ for (const [id, o] of Object.entries(spec.objects ?? {})) if (spec.props?.[o?.prop]?.hand === k && inHandAt(o, f)) return `object:${id}`;
97
+ let best = null, bw = 0.5;
98
+ for (const [ch, w] of Object.entries(c)) {
99
+ const m = /^w_(.+)_([lr])$/.exec(ch);
100
+ if (m && m[2] === k && w > bw) { best = m[1]; bw = w; }
101
+ }
102
+ return best && `anchor:${best}`;
103
+ }
104
+
105
+ function endOf(out, spec, f) {
106
+ const fr = out.frames[f - (out.f0 ?? 0)];
107
+ if (!fr) return null;
108
+ const { s, c } = fr, I = out.ctx.sk.index;
109
+ return { root: vec(s.P[I.Hips]), yaw: r1(c.yaw ?? 0), hand_l: vec(s.P[I.LeftHand]), hand_r: vec(s.P[I.RightHand]),
110
+ on_l: onOf(spec, c, "l", f), on_r: onOf(spec, c, "r", f) };
111
+ }
112
+
113
+ /**
114
+ * timelineOf(out, { used, spec }) -> entries
115
+ * @param {object} out compileMotion result
116
+ * @param {{ used: { skill: string, f0: number, end: number }[], spec: object }} ctx used = the
117
+ * skills expandUse ran, spec = the expanded spec
118
+ * @returns {object[]}
119
+ */
120
+ export function timelineOf(out, { used, spec }) {
121
+ const beats = beatsOf(out, used ?? [], spec);
122
+ if (!beats.length) return [];
123
+ const flags = plantedFlags(out), events = eventsOf(out, spec);
124
+ return beats.map(b => ({ ...b, end: endOf(out, spec, b.f[1]),
125
+ planted: { l: plantedIn(flags.l, b.f), r: plantedIn(flags.r, b.f) },
126
+ events: events.filter(e => e.f >= b.f[0] && e.f <= b.f[1]) }));
127
+ }
@@ -0,0 +1,121 @@
1
+ // C11 own views: the cameras of a verify_result contact_sheet with `views`. They are the
2
+ // agent's own cameras around the subject, never the user's shot camera. A view stays at one
3
+ // place for every frame of the sheet and frames what matters over those frames (the focus
4
+ // refs, else the whole character, plus any trail), so a small action fills the tile and
5
+ // motion reads as change inside one still frame. Pure over a compile result (compileSpec .out).
6
+ // front in front of the character, 10 degrees up
7
+ // side from the side the focus is on (the right when centred), 10 degrees up
8
+ // three-quarter between front and that side, 25 degrees up
9
+ // top straight down, the character's forward up the image
10
+ // depthViews adds a view when the asked ones would hide depth (see APART below).
11
+ import { refPoint } from "./refs.js";
12
+ import { objectBoxesAt } from "./object-pose.js";
13
+ import { resolveBone } from "./bones.js";
14
+ import { add, sub, scale, dot, cross, norm, apply, quatFromMat } from "./compiler/math.js";
15
+
16
+ export const VIEW_NAMES = ["front", "side", "three-quarter", "top"];
17
+ // FOV is the widest lens; a deep subject (a body seen from above) moves the camera back until
18
+ // its far side is at most DEPTH_RATIO times as far as its near side, and the lens narrows (down
19
+ // to MIN_FOV) to fill the tile again, so perspective stays mild.
20
+ const FOV = 35, MIN_FOV = 12, DEPTH_RATIO = 1.8, MARGIN = 1.12, PAD = 0.06, MIN_HALF = 0.15, MIN_DEPTH = 0.35, UP = [0, 1, 0];
21
+ const ELEVATION = { front: 10, side: 10, "three-quarter": 25 };
22
+ const BARE_OBJECT = /^object:(.+?)(\.(?:center|top|base))?$/s;
23
+ const rad = d => (d * Math.PI) / 180;
24
+ const mean = points => scale(points.reduce(add, [0, 0, 0]), 1 / Math.max(1, points.length));
25
+
26
+ const jointsAt = (out, f) => out.frames?.[f - (out.f0 ?? 0)]?.s.P ?? [];
27
+ const corners = b => [-1, 1].flatMap(x => [0, 2].flatMap(y => [-1, 1].map(z => add(b.pos, apply(b.R, [x * b.half[0], y * b.half[1], z * b.half[2]])))));
28
+
29
+ /** World points of a ref at frame f: a bare object:<id> is its part boxes' corners, any other ref its point. */
30
+ function refPoints(out, f, ref) {
31
+ const bare = BARE_OBJECT.exec(ref);
32
+ if (bare && !bare[2]) return (objectBoxesAt(out, bare[1], f) ?? []).flatMap(corners);
33
+ const p = refPoint(out, f, ref);
34
+ return p ? [p] : [];
35
+ }
36
+
37
+ /** The character's horizontal left and forward over the frames (the hip line, summed). */
38
+ function facingOf(out, frames) {
39
+ const sk = out.ctx.sk, l = sk.index[resolveBone(sk, "LeftUpLeg").bone], r = sk.index[resolveBone(sk, "RightUpLeg").bone];
40
+ let v = [0, 0, 0];
41
+ for (const f of frames) { const P = jointsAt(out, f); if (P.length) { const d = sub(P[l], P[r]); v = add(v, [d[0], 0, d[2]]); } }
42
+ const left = Math.hypot(v[0], v[2]) > 1e-6 ? norm(v) : [1, 0, 0];
43
+ return { left, forward: cross(left, UP) };
44
+ }
45
+
46
+ /** Path of a ref over absolute frames lo..hi: [{ f, p }] where it is known. */
47
+ export function trailPoints(out, ref, [lo, hi]) {
48
+ const points = [];
49
+ for (let f = lo; f <= hi; f++) { const p = refPoint(out, f, ref); if (p) points.push({ f, p }); }
50
+ return points;
51
+ }
52
+
53
+ /** A camera at `back` (unit, target to eye) whose frustum holds every point (with PAD) at MARGIN. */
54
+ function fit(name, points, back, upHint, aspect) {
55
+ const right = norm(cross(upHint, back)), up = cross(back, right);
56
+ const c0 = mean(points), rel = points.map(p => sub(p, c0)), xs = rel.map(q => dot(q, right)), ys = rel.map(q => dot(q, up)), zs = rel.map(q => dot(q, back));
57
+ const cx = (Math.min(...xs) + Math.max(...xs)) / 2, cy = (Math.min(...ys) + Math.max(...ys)) / 2;
58
+ const target = add(c0, add(scale(right, cx), scale(up, cy)));
59
+ // The vertical half-angle tangent each point needs from distance d (x through the aspect).
60
+ const need = d => Math.max(...rel.map((q, i) => MARGIN * Math.max(Math.max(Math.abs(xs[i] - cx) + PAD, MIN_HALF) / aspect, Math.max(Math.abs(ys[i] - cy) + PAD, MIN_HALF)) / (d - zs[i])));
61
+ const near = Math.max(...zs), far = Math.min(...zs);
62
+ let d = Math.max(near + MIN_DEPTH, near + (near - far) / (DEPTH_RATIO - 1)), tanV = need(d);
63
+ if (tanV > Math.tan(rad(FOV / 2))) {
64
+ // Too close for the widest lens: back off at FOV until every point fits.
65
+ tanV = Math.tan(rad(FOV / 2));
66
+ d = Math.max(d, ...rel.map((q, i) => zs[i] + MARGIN * Math.max(Math.max(Math.abs(xs[i] - cx) + PAD, MIN_HALF) / aspect, Math.max(Math.abs(ys[i] - cy) + PAD, MIN_HALF)) / tanV));
67
+ } else tanV = Math.max(tanV, Math.tan(rad(MIN_FOV / 2)));
68
+ const eye = add(target, scale(back, d)), fovDeg = Math.round((2 * Math.atan(tanV) * 180 / Math.PI) * 100) / 100;
69
+ tanV = Math.tan(rad(fovDeg / 2));
70
+ const quaternion = quatFromMat([0, 1, 2].map(i => [right[i], up[i], back[i]]));
71
+ return { name, eye, target, quaternion, fovDeg, aspect, right, up, back, tanV, tanH: tanV * aspect };
72
+ }
73
+
74
+ /**
75
+ * One camera per view name over `frames`: framed on the focus refs' points at those frames
76
+ * (none, or none known there: every joint of the character) and every trail point. Answers
77
+ * [{ name, eye, target, quaternion [x,y,z,w], fovDeg, aspect, right, up, back, tanV, tanH }].
78
+ * Deterministic: the same compile, frames and arguments give the same cameras.
79
+ */
80
+ export function viewCameras(out, { views, frames, focus = [], trails = [], aspect = 1 }) {
81
+ let points = frames.flatMap(f => focus.flatMap(ref => refPoints(out, f, ref)));
82
+ const body = frames.flatMap(f => jointsAt(out, f));
83
+ if (!points.length) points = body;
84
+ points = [...points, ...trails.flatMap(t => t.points.map(q => q.p))];
85
+ if (!points.length) return [];
86
+ const { left, forward } = facingOf(out, frames), hips = mean(frames.map(f => jointsAt(out, f)[out.ctx.sk.index.Hips]).filter(Boolean));
87
+ const side = focus.length && dot(sub(mean(points), hips), left) > 0.02 ? left : scale(left, -1);
88
+ return views.map(name => {
89
+ if (name === "top") return fit(name, points, UP, forward, aspect);
90
+ const e = rad(ELEVATION[name]), level = name === "front" ? forward : name === "side" ? side : norm(add(forward, side));
91
+ return fit(name, points, add(scale(level, Math.cos(e)), scale(UP, Math.sin(e))), UP, aspect);
92
+ });
93
+ }
94
+
95
+ // A picture hides distance along its own line of sight, so a sheet holds two views at least APART
96
+ // degrees apart. Front and three-quarter (45 degrees apart) look along nearly one line: the
97
+ // guitarist's right hand 20 cm in front of the guitar (session df388a9b) showed in neither.
98
+ // LOOK is each view's nominal direction: azimuth from the character's forward, elevation.
99
+ const LOOK = { front: [0, 10], side: [90, 10], "three-quarter": [45, 25], top: [0, 90] }, APART = 60;
100
+ const lookDir = name => { const [a, e] = LOOK[name].map(rad); return [Math.cos(e) * Math.sin(a), Math.sin(e), Math.cos(e) * Math.cos(a)]; };
101
+ const apartDeg = (a, b) => Math.round(Math.acos(Math.min(1, dot(lookDir(a), lookDir(b)))) * 180 / Math.PI);
102
+ const spread = views => views.some((a, i) => views.slice(i + 1).some(b => apartDeg(a, b) >= APART));
103
+
104
+ /**
105
+ * The views a sheet renders: the asked ones when two of them are APART degrees apart, else the
106
+ * asked ones plus the first of side, front and top that makes it so. Answers { views, added }:
107
+ * added is null, or { view, reason } for visualRefs.
108
+ */
109
+ export function depthViews(views) {
110
+ if (spread(views)) return { views, added: null };
111
+ const view = ["side", "front", "top"].find(name => !views.includes(name) && spread([...views, name]));
112
+ const line = views.length > 1 ? `${views.join(" and ")} look along nearly one line (${apartDeg(views[0], views[1])} degrees apart)` : `${views[0]} alone looks along one line`;
113
+ return { views: [...views, view], added: { view, reason: `${line}, so a gap along it (toward or away from the camera) does not show; ${view} looks across it` } };
114
+ }
115
+
116
+ /** Pixel [x, y] of world point p in a width x height image of cam, y counted from the bottom row; null behind it. */
117
+ export function projectPoint(cam, p, width, height) {
118
+ const rel = sub(p, cam.eye), depth = -dot(rel, cam.back);
119
+ if (depth < 0.05) return null;
120
+ return [((dot(rel, cam.right) / (depth * cam.tanH) + 1) / 2) * width, ((dot(rel, cam.up) / (depth * cam.tanV) + 1) / 2) * height];
121
+ }
@@ -0,0 +1,119 @@
1
+ // walkTo { to, arrive }: the engine picks the cadence instead of the agent sweeping period and step by
2
+ // hand. A use[i] walkTo with arrive (the frame the walk should end by, feet together: f0 + 16 + steps *
3
+ // period + 18) and none of steps, period, step, swing gets them planned here, before the skills expand:
4
+ // candidates every steps n and period p (frames per step) whose walk ends by arrive, the strides
5
+ // covering the distance from where the feet are at f0 to `to` (the first stride ~0.8 of
6
+ // the others, as walkTo's defaults), a stride at most STRIDE_MAX, the foot in the air
7
+ // SWING of the period;
8
+ // order a person's walk ratio first (stride x cadence: stride m * period frames ~ WALK_RATIO, the
9
+ // default 0.5 m every 20 frames), then using the time given (a walk that ends long before
10
+ // arrive stands still the rest), and no stroll slower than SLOW m/s;
11
+ // checked each candidate is compiled on a probe spec (the keys and skills up to the walk, the walk,
12
+ // nothing after it; no goals, checks or moving objects) and the first with no POP, REACH,
13
+ // SLIDE, FLOAT, FLIP or KNEE issue between f0 and its end wins (at most TRIALS compiles): the
14
+ // best on-time ones, then the top few again with a longer swing, a longer first stride or a
15
+ // lower lift (a quick walk often needs one of them), then the late ones, earliest first.
16
+ // When no walk ends by arrive (the distance is too far for the frames), the earliest clean one is
17
+ // taken and the note says the speed the request needs and the frame it ends. The spec keeps arrive (a
18
+ // recompile plans the same walk again, cached); observations.planned reads what was chosen.
19
+ import { expandUse } from "./skills/index.js";
20
+ import { stateAt } from "./compiler/walk.js";
21
+
22
+ const WALK_RATIO = 9.7, STRIDE_MIN = 0.25, STRIDE_MAX = 0.75, SWING = 0.88, SLOW = 0.5, TRIALS = 22, LATE = 4, FPS = 24;
23
+ const PERIODS = Array.from({ length: 16 }, (_, i) => 9 + i);
24
+ const PLANNED = ["steps", "period", "step", "swing"];
25
+ const WALK_KINDS = new Set(["POP", "REACH", "SLIDE", "FLOAT", "FLIP", "KNEE"]);
26
+ const cache = new Map();
27
+ const r2 = x => Math.round(x * 100) / 100;
28
+
29
+ /** The walkTo uses that plan their cadence: [{ i, u }]. */
30
+ const toPlan = spec => (Array.isArray(spec?.use) ? spec.use : []).map((u, i) => ({ i, u }))
31
+ .filter(({ u }) => u?.skill === "walkTo" && Number.isInteger(u.args?.arrive) && Array.isArray(u.args?.to) && !PLANNED.some(k => u.args[k] !== undefined)
32
+ && Object.values(u.args).every(v => !(v && typeof v === "object" && !Array.isArray(v))));
33
+
34
+ /** The strides of an n-step walk over d m: { first, step } or null when no stride fits. */
35
+ function strides(d, n) {
36
+ if (n === 2) return d >= STRIDE_MIN && d <= STRIDE_MAX ? { first: d, step: d } : null;
37
+ const first = Math.min(0.62, Math.max(0.3, (0.8 * d) / (n - 2 + 0.8))), step = (d - first) / (n - 2);
38
+ return step >= STRIDE_MIN && step <= STRIDE_MAX ? { first, step } : null;
39
+ }
40
+
41
+ /** Candidates for a walk of d m from f0, best first: on time (ending by arrive) by their score, then late ones by their end. */
42
+ function candidates(d, f0, arrive) {
43
+ const all = [];
44
+ for (const period of PERIODS) for (let n = 2; n <= 40; n++) {
45
+ const s = strides(d, n);
46
+ if (!s) continue;
47
+ const end = f0 + 16 + n * period + 18, v = (s.step * FPS) / period;
48
+ const ratio = Math.abs(Math.log((s.step * period) / WALK_RATIO)), idle = Math.max(0, arrive - end) / Math.max(1, arrive - f0);
49
+ all.push({ args: { steps: n, period, swing: Math.round(SWING * period), step: r2(s.step), firstStep: r2(s.first) }, end, v, late: end > arrive,
50
+ score: ratio + 1.5 * idle + Math.max(0, SLOW - v) * 4 });
51
+ }
52
+ const onTime = all.filter(c => !c.late).sort((a, b) => a.score - b.score), late = all.filter(c => c.late).sort((a, b) => a.end - b.end || a.score - b.score);
53
+ return { onTime, late };
54
+ }
55
+
56
+ /** A quick walk's other shapes: a longer swing, with a longer first stride, a lower lift, or both. */
57
+ function variants(c, d) {
58
+ const { steps: n, period } = c.args, swing = Math.round(0.8 * period), long = n > 2 && c.args.firstStep < 0.62 ? { firstStep: 0.62, step: r2((d - 0.62) / (n - 2)) } : null;
59
+ return [{}, long, { lift: 0.06 }, long && { ...long, lift: 0.06 }].filter(Boolean).map(x => ({ ...c, args: { ...c.args, swing, ...x } }))
60
+ .filter(v => v.args.step >= STRIDE_MIN && v.args.step <= STRIDE_MAX);
61
+ }
62
+
63
+ /** Issues in the walk's own frames (the kinds a cadence causes) of a compiled probe: a count. */
64
+ const walkIssues = (qa, f0, end) => qa.groups.filter(g => WALK_KINDS.has(g.kind) && g.last >= f0 && g.first <= end).reduce((n, g) => n + g.count, 0);
65
+
66
+ /**
67
+ * planWalks(spec, { sk, scene, trial }) -> [{ i, args, note }] for every walkTo with arrive (applyWalkPlans puts
68
+ * the args in). trial(spec) -> QA of a compile of that spec (pipeline.js), or throws.
69
+ */
70
+ export function planWalks(spec, { sk, scene, trial }) {
71
+ const plans = [];
72
+ for (const { i, u } of toPlan(spec)) {
73
+ const key = JSON.stringify([spec.keys, spec.use.slice(0, i + 1), spec.anchors, spec.props, spec.fits, spec.fix, scene?.objects ?? null]);
74
+ if (cache.has(key)) { plans.push(cache.get(key)); continue; }
75
+ const before = spec.use.slice(0, i).filter(x => x.f0 < u.f0);
76
+ let state, ends = [];
77
+ try {
78
+ const { spec: exp, used } = expandUse({ ...spec, frames: 20000, use: before }, { sk, scene, lenient: true });
79
+ state = stateAt(exp.keys ?? [], u.f0); ends = used.map(x => x.end ?? 0);
80
+ } catch { state = null; }
81
+ if (!state?.foot_l || !state?.foot_r) continue;
82
+ const c0 = [(state.foot_l[0] + state.foot_r[0]) / 2, (state.foot_l[2] + state.foot_r[2]) / 2], to = u.args.to, d = Math.hypot(to[0] - c0[0], to[to.length - 1] - c0[1]);
83
+ const { onTime, late } = candidates(d, u.f0, u.args.arrive);
84
+ const probe = c => {
85
+ const frames = Math.max(c.end, ...ends) + 1;
86
+ return { schema: spec.schema, fps: spec.fps ?? FPS, frames, keys: (spec.keys ?? []).filter(k => k.f < frames), use: [...before, { ...u, args: { ...u.args, ...c.args } }],
87
+ ...(spec.anchors ? { anchors: spec.anchors } : {}), ...(spec.props ? { props: spec.props } : {}), ...(spec.fits ? { fits: spec.fits } : {}), ...(spec.fix !== undefined ? { fix: spec.fix } : {}) };
88
+ };
89
+ let chosen = null, tried = 0, best = null, onTried = 0;
90
+ const order = [...onTime.slice(0, 6), ...onTime.slice(0, 3).flatMap(c => variants(c, d))].slice(0, TRIALS - LATE);
91
+ for (const c of [...order, ...late.slice(0, TRIALS - order.length)]) {
92
+ let issues;
93
+ try { issues = walkIssues(trial(probe(c)), u.f0, c.end); } catch { issues = Infinity; }
94
+ tried++; onTried += c.late ? 0 : 1;
95
+ if (!best || issues < best.issues) best = { c, issues };
96
+ if (issues === 0) { chosen = c; break; }
97
+ }
98
+ chosen ??= best?.issues < Infinity ? best.c : onTime[0] ?? late[0];
99
+ if (!chosen) continue;
100
+ const a = chosen.args, A = u.args.arrive;
101
+ const what = `use[${i}] walkTo arrive f${A}: steps ${a.steps}, period ${a.period}, swing ${a.swing}, step ${a.step} m, firstStep ${a.firstStep} m${a.lift ? `, lift ${a.lift} m` : ""} (${r2(chosen.v)} m/s), ends f${chosen.end}`;
102
+ const why = onTime.length ? `the ${onTried} walks tried that end by f${A} pop or miss a foot`
103
+ : `even the quickest walk over ${r2(d)} m (a step every ${PERIODS[0]} frames, strides up to ${STRIDE_MAX} m, 34 frames to start and stop) ends f${late[0]?.end}`;
104
+ const late1 = chosen.late ? `; f${A} is too soon: ${why}; give arrive >= f${chosen.end} or a shorter distance` : "";
105
+ const dirty = best && best.c === chosen && best.issues > 0 ? `; no candidate walked clean (${best.issues} issues in its frames)` : "";
106
+ const plan = { i, args: a, end: chosen.end, note: `${what}${late1}${dirty} (${tried} checked)` };
107
+ if (cache.size >= 64) cache.delete(cache.keys().next().value);
108
+ cache.set(key, plan);
109
+ plans.push(plan);
110
+ }
111
+ return plans;
112
+ }
113
+
114
+ /** The spec with every planned walk's args filled in (arrive leaves them: walkTo ignores it); the spec itself when there is none. */
115
+ export function applyWalkPlans(spec, plans) {
116
+ if (!plans.length) return spec;
117
+ const use = spec.use.map((u, i) => { const p = plans.find(x => x.i === i); return p && u?.skill === "walkTo" ? { ...u, args: { ...u.args, ...p.args } } : u; });
118
+ return { ...spec, use };
119
+ }
@@ -5,6 +5,8 @@ import { EMPTY_KIND, OBJECT_MENU_ENTRIES } from "./scene-objects.js";
5
5
  const GROUP_LABELS_KO = {
6
6
  Primitives: "기본 도형",
7
7
  "Set pieces": "세트 소품",
8
+ Lights: "조명",
9
+ Effects: "효과",
8
10
  };
9
11
 
10
12
  const OBJECT_LABELS_KO = {
@@ -18,6 +20,10 @@ const OBJECT_LABELS_KO = {
18
20
  Chair: "의자",
19
21
  Car: "자동차",
20
22
  "Plane (aircraft)": "비행기",
23
+ "Point light": "점 조명",
24
+ "Spot light": "스포트 조명",
25
+ Light: "조명",
26
+ Effect: "효과",
21
27
  };
22
28
 
23
29
  function displayObjectGroupName(name) {
@@ -67,7 +73,7 @@ export function CatalogueEntries({ onPick }) {
67
73
  <span className={`add-object-swatch ${entry.kind}`} style={{ background: entry.color }} aria-hidden="true" />
68
74
  <span>{displayObjectLabel(entry.label)}</span>
69
75
  {/* An empty has no size to quote: say what it is instead of "0 × 0 m". */}
70
- <small>{entry.kind === EMPTY_KIND ? ko("node only", "노드 전용") : `${entry.footprint.width} × ${entry.footprint.depth} m`}</small>
76
+ <small>{entry.light ? ko("practical light", "실제 조명") : entry.kind === EMPTY_KIND ? ko("node only", "노드 전용") : entry.kind === "effect" ? ko("smoke, fire, rain…", "연기, 불, 비…") : `${entry.footprint.width} × ${entry.footprint.depth} m`}</small>
71
77
  </button>
72
78
  ))}
73
79
  </div>
@@ -0,0 +1,98 @@
1
+ /**
2
+ * The inspector's object colours: the blockout palette, what a typed colour
3
+ * means, and the per-browser memory of hand-mixed tints. Pure, so the record
4
+ * normalizers and the light/glow fields read the same hex rules.
5
+ */
6
+
7
+ /** Blockout grey: what a primitive is until someone says otherwise. */
8
+ export const GREY_BOX = "#c2c6c8";
9
+
10
+ /** Blockout greys first, then the clay accents, then the blocking chromatics,
11
+ * for re-tinting from the inspector. The greys lead because a blockout starts
12
+ * grey; the saturated four exist so a shot can say "the red ball, the blue
13
+ * box" at a glance — the inspector had no way to say that at all (#88). They
14
+ * are muted off pure RGB so they still read as clay under the set's lighting.
15
+ * Any other colour is authored through the inspector's free colour input. */
16
+ export const OBJECT_COLORS = [
17
+ "#e2e5e6",
18
+ GREY_BOX,
19
+ "#9aa1a5",
20
+ "#767d81",
21
+ "#d9b18c",
22
+ "#8fae9b",
23
+ "#d94a4a",
24
+ "#4a7bd9",
25
+ "#e2c04a",
26
+ "#4fa86a",
27
+ ];
28
+
29
+ /** How many hand-mixed colours the inspector remembers. Six is one palette
30
+ * row: enough to keep a scene's custom tints reachable, short enough that the
31
+ * popover never turns into a second, unruly palette. */
32
+ const RECENT_OBJECT_COLORS_MAX = 6;
33
+ /** Session memory of hand-mixed tints; versioned like every other key here. */
34
+ const RECENT_OBJECT_COLORS_KEY = "cozyclay.objectColors.recent";
35
+
36
+ const HEX_SHORT = /^#?([0-9a-f]{3})$/i;
37
+ const HEX_LONG = /^#?([0-9a-f]{6})$/i;
38
+
39
+ /**
40
+ * What a colour input typed by a human means, or null if it means nothing.
41
+ *
42
+ * The hex field accepts what people actually type — `#f36`, `ff3366`, upper
43
+ * case, stray spaces — and folds it to the one form the record stores, so a
44
+ * colour typed by hand and the same colour picked from the native swatch are
45
+ * byte-identical (and therefore compare equal against the palette). Anything
46
+ * else returns null rather than throwing or guessing: a half-typed `#12` is a
47
+ * keystroke on the way somewhere, not an instruction to repaint the prop.
48
+ */
49
+ export function normalizeObjectColor(value) {
50
+ if (typeof value !== "string") return null;
51
+ const text = value.trim();
52
+ const short = HEX_SHORT.exec(text);
53
+ if (short) return `#${short[1].toLowerCase().replace(/./g, (digit) => digit + digit)}`;
54
+ const long = HEX_LONG.exec(text);
55
+ return long ? `#${long[1].toLowerCase()}` : null;
56
+ }
57
+
58
+ /**
59
+ * The recent-colours list after mixing `hex`: newest first, no duplicates,
60
+ * capped, presets excluded.
61
+ *
62
+ * Pure so the list can be proved without a browser, and so a corrupt stored
63
+ * list is repaired on the way through instead of painting a swatch with junk.
64
+ * Presets are skipped because they are already one row up — remembering them
65
+ * would push the author's own colours off the end with colours they never
66
+ * mixed. Returns the SAME array when nothing changes, so a re-pick of an
67
+ * unchanged colour cannot trigger a storage write or a re-render.
68
+ */
69
+ export function rememberObjectColor(list, hex) {
70
+ const clean = Array.isArray(list)
71
+ ? list.map(normalizeObjectColor).filter((color, index, all) => color && all.indexOf(color) === index && !OBJECT_COLORS.includes(color))
72
+ : [];
73
+ const color = normalizeObjectColor(hex);
74
+ const next = color && !OBJECT_COLORS.includes(color) ? [color, ...clean.filter((entry) => entry !== color)] : clean;
75
+ const capped = next.slice(0, RECENT_OBJECT_COLORS_MAX);
76
+ const unchanged = Array.isArray(list) && capped.length === list.length && capped.every((entry, index) => entry === list[index]);
77
+ return unchanged ? list : capped;
78
+ }
79
+
80
+ /** Recent colours as last left by this browser; storage can be unavailable
81
+ * (private mode), and a corrupt body must not take the inspector down with
82
+ * it — either way the palette simply starts without a memory. */
83
+ export function readStoredObjectColors(storage) {
84
+ try {
85
+ const parsed = JSON.parse(storage?.getItem(RECENT_OBJECT_COLORS_KEY) ?? "[]");
86
+ return rememberObjectColor(Array.isArray(parsed) ? parsed : [], null);
87
+ } catch {
88
+ return [];
89
+ }
90
+ }
91
+
92
+ export function writeStoredObjectColors(storage, list) {
93
+ try {
94
+ storage?.setItem(RECENT_OBJECT_COLORS_KEY, JSON.stringify(list));
95
+ } catch {
96
+ // Storage can be unavailable (private mode); the colours simply stop persisting.
97
+ }
98
+ }
@@ -0,0 +1,115 @@
1
+ import * as THREE from "three";
2
+ import { objectSize } from "./scene-objects.js";
3
+
4
+ // The ObjectGizmo's fixed parts: handle layout, sizes, colours and the two
5
+ // placement rules (where the gizmo stands, and which way its rings face).
6
+ export const AXES = [
7
+ { axis: "x", dir: new THREE.Vector3(1, 0, 0), color: "#ff5340" },
8
+ { axis: "y", dir: new THREE.Vector3(0, 1, 0), color: "#54e05c" },
9
+ { axis: "z", dir: new THREE.Vector3(0, 0, 1), color: "#3d8bff" },
10
+ ];
11
+ /** the same three directions, addressable by the record's axis key */
12
+ export const AXIS_VECTORS = { x: AXES[0].dir, y: AXES[1].dir, z: AXES[2].dir };
13
+ /** Move-tool plane handles. Unity colours each square after the axis it LOCKS,
14
+ * so the blue square (z locked) slides in xy. `normal` is the pinned axis. */
15
+ export const PLANES = [
16
+ { id: "xz", axes: ["x", "z"], normal: "y", color: "#54e05c" },
17
+ { id: "xy", axes: ["x", "y"], normal: "z", color: "#3d8bff" },
18
+ { id: "yz", axes: ["y", "z"], normal: "x", color: "#ff5340" },
19
+ ];
20
+ export const PLANE_OFFSET = 0.26; // where the square sits along each of its two axes
21
+ export const PLANE_SIZE = 0.2;
22
+ export const ARROW_LEN = 0.62;
23
+ export const SHAFT_R = 0.016;
24
+ export const TIP_R = 0.05;
25
+ export const TIP_LEN = 0.14;
26
+ export const RING_R = 0.52;
27
+ /**
28
+ * The axis rings are drawn as two half-tori instead of one full torus. A full
29
+ * ring is misleading: the half facing away from the camera is pixel-identical
30
+ * to the near half, so a drag started on the far side reads backwards.
31
+ * Depth-sorting the ring against the object would only help when the object
32
+ * is big enough to occlude the far half — the gizmo sits at the object's
33
+ * centre and the ring is only 0.52 m across, so for the small props a
34
+ * blocking tool is full of, the far half projects outside the silhouette and
35
+ * stays ambiguous. Fading it instead (FAR_HALF_ALPHA vs the near half's
36
+ * 0.95) is unambiguous at every object size and camera angle. Only the drawn
37
+ * halves fade — the pick proxy stays one full invisible torus per ring.
38
+ */
39
+ export const FAR_HALF_ALPHA = 0.28;
40
+ /** the outer screen-space ring: larger than the axis rings (RING_R) so it
41
+ * encloses them from every angle, and far enough out that its pick band
42
+ * [0.61, 0.75] clears the axis rings' [0.45, 0.59] — the two can never fight
43
+ * for a grab. (§3.3: "a larger circle enclosing the sphere, drawn flat to
44
+ * the viewer" — rotates about the view axis.) */
45
+ export const SCREEN_RING_R = 0.68;
46
+ /** Unity's yellow for the handle being dragged — and, after release, for the
47
+ * last-dragged one, until the selection or the tool changes (§3.1). */
48
+ export const ACTIVE_COLOR = "#ffd23d";
49
+ export const RING_TUBE = 0.022;
50
+ export const BOX_SIZE = 0.09; // scale-mode axis knobs
51
+ export const CENTRE_BOX = 0.11; // uniform-scale knob at the pivot
52
+ /** Pick proxies. A drawn 1.6 cm shaft is ~3 screen pixels — nothing anyone can
53
+ * reliably grab — so hit testing runs against invisible fat volumes around each
54
+ * arrow, ring and knob, the way every DCC gizmo does it. */
55
+ export const PICK_SHAFT_R = 0.09;
56
+ export const PICK_TUBE_R = 0.07;
57
+ export const PICK_CENTRE_R = 0.16;
58
+ /** metres of gizmo per metre of camera distance: keeps it a constant size on
59
+ * screen whether the object is at the lens or across the room */
60
+ export const SCREEN_SCALE = 0.16;
61
+
62
+ /** the object's own centre height, where the gizmo sits */
63
+ function gizmoHeight(object) {
64
+ return (object.y ?? 0) + Math.max(objectSize(object).height / 2, 0.25);
65
+ }
66
+
67
+ /** the handles-Map key a pick entry was registered under — the same
68
+ * expression __gizmoPick reports, so highlights and QA hooks agree on handle
69
+ * identity */
70
+ export function handleKey(entry) {
71
+ if (entry.corner) return `corner:${entry.corner.id}`;
72
+ return entry.axis ?? (entry.plane ? `plane:${entry.plane.id}` : "centre");
73
+ }
74
+
75
+ /**
76
+ * The four corners of a cutout card, in the card's own local space.
77
+ *
78
+ * A standee is a picture, and a picture is resized by its corners — grabbing
79
+ * one and pulling is the gesture everybody already has for "make this bigger",
80
+ * and unlike an axis knob it says which way is bigger without reading a label.
81
+ * `sx`/`sy` are which way each corner lies from the centre, so the drag can
82
+ * measure travel along the diagonal that corner actually points down.
83
+ */
84
+ export const CARD_CORNERS = [
85
+ { id: "tr", sx: 1, sy: 1 },
86
+ { id: "tl", sx: -1, sy: 1 },
87
+ { id: "br", sx: 1, sy: -1 },
88
+ { id: "bl", sx: -1, sy: -1 },
89
+ ];
90
+ export const CORNER_BOX = 0.085;
91
+ export const PICK_CORNER_R = 0.13;
92
+
93
+ /**
94
+ * Screen-space ring result: the object rotated `deltaDeg` about the world
95
+ * `viewAxis`, written back as all three Euler channels in one patch. The
96
+ * stored channels are Euler degrees per world axis, so the drag-start
97
+ * orientation is composed with the delta in quaternion space and decomposed
98
+ * again in the same order the renderer uses — exact for any start
99
+ * orientation, where a single-channel approximation would drift. Snap
100
+ * quantizes the accumulated delta to the same 5° grid the axis rings snap
101
+ * their start+delta to (start values are always on the grid themselves).
102
+ */
103
+
104
+ // Path authoring intersects presses with the set floor, exactly where a
105
+ // dropped pin will live.
106
+ export const GROUND = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0);
107
+ /** Where the gizmo stands: on the pose a take draws the object at (`held.pose`, held-object-edit.jsx), else at the authored centre. */
108
+ export function gizmoOrigin(object, held, out) {
109
+ return held?.pose ? out.fromArray(held.pose.centre) : out.set(object.x, gizmoHeight(object), object.z);
110
+ }
111
+
112
+ /** The rotation the handles take: the drawn object's own axes while a held-object drag edits it in rotate mode, else none (world axes). */
113
+ export function gizmoTurn(held, mode) {
114
+ return mode === "rotate" && held?.editable ? held.pose.quat : null;
115
+ }