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
@@ -3,15 +3,11 @@ import { useFrame, useThree } from "@react-three/fiber";
3
3
  import * as THREE from "three";
4
4
  import { GIZMO_LAYER, SHOT_ASPECT, fitAspect } from "./dualview.jsx";
5
5
  import { HANDLE_PROXY_FLAG, claimsPress } from "./gizmo-claim.js";
6
- import {
7
- CUTOUT_KIND,
8
- objectSize,
9
- rotatePatch,
10
- scalePatch,
11
- screenRotatePatch,
12
- translatePatch,
13
- wrapAngle,
14
- } from "./scene-objects.js";
6
+ import { CUTOUT_KIND, rotatePatch, scalePatch, screenRotatePatch, translatePatch, wrapAngle } from "./scene-objects.js";
7
+ import { dragTravel, dragTurn } from "./held-object-pose.js";
8
+ import { HeldTrail } from "./held-object-edit.jsx";
9
+ import { ACTIVE_COLOR, ARROW_LEN, AXES, AXIS_VECTORS, BOX_SIZE, CARD_CORNERS, CENTRE_BOX, CORNER_BOX, FAR_HALF_ALPHA, GROUND, PICK_CENTRE_R, PICK_CORNER_R, PICK_SHAFT_R, PICK_TUBE_R,
10
+ PLANES, PLANE_OFFSET, PLANE_SIZE, RING_R, RING_TUBE, SCREEN_RING_R, SCREEN_SCALE, SHAFT_R, TIP_LEN, TIP_R, gizmoOrigin, gizmoTurn, handleKey } from "./object-gizmo-parts.js";
15
11
 
16
12
  /**
17
13
  * The transform gizmo for the selected scene object — the thing every 3D tool
@@ -46,109 +42,8 @@ import {
46
42
  * space and decomposed back into the same channels (screenRotatePatch).
47
43
  */
48
44
 
49
- const AXES = [
50
- { axis: "x", dir: new THREE.Vector3(1, 0, 0), color: "#ff5340" },
51
- { axis: "y", dir: new THREE.Vector3(0, 1, 0), color: "#54e05c" },
52
- { axis: "z", dir: new THREE.Vector3(0, 0, 1), color: "#3d8bff" },
53
- ];
54
- /** the same three directions, addressable by the record's axis key */
55
- const AXIS_VECTORS = { x: AXES[0].dir, y: AXES[1].dir, z: AXES[2].dir };
56
- /** Move-tool plane handles. Unity colours each square after the axis it LOCKS,
57
- * so the blue square (z locked) slides in xy. `normal` is the pinned axis. */
58
- const PLANES = [
59
- { id: "xz", axes: ["x", "z"], normal: "y", color: "#54e05c" },
60
- { id: "xy", axes: ["x", "y"], normal: "z", color: "#3d8bff" },
61
- { id: "yz", axes: ["y", "z"], normal: "x", color: "#ff5340" },
62
- ];
63
- const PLANE_OFFSET = 0.26; // where the square sits along each of its two axes
64
- const PLANE_SIZE = 0.2;
65
- const ARROW_LEN = 0.62;
66
- const SHAFT_R = 0.016;
67
- const TIP_R = 0.05;
68
- const TIP_LEN = 0.14;
69
- const RING_R = 0.52;
70
- /**
71
- * The axis rings are drawn as two half-tori instead of one full torus. A full
72
- * ring is misleading: the half facing away from the camera is pixel-identical
73
- * to the near half, so a drag started on the far side reads backwards.
74
- * Depth-sorting the ring against the object would only help when the object
75
- * is big enough to occlude the far half — the gizmo sits at the object's
76
- * centre and the ring is only 0.52 m across, so for the small props a
77
- * blocking tool is full of, the far half projects outside the silhouette and
78
- * stays ambiguous. Fading it instead (FAR_HALF_ALPHA vs the near half's
79
- * 0.95) is unambiguous at every object size and camera angle. Only the drawn
80
- * halves fade — the pick proxy stays one full invisible torus per ring.
81
- */
82
- const FAR_HALF_ALPHA = 0.28;
83
- /** the outer screen-space ring: larger than the axis rings (RING_R) so it
84
- * encloses them from every angle, and far enough out that its pick band
85
- * [0.61, 0.75] clears the axis rings' [0.45, 0.59] — the two can never fight
86
- * for a grab. (§3.3: "a larger circle enclosing the sphere, drawn flat to
87
- * the viewer" — rotates about the view axis.) */
88
- const SCREEN_RING_R = 0.68;
89
- /** Unity's yellow for the handle being dragged — and, after release, for the
90
- * last-dragged one, until the selection or the tool changes (§3.1). */
91
- const ACTIVE_COLOR = "#ffd23d";
92
- const RING_TUBE = 0.022;
93
- const BOX_SIZE = 0.09; // scale-mode axis knobs
94
- const CENTRE_BOX = 0.11; // uniform-scale knob at the pivot
95
- /** Pick proxies. A drawn 1.6 cm shaft is ~3 screen pixels — nothing anyone can
96
- * reliably grab — so hit testing runs against invisible fat volumes around each
97
- * arrow, ring and knob, the way every DCC gizmo does it. */
98
- const PICK_SHAFT_R = 0.09;
99
- const PICK_TUBE_R = 0.07;
100
- const PICK_CENTRE_R = 0.16;
101
- /** metres of gizmo per metre of camera distance: keeps it a constant size on
102
- * screen whether the object is at the lens or across the room */
103
- const SCREEN_SCALE = 0.16;
104
-
105
- /** the object's own centre height, where the gizmo sits */
106
- function gizmoHeight(object) {
107
- return (object.y ?? 0) + Math.max(objectSize(object).height / 2, 0.25);
108
- }
109
-
110
- /** the handles-Map key a pick entry was registered under — the same
111
- * expression __gizmoPick reports, so highlights and QA hooks agree on handle
112
- * identity */
113
- function handleKey(entry) {
114
- if (entry.corner) return `corner:${entry.corner.id}`;
115
- return entry.axis ?? (entry.plane ? `plane:${entry.plane.id}` : "centre");
116
- }
117
-
118
- /**
119
- * The four corners of a cutout card, in the card's own local space.
120
- *
121
- * A standee is a picture, and a picture is resized by its corners — grabbing
122
- * one and pulling is the gesture everybody already has for "make this bigger",
123
- * and unlike an axis knob it says which way is bigger without reading a label.
124
- * `sx`/`sy` are which way each corner lies from the centre, so the drag can
125
- * measure travel along the diagonal that corner actually points down.
126
- */
127
- const CARD_CORNERS = [
128
- { id: "tr", sx: 1, sy: 1 },
129
- { id: "tl", sx: -1, sy: 1 },
130
- { id: "br", sx: 1, sy: -1 },
131
- { id: "bl", sx: -1, sy: -1 },
132
- ];
133
- const CORNER_BOX = 0.085;
134
- const PICK_CORNER_R = 0.13;
135
-
136
- /**
137
- * Screen-space ring result: the object rotated `deltaDeg` about the world
138
- * `viewAxis`, written back as all three Euler channels in one patch. The
139
- * stored channels are Euler degrees per world axis, so the drag-start
140
- * orientation is composed with the delta in quaternion space and decomposed
141
- * again in the same order the renderer uses — exact for any start
142
- * orientation, where a single-channel approximation would drift. Snap
143
- * quantizes the accumulated delta to the same 5° grid the axis rings snap
144
- * their start+delta to (start values are always on the grid themselves).
145
- */
146
-
147
- // Path authoring intersects presses with the set floor, exactly where a
148
- // dropped pin will live.
149
- const GROUND = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0);
150
45
 
151
- export default function ObjectGizmo({ object, objects = [], mode = "move", snap = true, enabled, pickOnly = false, paneRef, camRef, shotAspect = SHOT_ASPECT, onChange, onSelect, onGroundClick, onDragStart, onDragEnd, claimPointer }) {
46
+ export default function ObjectGizmo({ object, objects = [], mode = "move", snap = true, enabled, pickOnly = false, paneRef, camRef, shotAspect = SHOT_ASPECT, onChange, onSelect, onGroundClick, onDragStart, onDragEnd, claimPointer, held = null }) {
152
47
  const { gl, scene } = useThree();
153
48
  // shotAspect null = the camera fills the pane (the editor working view):
154
49
  // pointer mapping uses the pane rect itself instead of a letterboxed
@@ -174,7 +69,10 @@ export default function ObjectGizmo({ object, objects = [], mode = "move", snap
174
69
  const hoverRef = useRef(null); // last hovered key: pointer moves that keep it skip the re-render
175
70
  const dragRef = useRef(null);
176
71
  const stateRef = useRef(null);
177
- stateRef.current = { object, objects, mode, snap, onChange, onSelect, onGroundClick, onDragStart, onDragEnd, claimPointer };
72
+ // `held` (held-object-edit.jsx): the object is drawn by a compiled take. The
73
+ // gizmo stands on that drawn pose, and while a hand holds the object a move
74
+ // or rotate drag edits its motion through `held` instead of the record.
75
+ stateRef.current = { object, objects, mode, snap, onChange, onSelect, onGroundClick, onDragStart, onDragEnd, claimPointer, held };
178
76
  const tools = useMemo(
179
77
  () => ({
180
78
  raycaster: new THREE.Raycaster(),
@@ -347,6 +245,7 @@ export default function ObjectGizmo({ object, objects = [], mode = "move", snap
347
245
  // the moment the pointer crosses the ring's ±180° seam.
348
246
  drag.turned += wrapAngle(((angle - drag.lastAngle) * 180) / Math.PI);
349
247
  drag.lastAngle = angle;
248
+ if (drag.held) return void stateRef.current.held?.rotate(drag.dir, dragTurn(drag.turned, snapping ? 5 : 0));
350
249
  if (drag.axis === "screen") {
351
250
  // The screen ring spins about the view axis: compose the
352
251
  // delta onto the drag-start orientation in quaternion space
@@ -399,6 +298,7 @@ export default function ObjectGizmo({ object, objects = [], mode = "move", snap
399
298
  return;
400
299
  }
401
300
  tools.delta.subVectors(tools.hit, drag.hitStart);
301
+ if (drag.held) return void stateRef.current.held?.move(dragTravel(tools.delta, drag.planeAxes ? drag.planeAxes.map((axis) => AXIS_VECTORS[axis]) : [drag.dir], snapping ? 0.05 : 0));
402
302
  if (drag.planeAxes) {
403
303
  // Plane handle: two axes travel, the third is pinned. This is what
404
304
  // replaced body-dragging — the XZ square is how a set gets dressed.
@@ -438,7 +338,8 @@ export default function ObjectGizmo({ object, objects = [], mode = "move", snap
438
338
  const end = stateRef.current.onDragEnd;
439
339
  const token = drag.token;
440
340
  teardownDrag();
441
- if (token != null && end) end(token, { commit });
341
+ if (drag.held) stateRef.current.held?.end(commit);
342
+ else if (token != null && end) end(token, { commit });
442
343
  };
443
344
 
444
345
  /** pointerup and pointercancel both commit: the travel already applied
@@ -482,7 +383,8 @@ export default function ObjectGizmo({ object, objects = [], mode = "move", snap
482
383
  // (a jump of however far the car has driven). The pivot, and the yaw
483
384
  // the card's corners are seen at, are the shown ones.
484
385
  const live = stateRef.current.objects.find((entry) => entry.id === shown.id) ?? shown;
485
- tools.origin.set(shown.x, gizmoHeight(shown), shown.z);
386
+ const heldEdit = stateRef.current.held;
387
+ gizmoOrigin(shown, heldEdit, tools.origin);
486
388
  camera.getWorldDirection(tools.eye);
487
389
  let plane;
488
390
  let drag;
@@ -491,7 +393,9 @@ export default function ObjectGizmo({ object, objects = [], mode = "move", snap
491
393
  // pivot. The screen ring has no axis of its own: it spins
492
394
  // about the camera's own forward, so its plane is the
493
395
  // camera-facing plane through the pivot.
494
- const spinDir = axis === "screen" ? tools.eye : dir;
396
+ // A held object turns about its own axes (its rings are drawn turned too).
397
+ const turn = gizmoTurn(heldEdit, kind);
398
+ const spinDir = axis === "screen" ? tools.eye : turn ? dir.clone().applyQuaternion(new THREE.Quaternion(...turn)) : dir;
495
399
  plane = new THREE.Plane().setFromNormalAndCoplanarPoint(spinDir, tools.origin.clone());
496
400
  const hit = new THREE.Vector3();
497
401
  if (!tools.raycaster.ray.intersectPlane(plane, hit)) return false;
@@ -577,12 +481,14 @@ export default function ObjectGizmo({ object, objects = [], mode = "move", snap
577
481
  // (§6.1). The `cancel` handed over is teardown-only — the
578
482
  // coordinator owns the close, and a close attempted from inside it
579
483
  // is inert because the token is retired first (§5.2).
580
- const token = stateRef.current.onDragStart?.({
484
+ // A held-object drag edits the motion, not the record: no scene transaction.
485
+ const heldDrag = Boolean(heldEdit?.editable && (kind === "rotate" || (!heldEdit.hinge && (kind === "move" || kind === "plane"))) && heldEdit.begin());
486
+ const token = heldDrag ? undefined : stateRef.current.onDragStart?.({
581
487
  owner: "gizmo",
582
488
  cancel: teardownDrag,
583
489
  });
584
490
  const corner = kind === "corner" ? dir : null;
585
- dragRef.current = { ...drag, id: live.id, axis, plane, corner, start: { ...live }, token };
491
+ dragRef.current = { ...drag, id: live.id, axis, plane, corner, start: { ...live }, token, held: heldDrag };
586
492
  // the grabbed handle turns yellow now and stays yellow after
587
493
  // release, until the selection or the tool changes (§3.1)
588
494
  setActiveHandle(handleKey({ axis, plane, corner }));
@@ -593,7 +499,7 @@ export default function ObjectGizmo({ object, objects = [], mode = "move", snap
593
499
  // Only a real transaction gets the extra lifecycle edges: blur and
594
500
  // Escape would otherwise change mid-drag behaviour for callers that
595
501
  // pass no transaction props (the byte-for-byte fallback path).
596
- if (token != null) {
502
+ if (token != null || heldDrag) {
597
503
  window.addEventListener("blur", onBlur);
598
504
  window.addEventListener("keydown", onKeyDown, true);
599
505
  }
@@ -753,7 +659,7 @@ export default function ObjectGizmo({ object, objects = [], mode = "move", snap
753
659
  const camera = camRef?.current;
754
660
  if (!root || !camera || !object) return;
755
661
  camera.getWorldPosition(tools.camPos);
756
- tools.origin.set(object.x, gizmoHeight(object), object.z);
662
+ gizmoOrigin(object, held, tools.origin);
757
663
  root.scale.setScalar(Math.max(0.35, tools.camPos.distanceTo(tools.origin) * SCREEN_SCALE));
758
664
  if (mode !== "rotate") return;
759
665
  // The rotate rings are view-dependent, so their orientation is frame
@@ -764,6 +670,14 @@ export default function ObjectGizmo({ object, objects = [], mode = "move", snap
764
670
  camera.getWorldDirection(tools.eye);
765
671
  const screen = screenRingRef.current;
766
672
  if (screen) screen.quaternion.copy(camera.quaternion);
673
+ // Turned rings (a held object's own axes): the near/far split and the
674
+ // billboard are worked out in the gizmo's own frame.
675
+ const turn = gizmoTurn(held, mode);
676
+ if (turn) {
677
+ const unturn = new THREE.Quaternion(...turn).invert();
678
+ tools.eye.applyQuaternion(unturn);
679
+ screen?.quaternion.premultiply(unturn);
680
+ }
767
681
  for (const { axis, u, v } of ringBases) {
768
682
  const ring = ringGroupsRef.current[axis];
769
683
  if (!ring) continue;
@@ -855,8 +769,9 @@ export default function ObjectGizmo({ object, objects = [], mode = "move", snap
855
769
  const screenHovered = hoveredHandle === "screen";
856
770
  const centreActive = activeHandle === "centre";
857
771
  const centreHovered = hoveredHandle === "centre";
858
- return (
859
- <group ref={rootRef} position={[object.x, gizmoHeight(object), object.z]} renderOrder={999}>
772
+ return (<>
773
+ <HeldTrail trail={held?.trail ?? null} />
774
+ <group ref={rootRef} position={gizmoOrigin(object, held, new THREE.Vector3()).toArray()} quaternion={gizmoTurn(held, mode) ?? [0, 0, 0, 1]} renderOrder={999}>
860
775
  {AXES.map(({ axis, dir, color }) => {
861
776
  const quat = new THREE.Quaternion().setFromUnitVectors(new THREE.Vector3(0, 1, 0), dir);
862
777
  // The handle under a drag — and, after release, the last one —
@@ -1033,5 +948,5 @@ export default function ObjectGizmo({ object, objects = [], mode = "move", snap
1033
948
  </>
1034
949
  )}
1035
950
  </group>
1036
- );
951
+ </>);
1037
952
  }
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Glow: a record that gives off light of its own, drawn in one pass.
3
+ *
4
+ * There is no post-processing anywhere (the viewport, the shot preview,
5
+ * capture_frame, contact sheets and the video export all render the scene with
6
+ * one gl.render), so a bloom pass would have to be bolted onto every path.
7
+ * Instead a glow is two single-pass parts, both on the default layer:
8
+ * - an emissive colour on every material the record draws, which no stage
9
+ * light can darken, and
10
+ * - an additive, camera-facing halo sprite centred on the record, `halo` ×
11
+ * the record's radius. It writes no depth, casts no shadow and is never
12
+ * raycast, so it cannot occlude, shadow or be picked.
13
+ * Both scale with `gain` (the take's level, times a light's flicker).
14
+ * A glowing record is self-lit, so its meshes cast no shadow while it glows: a
15
+ * practical light placed inside a glowing bulb or flame lights the set instead
16
+ * of being shut in by the bulb's own faces (light-blockers.js).
17
+ *
18
+ * State is kept per group in a WeakMap, never in `userData` (React replaces a
19
+ * group's userData on every render). Removing the glow restores each
20
+ * material's own emissive and drops the halo.
21
+ */
22
+
23
+ import * as THREE from "three";
24
+
25
+ /** Halo brightness per unit of glow intensity. */
26
+ const HALO_GAIN = 0.2;
27
+ /** Radius used for a record with no size of its own (a light, an Empty), metres. */
28
+ const POINT_RADIUS = 0.02;
29
+ const noRaycast = () => {};
30
+ const glowStates = new WeakMap();
31
+
32
+ /** True while the group carries a glow that a later frame may have to remove. */
33
+ export function hasGlow(group) {
34
+ return glowStates.has(group);
35
+ }
36
+
37
+ let haloMap = null;
38
+ /** A white disc whose alpha is a bright core over a long soft tail. */
39
+ function haloTexture() {
40
+ if (haloMap) return haloMap;
41
+ const size = 64;
42
+ const data = new Uint8Array(size * size * 4);
43
+ for (let y = 0; y < size; y += 1) {
44
+ for (let x = 0; x < size; x += 1) {
45
+ const r = Math.hypot(x + 0.5 - size / 2, y + 0.5 - size / 2) / (size / 2);
46
+ const tail = r >= 1 ? 0 : (1 - r) ** 1.6;
47
+ const alpha = Math.min(1, 0.7 * Math.exp(-((r / 0.25) ** 2)) + 0.5 * tail);
48
+ data.set([255, 255, 255, Math.round(alpha * 255)], (y * size + x) * 4);
49
+ }
50
+ }
51
+ haloMap = new THREE.DataTexture(data, size, size);
52
+ haloMap.magFilter = THREE.LinearFilter;
53
+ haloMap.minFilter = THREE.LinearFilter;
54
+ haloMap.needsUpdate = true;
55
+ return haloMap;
56
+ }
57
+
58
+ function makeHalo() {
59
+ const material = new THREE.SpriteMaterial({ map: haloTexture(), blending: THREE.AdditiveBlending, transparent: true, depthWrite: false, fog: false });
60
+ const halo = new THREE.Sprite(material);
61
+ halo.userData.glowHalo = true;
62
+ // Beauty render only: the ink prepass and the depth/normal passes draw with
63
+ // an override material, which would turn the soft halo into a solid quad.
64
+ halo.userData.beautyOnly = true;
65
+ halo.raycast = noRaycast;
66
+ halo.castShadow = false;
67
+ halo.receiveShadow = false;
68
+ halo.renderOrder = 2;
69
+ return halo;
70
+ }
71
+
72
+ /** Each mesh the record draws on the default layer, and each of its lit
73
+ * materials (editor markers and the halo itself are left alone). */
74
+ function forEachGlowMesh(group, visitMesh, visitMaterial) {
75
+ group.traverse((node) => {
76
+ if (!node.isMesh || !node.layers.isEnabled(0)) return;
77
+ visitMesh(node);
78
+ for (const material of Array.isArray(node.material) ? node.material : [node.material]) {
79
+ if (material?.emissive?.isColor) visitMaterial(material);
80
+ }
81
+ });
82
+ }
83
+
84
+ function clearGlow(group, state) {
85
+ for (const [material, base] of state.bases) {
86
+ material.emissive.copy(base.emissive);
87
+ material.emissiveIntensity = base.intensity;
88
+ }
89
+ for (const mesh of state.shadowless) mesh.castShadow = true;
90
+ state.halo.removeFromParent();
91
+ state.halo.material.dispose();
92
+ glowStates.delete(group);
93
+ }
94
+
95
+ /** The record's centre (local) and its radius (world): half its largest
96
+ * extent, so a sphere's radius is exact; a record with no size (a light, an
97
+ * Empty) counts as POINT_RADIUS. Size comes from the record, the same numbers
98
+ * the selection cage and the plan use. */
99
+ function recordSphere(object, scale) {
100
+ const width = (object.footprint?.width ?? 0) * Math.abs(scale.x);
101
+ const height = (object.height ?? 0) * Math.abs(scale.y);
102
+ const depth = (object.footprint?.depth ?? 0) * Math.abs(scale.z);
103
+ const radius = Math.max(width, height, depth) / 2;
104
+ return { centreY: (object.height ?? 0) / 2, radius: radius > 0 ? radius : POINT_RADIUS };
105
+ }
106
+
107
+ /** Apply, update or remove the record's glow for this frame. */
108
+ export function placeObjectGlow(group, object, gain) {
109
+ const glow = object.glow;
110
+ let state = glowStates.get(group);
111
+ if (!glow) {
112
+ if (state) clearGlow(group, state);
113
+ return;
114
+ }
115
+ if (!state) {
116
+ state = { halo: makeHalo(), bases: new Map(), shadowless: new Set() };
117
+ glowStates.set(group, state);
118
+ }
119
+ if (state.halo.parent !== group) group.add(state.halo);
120
+ const strength = glow.intensity * gain;
121
+ forEachGlowMesh(group, (mesh) => {
122
+ // a mesh rebuilt while glowing (a new kind, a loaded file) casts again until this pass
123
+ if (!mesh.castShadow) return;
124
+ mesh.castShadow = false;
125
+ state.shadowless.add(mesh);
126
+ }, (material) => {
127
+ if (!state.bases.has(material)) state.bases.set(material, { emissive: material.emissive.clone(), intensity: material.emissiveIntensity });
128
+ material.emissive.set(glow.color);
129
+ material.emissiveIntensity = strength;
130
+ });
131
+ const { centreY, radius } = recordSphere(object, group.scale);
132
+ const size = 2 * radius * glow.halo * gain;
133
+ const halo = state.halo;
134
+ halo.visible = size > 0 && strength > 0;
135
+ halo.position.set(0, centreY, 0);
136
+ // The sprite inherits the record's scale; divide it out so the halo stays round.
137
+ halo.scale.set(size / (Math.abs(group.scale.x) || 1), size / (Math.abs(group.scale.y) || 1), 1);
138
+ halo.material.color.set(glow.color).multiplyScalar(HALO_GAIN * strength);
139
+ }
@@ -0,0 +1,33 @@
1
+ // Effective visibility of a scene record through its parents and its carrying
2
+ // character. Pure and dependency-free, so the renderers, the agent commands and
3
+ // the effects can share it; scene-objects.js re-exports it.
4
+
5
+ /** True when this record, a parent above it, or a character carrying any of
6
+ * those records is hidden. A cycle in parent links ends the walk. When the
7
+ * id is in `objects`, that stored record is what the walk reads, so a display
8
+ * copy cannot disagree with the authored flag. Nothing in the lists is changed. */
9
+ export function isEffectivelyHidden(entity, objects = [], characters = []) {
10
+ if (!entity || typeof entity !== "object") return false;
11
+ const byId = new Map();
12
+ for (const object of objects) {
13
+ if (object && typeof object.id === "string") byId.set(object.id, object);
14
+ }
15
+ const characterHidden = (id) => characters.some((item) => item && typeof item === "object" && item.id === id && item.hidden === true);
16
+ const seen = new Set();
17
+ let current = entity;
18
+ while (current && typeof current === "object") {
19
+ const id = typeof current.id === "string" ? current.id : null;
20
+ if (id) {
21
+ if (seen.has(id)) break;
22
+ seen.add(id);
23
+ if (byId.has(id)) current = byId.get(id);
24
+ }
25
+ if (current.hidden === true) return true;
26
+ const characterId = current.attach && typeof current.attach.characterId === "string" ? current.attach.characterId : null;
27
+ if (characterId && characterHidden(characterId)) return true;
28
+ const parentId = typeof current.parent === "string" ? current.parent : null;
29
+ if (!parentId) break;
30
+ current = byId.get(parentId) ?? null;
31
+ }
32
+ return false;
33
+ }
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Practical lights and glow: the record fields, their defaults and clamps, and
3
+ * the per-frame numbers the renderer reads (flicker, take level, shadow cap).
4
+ *
5
+ * Pure on purpose: no three.js and no React, so the agent sidecar, the record
6
+ * normalizer and Node tests all read the same rules.
7
+ *
8
+ * A light is a record with renderer "light". It is created from one of two
9
+ * catalogue kinds, `point-light` and `spot-light`, which differ only in the
10
+ * `light.type` they start with. Like an Empty it has no size: no collision, no
11
+ * surface, no footprint on the plan.
12
+ *
13
+ * `glow` can sit on any record. It makes the record's materials emissive and
14
+ * adds a camera-facing additive halo, so an ember or a bulb reads as a light
15
+ * source in the shot render without any post-processing pass.
16
+ */
17
+
18
+ import { normalizeObjectColor } from "./object-colors.js";
19
+
20
+ export const LIGHT_KIND = "light";
21
+
22
+ /** three.js intensity per authored unit. Authored values are candela-like
23
+ * (decay 2: lighter flame 0.3-0.6, candle 0.3, desk lamp 5, street lamp 40).
24
+ * The scale is tuned against the default stage key (directional, 1.12) so a
25
+ * street lamp's pool at 4 m reads about as bright as half the key, and a
26
+ * lighter 25 cm from a face lifts it to roughly the key. */
27
+ export const LIGHT_INTENSITY_SCALE = 0.35;
28
+
29
+ /** At most this many practical lights render shadows (each point shadow is six
30
+ * extra depth passes). The rest still light the scene and are reported. */
31
+ export const PRACTICAL_SHADOW_CAP = 2;
32
+
33
+ const LIGHT_DEFAULTS = Object.freeze({
34
+ point: Object.freeze({ type: "point", color: "#ffc787", intensity: 5, range: 0, angle: 30, softness: 0.3, shadow: false, flicker: 0, aim: null }),
35
+ spot: Object.freeze({ type: "spot", color: "#ffe0b2", intensity: 40, range: 0, angle: 30, softness: 0.3, shadow: true, flicker: 0, aim: null }),
36
+ });
37
+ const GLOW_DEFAULTS = Object.freeze({ color: "#ff9a3c", intensity: 4, halo: 1.5 });
38
+
39
+ const LIGHT_SIZE = { footprint: { width: 0, depth: 0 }, height: 0 };
40
+ /** The record entry every light normalizes through. */
41
+ const LIGHT_ENTRY = Object.freeze({ kind: LIGHT_KIND, label: "Light", group: "Lights", ...LIGHT_SIZE, color: "#ffd38a" });
42
+ /** The two creatable kinds: the add menu and the agent catalogue list these.
43
+ * `placeY` is where a fresh one hangs, so it lights something on arrival. */
44
+ export const LIGHT_MENU_ENTRIES = Object.freeze([
45
+ Object.freeze({ kind: "point-light", renderer: LIGHT_KIND, label: "Point light", group: "Lights", ...LIGHT_SIZE, color: "#ffc787", placeY: 1.5, light: LIGHT_DEFAULTS.point }),
46
+ Object.freeze({ kind: "spot-light", renderer: LIGHT_KIND, label: "Spot light", group: "Lights", ...LIGHT_SIZE, color: "#ffe0b2", placeY: 3, light: LIGHT_DEFAULTS.spot }),
47
+ ]);
48
+
49
+ /** The library entry for a light kind or the light renderer, or null. */
50
+ export function lightLibraryEntry(kind) {
51
+ if (kind === LIGHT_KIND) return LIGHT_ENTRY;
52
+ return LIGHT_MENU_ENTRIES.find((entry) => entry.kind === kind) ?? null;
53
+ }
54
+
55
+ const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
56
+ const finite = (value, fallback, min, max) => {
57
+ const n = typeof value === "number" || typeof value === "string" ? Number(value) : NaN;
58
+ return Number.isFinite(n) ? clamp(n, min, max) : fallback;
59
+ };
60
+ const hex = (value, fallback) => normalizeObjectColor(value) ?? fallback;
61
+ const point = (value) => {
62
+ if (!value || typeof value !== "object") return null;
63
+ const out = { x: Number(value.x), y: Number(value.y), z: Number(value.z) };
64
+ return Object.values(out).every(Number.isFinite) ? out : null;
65
+ };
66
+
67
+ /** A stored or requested light repaired into the full record. `type` falls back
68
+ * to `typeHint` (the kind it was created as), then to "point". */
69
+ export function normalizeObjectLight(value, typeHint = "point") {
70
+ const source = value && typeof value === "object" && !Array.isArray(value) ? value : {};
71
+ const type = source.type === "spot" || source.type === "point" ? source.type : typeHint === "spot" ? "spot" : "point";
72
+ const base = LIGHT_DEFAULTS[type];
73
+ return {
74
+ type,
75
+ color: hex(source.color, base.color),
76
+ intensity: finite(source.intensity, base.intensity, 0, 1000),
77
+ range: finite(source.range, base.range, 0, 100),
78
+ angle: finite(source.angle, base.angle, 1, 89),
79
+ softness: finite(source.softness, base.softness, 0, 1),
80
+ shadow: typeof source.shadow === "boolean" ? source.shadow : base.shadow,
81
+ flicker: finite(source.flicker, base.flicker, 0, 1),
82
+ aim: point(source.aim),
83
+ };
84
+ }
85
+
86
+ /** A glow repaired into `{ color, intensity, halo }`, or null for none. */
87
+ export function normalizeObjectGlow(value) {
88
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
89
+ return {
90
+ color: hex(value.color, GLOW_DEFAULTS.color),
91
+ intensity: finite(value.intensity, GLOW_DEFAULTS.intensity, 0, 20),
92
+ halo: finite(value.halo, GLOW_DEFAULTS.halo, 0, 4),
93
+ };
94
+ }
95
+
96
+ /** The light/glow fields a normalized record carries. A light renderer always
97
+ * has a light; any record keeps a glow only when it has one, so records written
98
+ * before glow existed come back byte-for-byte unchanged. */
99
+ export function objectLookFields(record, entry) {
100
+ const out = {};
101
+ if ((entry.renderer ?? entry.kind) === LIGHT_KIND) out.light = normalizeObjectLight(record.light, record.light?.type ?? entry.light?.type);
102
+ const glow = normalizeObjectGlow(record.glow);
103
+ if (glow) out.glow = glow;
104
+ // A kit part a take switches on (a muzzle flash and its light) stays hidden without one (object-travel.js trackShown).
105
+ if (record.showOnlyInTake === true) out.showOnlyInTake = true;
106
+ return out;
107
+ }
108
+
109
+ /** The light/glow part of an object.update patch: each field merges over the
110
+ * record's own and is repaired; `glow: null` removes the glow. Only changes are
111
+ * returned. A light on a record that is not a light is ignored. */
112
+ export function objectLookPatch(object, patch) {
113
+ const update = {};
114
+ if (patch?.light && typeof patch.light === "object" && object.renderer === LIGHT_KIND) {
115
+ const next = normalizeObjectLight({ ...object.light, ...patch.light }, object.light?.type);
116
+ if (JSON.stringify(next) !== JSON.stringify(object.light)) update.light = next;
117
+ }
118
+ if (patch?.glow !== undefined) {
119
+ const next = patch.glow === null ? null : normalizeObjectGlow({ ...(object.glow ?? {}), ...patch.glow });
120
+ if (JSON.stringify(next) !== JSON.stringify(object.glow ?? null)) update.glow = next;
121
+ }
122
+ return update;
123
+ }
124
+
125
+ /** 32-bit FNV-1a of the record id: the flicker's seed. */
126
+ function seedOf(id) {
127
+ let hash = 0x811c9dc5;
128
+ for (const char of String(id)) {
129
+ hash ^= char.codePointAt(0);
130
+ hash = Math.imul(hash, 0x01000193) >>> 0;
131
+ }
132
+ return hash;
133
+ }
134
+
135
+ /** Lattice value in [-1, 1] for an integer cell of a seeded stream. */
136
+ function lattice(seed, cell) {
137
+ let h = Math.imul(seed ^ Math.imul(cell, 0x27d4eb2d), 0x165667b1) >>> 0;
138
+ h = Math.imul(h ^ (h >>> 15), 0x85ebca6b) >>> 0;
139
+ h = Math.imul(h ^ (h >>> 13), 0xc2b2ae35) >>> 0;
140
+ return ((h ^ (h >>> 16)) >>> 0) / 0x7fffffff - 1;
141
+ }
142
+
143
+ /** Smooth value noise in [-1, 1] at time `t` (cells of 1). */
144
+ function noise(seed, t) {
145
+ const cell = Math.floor(t);
146
+ const f = t - cell;
147
+ const s = f * f * (3 - 2 * f);
148
+ return lattice(seed, cell) * (1 - s) + lattice(seed, cell + 1) * s;
149
+ }
150
+
151
+ /**
152
+ * The flicker multiplier of a light at `frame`: a pure function of the id, the
153
+ * frame and the amount, so scrubbing, playback and export see the same value.
154
+ * 0 is steady (exactly 1); 0.3 wavers like a candle; 0.6 jumps like a lighter.
155
+ */
156
+ export function flickerAt(id, frame, amount, fps = 24) {
157
+ const depth = finite(amount, 0, 0, 1);
158
+ if (!(depth > 0)) return 1;
159
+ const seed = seedOf(id);
160
+ const t = (Number(frame) || 0) / (fps > 0 ? fps : 24);
161
+ const n = 0.55 * noise(seed, t * 7) + 0.3 * noise(seed ^ 0x9e3779b9, t * 15) + 0.15 * noise(seed ^ 0x7f4a7c15, t * 31);
162
+ return Math.max(0, 1 + depth * 0.65 * n);
163
+ }
164
+
165
+ /**
166
+ * Which shown lights that ask for a shadow get one: the first
167
+ * PRACTICAL_SHADOW_CAP in record order. `capped` lists the rest by id.
168
+ * `isHidden(record)` drops records the stage does not draw.
169
+ */
170
+ export function practicalShadows(objects, isHidden = (object) => object?.hidden === true) {
171
+ const asking = (objects ?? []).filter((object) => object?.renderer === LIGHT_KIND && object.light?.shadow === true && !isHidden(object));
172
+ return { casting: asking.slice(0, PRACTICAL_SHADOW_CAP).map((object) => object.id), capped: asking.slice(PRACTICAL_SHADOW_CAP).map((object) => object.id) };
173
+ }
174
+
175
+ /** The records as the stage draws them: lights past the shadow cap render with
176
+ * `shadow: false` and `shadowCapped: true`. The same array when none is capped. */
177
+ export function capPracticalShadows(objects) {
178
+ const capped = new Set(practicalShadows(objects).capped);
179
+ if (!capped.size) return objects;
180
+ return objects.map((object) => (capped.has(object.id) ? { ...object, light: { ...object.light, shadow: false }, shadowCapped: true } : object));
181
+ }
182
+
183
+ /** Receipt warnings, one per light that asked for a shadow but is past the cap,
184
+ * limited to `ids` when given (the records an edit touched). */
185
+ function shadowCapWarnings(objects, ids = null, isHidden = undefined) {
186
+ const { casting, capped } = practicalShadows(objects, isHidden);
187
+ return capped.filter((id) => !ids || ids.includes(id)).slice(0, 4).map((id) => ({ code: "SHADOW_CAP", id,
188
+ message: `Renders without a shadow: only ${PRACTICAL_SHADOW_CAP} lights cast shadows (${casting.join(", ")}).`.slice(0, 120) }));
189
+ }
190
+
191
+ /** A command result with the shadow-cap warnings for the records it touched
192
+ * appended, so a receipt says when a requested shadow is not drawn. */
193
+ export function withShadowCapWarnings(result, objects) {
194
+ const warnings = shadowCapWarnings(objects, result?.affectedIds ?? []);
195
+ return warnings.length ? { ...result, warnings: [...(result.warnings ?? []), ...warnings] } : result;
196
+ }
@@ -0,0 +1,10 @@
1
+ // A leaf module: the motion compiler's scene reader (motion3/scene.js) needs an object's size without
2
+ // scene-objects.js, which imports three; the agent sidecar reaches motion3 through motion.describe and must not.
3
+ /** The object's world size along each axis, for the gizmo and the plan board. */
4
+ export function objectSize(object) {
5
+ return {
6
+ width: (object.footprint?.width ?? 1) * (object.scaleX ?? 1),
7
+ height: (object.height ?? 1) * (object.scaleY ?? 1),
8
+ depth: (object.footprint?.depth ?? 1) * (object.scaleZ ?? 1),
9
+ };
10
+ }