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,72 @@
1
+ /**
2
+ * The per-frame half of practical lights and glow, kept free of React so the
3
+ * place pass can be exercised in Node: SceneObject calls `placeObjectLook`
4
+ * from its imperative place pass (and the recorder through it, for every
5
+ * frame it renders), and it writes the light's numbers and the glow for that
6
+ * frame only.
7
+ */
8
+
9
+ import * as THREE from "three";
10
+ import { LIGHT_INTENSITY_SCALE, flickerAt } from "./object-look.js";
11
+ import { trackLevel } from "./object-travel.js";
12
+ import { hasGlow, placeObjectGlow } from "./object-glow.js";
13
+
14
+ const DEG = Math.PI / 180;
15
+ /** A shadow camera's far plane when the light has no range of its own. */
16
+ export const SHADOW_FAR = 40;
17
+
18
+ const DOWN = new THREE.Vector3(0, -1, 0);
19
+ const scratchDir = new THREE.Vector3();
20
+ const scratchPos = new THREE.Vector3();
21
+ const scratchQuat = new THREE.Quaternion();
22
+ const scratchScale = new THREE.Vector3();
23
+
24
+ function childWith(group, key) {
25
+ for (const child of group.children) if (child.userData?.[key]) return child;
26
+ return null;
27
+ }
28
+
29
+ /** Write the light's numbers for this frame. `gain` is level × flicker. */
30
+ function placeSceneLight(group, object, gain) {
31
+ const light = childWith(group, "sceneLight");
32
+ if (!light) return;
33
+ const { color, intensity, range, angle, softness, aim } = object.light;
34
+ light.color.set(color);
35
+ light.intensity = intensity * LIGHT_INTENSITY_SCALE * gain;
36
+ light.distance = range;
37
+ if (light.castShadow && light.isPointLight) {
38
+ const far = range > 0 ? range : SHADOW_FAR;
39
+ if (light.shadow.camera.far !== far) {
40
+ light.shadow.camera.far = far;
41
+ light.shadow.camera.updateProjectionMatrix();
42
+ }
43
+ }
44
+ if (!light.isSpotLight) return;
45
+ light.angle = angle * DEG;
46
+ light.penumbra = softness;
47
+ // The target is kept outside the scene graph, in world space: an aim point
48
+ // is a place in the set, and the record's own -Y is where it hangs pointing.
49
+ group.updateWorldMatrix(true, false);
50
+ group.matrixWorld.decompose(scratchPos, scratchQuat, scratchScale);
51
+ if (aim) scratchDir.set(aim.x - scratchPos.x, aim.y - scratchPos.y, aim.z - scratchPos.z);
52
+ else scratchDir.copy(DOWN).applyQuaternion(scratchQuat);
53
+ if (scratchDir.lengthSq() < 1e-10) scratchDir.copy(DOWN).applyQuaternion(scratchQuat);
54
+ scratchDir.normalize();
55
+ light.target.position.copy(scratchPos).add(scratchDir);
56
+ light.target.updateMatrixWorld();
57
+ const cone = childWith(group, "lightMarker")?.children.find((child) => child.userData?.lightCone);
58
+ if (cone) cone.quaternion.setFromUnitVectors(DOWN, scratchDir.applyQuaternion(scratchQuat.invert()));
59
+ }
60
+
61
+ /**
62
+ * The per-frame look of one record: its light (if it is one) and its glow (if
63
+ * it has one), from the take's level at `frame` and the light's flicker. Pure
64
+ * in the frame number, so any frame can be drawn in any order.
65
+ */
66
+ export function placeObjectLook(group, object, frame, take) {
67
+ if (!object.light && !object.glow && !hasGlow(group)) return;
68
+ const level = trackLevel(object.id, frame, take);
69
+ const gain = level * (object.light ? flickerAt(object.id, frame, object.light.flicker, take?.fps) : 1);
70
+ if (object.light) placeSceneLight(group, object, gain);
71
+ placeObjectGlow(group, object, gain);
72
+ }
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Practical lights in the set: a point or spot light a record carries, and its
3
+ * editor-only marker.
4
+ *
5
+ * The light itself lives on the default layer, so the viewport, the shot
6
+ * preview, capture_frame, contact sheets and the video export all light the
7
+ * set with it. The marker (a bulb, plus the cone for a spot) is editor
8
+ * furniture on GIZMO_LAYER, exactly like an Empty's axes: drawn in the working
9
+ * views, dropped by every recorded camera, and picked through the same
10
+ * `emptyPick` volume so the user can select and move the light.
11
+ *
12
+ * Everything that changes with time (take level, flicker, a spot's aim while
13
+ * the record travels) is written by `placeObjectLook` (scene-light-place.js),
14
+ * which SceneObject calls from its imperative place pass. The recorder runs
15
+ * that pass for every frame it renders, so a scrub or an export never sees a
16
+ * stale light.
17
+ */
18
+
19
+ import { useEffect, useMemo } from "react";
20
+ import { useThree } from "@react-three/fiber";
21
+ import * as THREE from "three";
22
+ import { GIZMO_LAYER } from "./dualview.jsx";
23
+ import { EMPTY_MARKER_SIZE } from "./scene-objects.js";
24
+ import { SHADOW_FAR } from "./scene-light-place.js";
25
+
26
+ const DEG = Math.PI / 180;
27
+ const BULB_RADIUS = 0.07;
28
+ /** Drawn length of a spot's cone outline, metres (the light reaches further). */
29
+ const CONE_LENGTH = 0.7;
30
+
31
+ function makeLight(type, shadow) {
32
+ const light = type === "spot" ? new THREE.SpotLight() : new THREE.PointLight();
33
+ // three.js hangs a spot 1 m up (DEFAULT_UP) by default; a practical sits on its record's pivot.
34
+ light.position.set(0, 0, 0);
35
+ light.decay = 2;
36
+ light.castShadow = shadow;
37
+ light.userData.sceneLight = true;
38
+ if (shadow) {
39
+ const size = type === "spot" ? 1024 : 512;
40
+ light.shadow.mapSize.set(size, size);
41
+ light.shadow.bias = -0.0004;
42
+ light.shadow.normalBias = 0.02;
43
+ // A lighter a hand-width from a face must still shadow the hand.
44
+ light.shadow.camera.near = 0.03;
45
+ light.shadow.camera.far = SHADOW_FAR;
46
+ light.shadow.camera.updateProjectionMatrix();
47
+ }
48
+ return light;
49
+ }
50
+
51
+ /** Line pairs of a circle of `radius` in the plane spanned by axes a and b. */
52
+ function circle(out, radius, a, b, y = 0, segments = 24) {
53
+ for (let i = 0; i < segments; i += 1) {
54
+ for (const k of [i, i + 1]) {
55
+ const t = (k / segments) * Math.PI * 2;
56
+ const p = [0, y, 0];
57
+ p[a] += Math.cos(t) * radius;
58
+ p[b] += Math.sin(t) * radius;
59
+ out.push(...p);
60
+ }
61
+ }
62
+ return out;
63
+ }
64
+
65
+ function lineGeometry(positions) {
66
+ const geometry = new THREE.BufferGeometry();
67
+ geometry.setAttribute("position", new THREE.Float32BufferAttribute(positions, 3));
68
+ return geometry;
69
+ }
70
+
71
+ /** The bulb: three rings and a few short rays, so it reads as a light, not a ball. */
72
+ function bulbGeometry() {
73
+ const out = [];
74
+ circle(out, BULB_RADIUS, 0, 2);
75
+ circle(out, BULB_RADIUS, 0, 1);
76
+ circle(out, BULB_RADIUS, 1, 2);
77
+ for (let i = 0; i < 8; i += 1) {
78
+ const t = (i / 8) * Math.PI * 2;
79
+ out.push(Math.cos(t) * BULB_RADIUS * 1.3, 0, Math.sin(t) * BULB_RADIUS * 1.3, Math.cos(t) * BULB_RADIUS * 2, 0, Math.sin(t) * BULB_RADIUS * 2);
80
+ }
81
+ return lineGeometry(out);
82
+ }
83
+
84
+ /** A spot's cone along local -Y: four edges and the rim at CONE_LENGTH. */
85
+ function coneGeometry(angleDeg) {
86
+ const radius = CONE_LENGTH * Math.tan(angleDeg * DEG);
87
+ const out = circle([], radius, 0, 2, -CONE_LENGTH, 32);
88
+ for (let i = 0; i < 4; i += 1) {
89
+ const t = (i / 4) * Math.PI * 2;
90
+ out.push(0, 0, 0, Math.cos(t) * radius, -CONE_LENGTH, Math.sin(t) * radius);
91
+ }
92
+ return lineGeometry(out);
93
+ }
94
+
95
+ const onGizmoLayer = (node) => {
96
+ if (node) node.layers.set(GIZMO_LAYER);
97
+ };
98
+
99
+ function LightMarker({ type, angle, color }) {
100
+ const bulb = useMemo(() => bulbGeometry(), []);
101
+ const cone = useMemo(() => (type === "spot" ? coneGeometry(angle) : null), [type, angle]);
102
+ useEffect(() => () => bulb.dispose(), [bulb]);
103
+ useEffect(() => () => cone?.dispose(), [cone]);
104
+ return (
105
+ <group userData={{ lightMarker: true }}>
106
+ <lineSegments ref={onGizmoLayer} geometry={bulb} renderOrder={997} frustumCulled={false}>
107
+ <lineBasicMaterial color={color} depthTest={false} depthWrite={false} transparent opacity={0.95} />
108
+ </lineSegments>
109
+ {cone && (
110
+ <group userData={{ lightCone: true }}>
111
+ <lineSegments ref={onGizmoLayer} geometry={cone} renderOrder={997} frustumCulled={false}>
112
+ <lineBasicMaterial color={color} depthTest={false} depthWrite={false} transparent opacity={0.55} />
113
+ </lineSegments>
114
+ </group>
115
+ )}
116
+ {/* Never drawn, still raycast: the object picker's gizmo-layer pass
117
+ finds it by `emptyPick` and selects the light's record. */}
118
+ <mesh ref={onGizmoLayer} visible={false} userData={{ emptyPick: true }}>
119
+ <sphereGeometry args={[EMPTY_MARKER_SIZE * 0.45, 12, 8]} />
120
+ <meshBasicMaterial />
121
+ </mesh>
122
+ </group>
123
+ );
124
+ }
125
+
126
+ /** A light record's content: the three.js light and its marker. Rebuilt only
127
+ * when the type or the shadow switch changes (both change the shader). */
128
+ export function SceneLight({ object }) {
129
+ const { type, shadow, angle, color } = object.light;
130
+ const light = useMemo(() => makeLight(type, shadow), [type, shadow]);
131
+ useEffect(() => () => light.dispose(), [light]);
132
+ return (
133
+ <>
134
+ <primitive object={light} />
135
+ <LightMarker type={type} angle={angle} color={color} />
136
+ </>
137
+ );
138
+ }
139
+
140
+ /** Ask for a frame when the record's light or glow changes. Both are written by
141
+ * the place pass, which a demand frame loop runs only when something asks: an
142
+ * edit that changes no three.js prop would otherwise wait for the next orbit. */
143
+ export function useLookRefresh(object) {
144
+ const invalidate = useThree((state) => state.invalidate);
145
+ useEffect(() => {
146
+ invalidate();
147
+ }, [invalidate, object.light, object.glow]);
148
+ }
@@ -22,6 +22,13 @@ import { createObjectPath, translateObjectPath } from "./object-path.js";
22
22
  import { elementByPath } from "./studio-elements.js";
23
23
  import { MESH_DEFAULT_HEIGHT, MESH_HEIGHT_MIN } from "./scene-mesh.js";
24
24
  import { normalizeSculptRecipe, sculptStandingBox } from "./sculpt-recipe.js";
25
+ import { GREY_BOX } from "./object-colors.js";
26
+ import { isEffectivelyHidden } from "./object-hidden.js";
27
+ import { objectSize } from "./object-size.js";
28
+ import { EFFECT_ENTRY, effectFields } from "./effects/effect-record.js";
29
+ import { LIGHT_KIND, LIGHT_MENU_ENTRIES, lightLibraryEntry, objectLookFields, objectLookPatch } from "./object-look.js";
30
+ export { OBJECT_COLORS, normalizeObjectColor, rememberObjectColor, readStoredObjectColors, writeStoredObjectColors } from "./object-colors.js";
31
+ export { isEffectivelyHidden };
25
32
 
26
33
  export const DEFAULT_SCENE_OBJECTS = [];
27
34
  /** The persistence contract (plan §8.1): the version lives in the key AND in
@@ -60,7 +67,6 @@ const snapTo = (value, step) => Number((step > 0 ? Math.round(value / step) * st
60
67
  * else, and a tinted maquette reads as a finished prop. The hand-built set
61
68
  * pieces keep their clay colours because they ARE the thing they depict.
62
69
  */
63
- const GREY_BOX = "#c2c6c8";
64
70
  export const OBJECT_LIBRARY = [
65
71
  { kind: "cube", label: "Cube", group: "Primitives", footprint: { width: 1, depth: 1 }, height: 1, color: GREY_BOX },
66
72
  { kind: "sphere", label: "Sphere", group: "Primitives", footprint: { width: 1, depth: 1 }, height: 1, color: GREY_BOX },
@@ -75,96 +81,6 @@ export const OBJECT_LIBRARY = [
75
81
  { kind: "small-plane", label: "Plane (aircraft)", group: "Set pieces", footprint: { width: 3.4, depth: 3.6 }, height: 1.4, color: "#7896a4" },
76
82
  ];
77
83
 
78
- /** Blockout greys first, then the clay accents, then the blocking chromatics,
79
- * for re-tinting from the inspector. The greys lead because a blockout starts
80
- * grey; the saturated four exist so a shot can say "the red ball, the blue
81
- * box" at a glance — the inspector had no way to say that at all (#88). They
82
- * are muted off pure RGB so they still read as clay under the set's lighting.
83
- * Any other colour is authored through the inspector's free colour input. */
84
- export const OBJECT_COLORS = [
85
- "#e2e5e6",
86
- GREY_BOX,
87
- "#9aa1a5",
88
- "#767d81",
89
- "#d9b18c",
90
- "#8fae9b",
91
- "#d94a4a",
92
- "#4a7bd9",
93
- "#e2c04a",
94
- "#4fa86a",
95
- ];
96
-
97
- /** How many hand-mixed colours the inspector remembers. Six is one palette
98
- * row: enough to keep a scene's custom tints reachable, short enough that the
99
- * popover never turns into a second, unruly palette. */
100
- const RECENT_OBJECT_COLORS_MAX = 6;
101
- /** Session memory of hand-mixed tints; versioned like every other key here. */
102
- const RECENT_OBJECT_COLORS_KEY = "cozyclay.objectColors.recent";
103
-
104
- const HEX_SHORT = /^#?([0-9a-f]{3})$/i;
105
- const HEX_LONG = /^#?([0-9a-f]{6})$/i;
106
-
107
- /**
108
- * What a colour input typed by a human means, or null if it means nothing.
109
- *
110
- * The hex field accepts what people actually type — `#f36`, `ff3366`, upper
111
- * case, stray spaces — and folds it to the one form the record stores, so a
112
- * colour typed by hand and the same colour picked from the native swatch are
113
- * byte-identical (and therefore compare equal against the palette). Anything
114
- * else returns null rather than throwing or guessing: a half-typed `#12` is a
115
- * keystroke on the way somewhere, not an instruction to repaint the prop.
116
- */
117
- export function normalizeObjectColor(value) {
118
- if (typeof value !== "string") return null;
119
- const text = value.trim();
120
- const short = HEX_SHORT.exec(text);
121
- if (short) return `#${short[1].toLowerCase().replace(/./g, (digit) => digit + digit)}`;
122
- const long = HEX_LONG.exec(text);
123
- return long ? `#${long[1].toLowerCase()}` : null;
124
- }
125
-
126
- /**
127
- * The recent-colours list after mixing `hex`: newest first, no duplicates,
128
- * capped, presets excluded.
129
- *
130
- * Pure so the list can be proved without a browser, and so a corrupt stored
131
- * list is repaired on the way through instead of painting a swatch with junk.
132
- * Presets are skipped because they are already one row up — remembering them
133
- * would push the author's own colours off the end with colours they never
134
- * mixed. Returns the SAME array when nothing changes, so a re-pick of an
135
- * unchanged colour cannot trigger a storage write or a re-render.
136
- */
137
- export function rememberObjectColor(list, hex) {
138
- const clean = Array.isArray(list)
139
- ? list.map(normalizeObjectColor).filter((color, index, all) => color && all.indexOf(color) === index && !OBJECT_COLORS.includes(color))
140
- : [];
141
- const color = normalizeObjectColor(hex);
142
- const next = color && !OBJECT_COLORS.includes(color) ? [color, ...clean.filter((entry) => entry !== color)] : clean;
143
- const capped = next.slice(0, RECENT_OBJECT_COLORS_MAX);
144
- const unchanged = Array.isArray(list) && capped.length === list.length && capped.every((entry, index) => entry === list[index]);
145
- return unchanged ? list : capped;
146
- }
147
-
148
- /** Recent colours as last left by this browser; storage can be unavailable
149
- * (private mode), and a corrupt body must not take the inspector down with
150
- * it — either way the palette simply starts without a memory. */
151
- export function readStoredObjectColors(storage) {
152
- try {
153
- const parsed = JSON.parse(storage?.getItem(RECENT_OBJECT_COLORS_KEY) ?? "[]");
154
- return rememberObjectColor(Array.isArray(parsed) ? parsed : [], null);
155
- } catch {
156
- return [];
157
- }
158
- }
159
-
160
- export function writeStoredObjectColors(storage, list) {
161
- try {
162
- storage?.setItem(RECENT_OBJECT_COLORS_KEY, JSON.stringify(list));
163
- } catch {
164
- // Storage can be unavailable (private mode); the colours simply stop persisting.
165
- }
166
- }
167
-
168
84
  /**
169
85
  * A cutout is a standee: an imported image standing on a card. Its size is NOT
170
86
  * library data — the height is measured by the user and the width follows the
@@ -253,14 +169,16 @@ const EMPTY_ENTRY = {
253
169
  color: "#ffffff",
254
170
  };
255
171
 
256
- /** True for a record that is only a node: no mesh, no collision, no surface. */
172
+ /** True for a record that is only a node: no mesh, no collision, no surface.
173
+ * A practical light (object-look.js) is one too: it lights, it does not stand. */
257
174
  export function isEmptyObject(object) {
258
- return object?.renderer === EMPTY_KIND;
175
+ return object?.renderer === EMPTY_KIND || object?.renderer === LIGHT_KIND;
259
176
  }
260
177
 
261
- /** The catalogue grouped for the create menu: the library plus the empty,
262
- * which is creatable but has no footprint to satisfy the library's contract. */
263
- export const OBJECT_MENU_ENTRIES = Object.freeze([EMPTY_ENTRY, ...OBJECT_LIBRARY]);
178
+ /** The catalogue grouped for the create menu: the library plus the empty, the
179
+ * two practical lights and the effect emitter, which are creatable but have no
180
+ * footprint to satisfy the library's contract. */
181
+ export const OBJECT_MENU_ENTRIES = Object.freeze([EMPTY_ENTRY, ...OBJECT_LIBRARY, ...LIGHT_MENU_ENTRIES, EFFECT_ENTRY]);
264
182
 
265
183
  /** Every kind that can exist in a scene: the catalogue you can create from,
266
184
  * plus the kinds that arrive by import and so are deliberately absent from the
@@ -271,7 +189,8 @@ function objectLibraryEntry(kind) {
271
189
  if (kind === MESH_KIND) return MESH_ENTRY;
272
190
  if (kind === SCULPT_KIND) return SCULPT_ENTRY;
273
191
  if (kind === EMPTY_KIND) return EMPTY_ENTRY;
274
- return OBJECT_LIBRARY.find((entry) => entry.kind === kind) ?? null;
192
+ if (kind === EFFECT_ENTRY.kind) return EFFECT_ENTRY;
193
+ return OBJECT_LIBRARY.find((entry) => entry.kind === kind) ?? lightLibraryEntry(kind);
275
194
  }
276
195
 
277
196
  /** Return the authored walkable surface for mocap contact staging. */
@@ -397,9 +316,10 @@ export function createSceneObject(kind, existing = [], placement = {}) {
397
316
  return {
398
317
  id,
399
318
  name,
400
- renderer: kind,
319
+ // A light kind (point-light, spot-light) is a preset of the one light renderer.
320
+ renderer: entry.renderer ?? kind,
401
321
  x: TRANSFORM_LIMITS.x(Number(placement.x) || 0),
402
- y: 0,
322
+ y: entry.placeY ?? 0,
403
323
  z: TRANSFORM_LIMITS.z(Number(placement.z) || 0),
404
324
  rot: TRANSFORM_LIMITS.rot(Number(placement.rot) || 0),
405
325
  rotX: 0,
@@ -420,6 +340,8 @@ export function createSceneObject(kind, existing = [], placement = {}) {
420
340
  footprint: { ...entry.footprint },
421
341
  height: entry.height,
422
342
  supportY: Number.isFinite(entry.supportY) ? entry.supportY : entry.height,
343
+ ...objectLookFields({}, entry),
344
+ ...effectFields({ renderer: kind }),
423
345
  };
424
346
  }
425
347
 
@@ -625,7 +547,8 @@ const TRANSFORM_LIMITS = {
625
547
  /** The patch keys updateSceneObject reads for this object; any other key is
626
548
  * silently ignored there, so callers that must not be silent check against this. */
627
549
  export function objectPatchFields(object) {
628
- const fields = [...Object.keys(TRANSFORM_LIMITS), "name", "color", "hidden", "opacity", "path"];
550
+ const fields = [...Object.keys(TRANSFORM_LIMITS), "name", "color", "hidden", "opacity", "path", "glow"];
551
+ if (object?.renderer === LIGHT_KIND) fields.push("light");
629
552
  if (object?.renderer === CUTOUT_KIND) fields.push("assetId", "sourceAssetId", "matteAssetId", "matteScale", "height", "aspect", "width", "stretch");
630
553
  if (object?.renderer === MESH_KIND) fields.push("clay", "assetId", "height");
631
554
  if (object?.renderer === SCULPT_KIND) fields.push("recipe");
@@ -653,36 +576,6 @@ export function descendantsOf(objects, id) {
653
576
  return out;
654
577
  }
655
578
 
656
- /** True when this record, a parent above it, or a character carrying any of
657
- * those records is hidden. A cycle in parent links ends the walk. When the
658
- * id is in `objects`, that stored record is what the walk reads, so a display
659
- * copy cannot disagree with the authored flag. Nothing in the lists is changed. */
660
- export function isEffectivelyHidden(entity, objects = [], characters = []) {
661
- if (!entity || typeof entity !== "object") return false;
662
- const byId = new Map();
663
- for (const object of objects) {
664
- if (object && typeof object.id === "string") byId.set(object.id, object);
665
- }
666
- const characterHidden = (id) => characters.some((item) => item && typeof item === "object" && item.id === id && item.hidden === true);
667
- const seen = new Set();
668
- let current = entity;
669
- while (current && typeof current === "object") {
670
- const id = typeof current.id === "string" ? current.id : null;
671
- if (id) {
672
- if (seen.has(id)) break;
673
- seen.add(id);
674
- if (byId.has(id)) current = byId.get(id);
675
- }
676
- if (current.hidden === true) return true;
677
- const characterId = current.attach && typeof current.attach.characterId === "string" ? current.attach.characterId : null;
678
- if (characterId && characterHidden(characterId)) return true;
679
- const parentId = typeof current.parent === "string" ? current.parent : null;
680
- if (!parentId) break;
681
- current = byId.get(parentId) ?? null;
682
- }
683
- return false;
684
- }
685
-
686
579
  /** Float dust (6e-17 where 0 belongs) is snapped away, and everything else is
687
580
  * kept to 1e-9: fine enough that hundreds of small gizmo steps cannot drift a
688
581
  * carried child visibly, coarse enough that stored values stay readable. */
@@ -963,6 +856,8 @@ export function updateSceneObject(objects, id, patch) {
963
856
  Object.assign(update, sculptStandingBox(normalized.recipe));
964
857
  }
965
858
  }
859
+ // Light and glow merge over the record's own and are repaired (object-look.js).
860
+ Object.assign(update, objectLookPatch(object, patch));
966
861
  if (!Object.keys(update).length) return object;
967
862
  changed = true;
968
863
  return { ...object, ...update };
@@ -1126,7 +1021,7 @@ export function normalizeSceneObject(record) {
1126
1021
  return {
1127
1022
  id: record.id,
1128
1023
  name: typeof record.name === "string" && record.name ? record.name : entry.label,
1129
- renderer: entry.kind,
1024
+ renderer: entry.renderer ?? entry.kind,
1130
1025
  x: TRANSFORM_LIMITS.x(pick(record.x, 0)),
1131
1026
  y: TRANSFORM_LIMITS.y(pick(record.y, 0)),
1132
1027
  z: TRANSFORM_LIMITS.z(pick(record.z, 0)),
@@ -1191,6 +1086,10 @@ export function normalizeSceneObject(record) {
1191
1086
  height: Math.max(MESH_HEIGHT_MIN, pick(record.height, entry.height)),
1192
1087
  }
1193
1088
  : { footprint: { ...entry.footprint }, height: entry.height, supportY: Number.isFinite(entry.supportY) ? entry.supportY : entry.height }),
1089
+ // A light's light, and a glow on any record (object-look.js).
1090
+ ...objectLookFields(record, entry),
1091
+ // An effect record's emitter (src/effects/effect-record.js).
1092
+ ...effectFields(record),
1194
1093
  };
1195
1094
  }
1196
1095
 
@@ -1315,14 +1214,7 @@ export function screenRotatePatch(start, viewAxis, deltaDeg, snap = ROTATE_SNAP)
1315
1214
  };
1316
1215
  }
1317
1216
 
1318
- /** The object's world size along each axis, for the gizmo and the plan board. */
1319
- export function objectSize(object) {
1320
- return {
1321
- width: (object.footprint?.width ?? 1) * (object.scaleX ?? 1),
1322
- height: (object.height ?? 1) * (object.scaleY ?? 1),
1323
- depth: (object.footprint?.depth ?? 1) * (object.scaleZ ?? 1),
1324
- };
1325
- }
1217
+ export { objectSize };
1326
1218
  /** The tolerance that keeps an edge-abutting footprint from counting as
1327
1219
  * overlap: a strict `<` against `max - EPS` makes a 0.5 - 0.5 touch false. */
1328
1220
  const OVERLAP_EPS = 1e-4;