cozyclay 2.1.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (583) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +12 -30
  3. package/bin/agent/agent-routes.mjs +40 -113
  4. package/bin/agent/agent-runner.mjs +9 -10
  5. package/bin/agent/cliproxy-provider.mjs +135 -0
  6. package/bin/agent/credential-store.mjs +30 -7
  7. package/bin/agent/pi-tools.mjs +4 -1
  8. package/bin/agent/provider-keys.mjs +32 -0
  9. package/bin/agent/provider-login.mjs +110 -0
  10. package/bin/agent/provider-routes.mjs +99 -0
  11. package/bin/agent/providers.mjs +19 -97
  12. package/bin/agent/script-tool.mjs +17 -4
  13. package/bin/agent/studio-motion-reference.mjs +23 -0
  14. package/bin/agent/studio-prompt.mjs +1 -1
  15. package/bin/agent/studio-rebase-text.mjs +32 -0
  16. package/bin/agent/studio-rebase.mjs +26 -39
  17. package/bin/agent/studio-tools.mjs +9 -28
  18. package/bin/agent/tool-arguments.mjs +49 -0
  19. package/bin/agent/turn-frames.mjs +66 -0
  20. package/bin/codex-auth.mjs +9 -2
  21. package/bin/cozyclay.mjs +10 -116
  22. package/bin/live/cli.mjs +22 -6
  23. package/bin/mcp-runtime.mjs +2 -12
  24. package/bin/package-signature.mjs +0 -4
  25. package/dist/app/index.html +7 -6
  26. package/dist/assets/EXRLoader-CvVIvGVE.js +1 -0
  27. package/dist/assets/analytics-C9sUnndd.js +1 -0
  28. package/dist/assets/app-CCh--1Hc.js +5087 -0
  29. package/dist/assets/app-f-fkV5uP.css +1 -0
  30. package/dist/assets/compile-worker-CkxiD6lH.js +5 -0
  31. package/dist/assets/fflate.module-KbGj5-H6.js +5 -0
  32. package/dist/assets/{first-shot-handoff-BlT33CGf.js → first-shot-handoff-CC4xe4HM.js} +1 -1
  33. package/dist/assets/{landing-BSesrCpL.js → landing-DjHD8u7k.js} +1 -1
  34. package/dist/assets/scene-asset-sync-Hx_FzdOH.js +20 -0
  35. package/dist/assets/scene-asset-sync-_S8NQl5B.css +1 -0
  36. package/dist/assets/workflow-C4yKH1p4.js +179 -0
  37. package/dist/cozyclay-package.json +1 -1
  38. package/dist/index.html +3 -3
  39. package/dist/llms.txt +14 -0
  40. package/dist/sw.js +3 -6
  41. package/dist/workflow/index.html +5 -4
  42. package/mcp/LIVE-PROTOCOL.md +7 -47
  43. package/mcp/README.md +16 -26
  44. package/mcp/live-hub.mjs +6 -10
  45. package/mcp/package.json +1 -3
  46. package/mcp/tool-handlers.mjs +4 -168
  47. package/package.json +3 -22
  48. package/src/App.jsx +174 -2639
  49. package/src/agent-progress.js +79 -0
  50. package/src/agent-turn-mark.js +30 -0
  51. package/src/analytics.js +1 -19
  52. package/src/app-context.js +5 -0
  53. package/src/app-stage.jsx +2 -34
  54. package/src/asset-pane.jsx +6 -5
  55. package/src/camera-block.js +21 -0
  56. package/src/camera-direction.js +120 -0
  57. package/src/character-kind.js +3 -9
  58. package/src/command-bus.js +8 -6
  59. package/src/commands/cast.js +5 -34
  60. package/src/commands/elements/character.js +1 -1
  61. package/src/commands/elements/motion.js +1 -6
  62. package/src/commands/elements/object.js +6 -2
  63. package/src/commands/elements/shot.js +4 -1
  64. package/src/commands/elements.js +38 -14
  65. package/src/commands/index.js +3 -1
  66. package/src/commands/kits.js +37 -0
  67. package/src/commands/motion-timing.js +57 -0
  68. package/src/commands/motion.js +9 -152
  69. package/src/commands/motion3-compile.js +65 -0
  70. package/src/commands/motion3.js +207 -0
  71. package/src/commands/objects.js +6 -4
  72. package/src/commands/shot.js +49 -11
  73. package/src/commands/stage.js +14 -1
  74. package/src/commands/view.js +4 -1
  75. package/src/document-mirrors.js +42 -0
  76. package/src/document-store.js +4 -0
  77. package/src/domains/cast.js +19 -194
  78. package/src/domains/motion/motion-range-pin-rules.js +1 -1
  79. package/src/domains/motion/take-source.js +51 -0
  80. package/src/domains/motion.js +60 -1838
  81. package/src/domains/scenes.js +12 -55
  82. package/src/domains/shots.js +2 -2
  83. package/src/domains/stage.js +4 -2
  84. package/src/draft-ghost.js +46 -0
  85. package/src/draft-ghost.jsx +129 -0
  86. package/src/dualview.jsx +4 -3
  87. package/src/effects/EffectsLayer.jsx +112 -0
  88. package/src/effects/effect-meshes.js +232 -0
  89. package/src/effects/effect-record.js +103 -0
  90. package/src/effects/emitter.js +59 -0
  91. package/src/effects/simulate.js +261 -0
  92. package/src/effects/state.js +59 -0
  93. package/src/execution-telemetry.js +2 -3
  94. package/src/first-success-guide.jsx +5 -5
  95. package/src/ghost-set.js +257 -0
  96. package/src/held-object-edit.jsx +113 -0
  97. package/src/held-object-pose.js +175 -0
  98. package/src/hierarchy-model.js +1 -1
  99. package/src/hierarchy-panel.jsx +1 -1
  100. package/src/imported-mesh.jsx +103 -0
  101. package/src/kits/held.js +84 -0
  102. package/src/kits/index.js +70 -0
  103. package/src/kits/smoking.js +57 -0
  104. package/src/light-blockers.js +93 -0
  105. package/src/motion/beat-board-model.js +84 -0
  106. package/src/motion/beat-board.css +124 -0
  107. package/src/motion/beat-board.jsx +59 -0
  108. package/src/motion/center-of-mass.js +66 -0
  109. package/src/motion/fix-collisions.js +850 -0
  110. package/src/motion/physics-review.js +118 -0
  111. package/src/{ardy → motion}/physics-support.js +1 -76
  112. package/src/motion/rig-snapshot.js +22 -0
  113. package/src/motion/take-segments.jsx +140 -0
  114. package/src/{ardy → motion}/timeline-coordinates.js +0 -21
  115. package/src/{ardy → motion}/timeline.css +1 -2
  116. package/src/{ardy → motion}/timeline.jsx +48 -419
  117. package/src/motion/waypoints.js +76 -0
  118. package/src/motion3/beats.js +83 -0
  119. package/src/motion3/body.js +158 -0
  120. package/src/motion3/bones.js +47 -0
  121. package/src/motion3/carried.js +47 -0
  122. package/src/motion3/compile-job.js +75 -0
  123. package/src/motion3/compile-worker.js +10 -0
  124. package/src/motion3/compiler/anchors.js +105 -0
  125. package/src/motion3/compiler/arm-orient.js +128 -0
  126. package/src/motion3/compiler/channels.js +125 -0
  127. package/src/motion3/compiler/compile.js +205 -0
  128. package/src/motion3/compiler/gait-rhythm.js +97 -0
  129. package/src/motion3/compiler/grip.js +171 -0
  130. package/src/motion3/compiler/head-steady.js +48 -0
  131. package/src/motion3/compiler/hold.js +134 -0
  132. package/src/motion3/compiler/idle.js +113 -0
  133. package/src/motion3/compiler/limbs.js +170 -0
  134. package/src/motion3/compiler/math.js +72 -0
  135. package/src/motion3/compiler/pose.js +162 -0
  136. package/src/motion3/compiler/quat.js +49 -0
  137. package/src/motion3/compiler/skeleton.js +74 -0
  138. package/src/motion3/compiler/validate-common.js +32 -0
  139. package/src/motion3/compiler/validate-goals.js +118 -0
  140. package/src/motion3/compiler/validate-objects.js +143 -0
  141. package/src/motion3/compiler/validate-parts.js +116 -0
  142. package/src/motion3/compiler/validate.js +122 -0
  143. package/src/motion3/compiler/vocab.js +73 -0
  144. package/src/motion3/compiler/walk-pelvis.js +275 -0
  145. package/src/motion3/compiler/walk.js +207 -0
  146. package/src/motion3/compiler/wrist-clamp.js +32 -0
  147. package/src/motion3/delta.js +47 -0
  148. package/src/motion3/describe.js +174 -0
  149. package/src/motion3/docs.js +27 -0
  150. package/src/motion3/edit-carry.js +21 -0
  151. package/src/motion3/fix/ease.js +16 -0
  152. package/src/motion3/fix/ground.js +37 -0
  153. package/src/motion3/fix/index.js +52 -0
  154. package/src/motion3/fix/push.js +71 -0
  155. package/src/motion3/fix/reach.js +46 -0
  156. package/src/motion3/fix/retime.js +79 -0
  157. package/src/motion3/fix/self.js +211 -0
  158. package/src/motion3/fix/slide.js +70 -0
  159. package/src/motion3/fix/turn.js +51 -0
  160. package/src/motion3/follow.js +39 -0
  161. package/src/motion3/free-motion.js +173 -0
  162. package/src/motion3/grip-choice.js +130 -0
  163. package/src/motion3/hold-plan.js +157 -0
  164. package/src/motion3/json-patch.js +77 -0
  165. package/src/motion3/key-aim.js +105 -0
  166. package/src/motion3/key-edit.js +82 -0
  167. package/src/motion3/lenient.js +127 -0
  168. package/src/motion3/object-keys.js +236 -0
  169. package/src/motion3/object-pose.js +182 -0
  170. package/src/motion3/object-tracks.js +12 -0
  171. package/src/motion3/pipeline.js +102 -0
  172. package/src/motion3/playback.js +51 -0
  173. package/src/motion3/probes.js +91 -0
  174. package/src/motion3/qa/body-checks.js +34 -0
  175. package/src/motion3/qa/check-names.js +14 -0
  176. package/src/motion3/qa/checks.js +121 -0
  177. package/src/motion3/qa/contact-checks.js +51 -0
  178. package/src/motion3/qa/explain.js +80 -0
  179. package/src/motion3/qa/fmt.js +5 -0
  180. package/src/motion3/qa/foot-checks.js +72 -0
  181. package/src/motion3/qa/geometry.js +77 -0
  182. package/src/motion3/qa/goals.js +191 -0
  183. package/src/motion3/qa/ground.js +60 -0
  184. package/src/motion3/qa/hints-contact.js +179 -0
  185. package/src/motion3/qa/hints-goal.js +160 -0
  186. package/src/motion3/qa/hints-knee.js +24 -0
  187. package/src/motion3/qa/hints-motion.js +209 -0
  188. package/src/motion3/qa/hints-object.js +91 -0
  189. package/src/motion3/qa/index.js +42 -0
  190. package/src/motion3/qa/key-checks.js +82 -0
  191. package/src/motion3/qa/near.js +96 -0
  192. package/src/motion3/qa/object-checks.js +135 -0
  193. package/src/motion3/qa/patch.js +161 -0
  194. package/src/motion3/qa/severity.js +38 -0
  195. package/src/motion3/qa/spec-checks.js +65 -0
  196. package/src/motion3/qa/style.js +218 -0
  197. package/src/motion3/qa/wrist-checks.js +69 -0
  198. package/src/motion3/qa/wrist-obs.js +60 -0
  199. package/src/motion3/refs.js +120 -0
  200. package/src/motion3/repair-tune.js +121 -0
  201. package/src/motion3/repair.js +83 -0
  202. package/src/motion3/report.js +67 -0
  203. package/src/motion3/rigs/index.js +14 -0
  204. package/src/motion3/rigs/x-bot-tpose-body.json +1 -0
  205. package/src/motion3/rigs/x-bot-tpose.json +1 -0
  206. package/src/motion3/rigs/y-bot-tpose-body.json +1 -0
  207. package/src/motion3/rigs/y-bot-tpose.json +1 -0
  208. package/src/motion3/scene.js +60 -0
  209. package/src/motion3/schema/brief.js +89 -0
  210. package/src/motion3/schema/fields.js +44 -0
  211. package/src/motion3/schema/spec-examples.js +46 -0
  212. package/src/motion3/schema/topics.js +111 -0
  213. package/src/motion3/schema/types.js +147 -0
  214. package/src/motion3/search.js +117 -0
  215. package/src/motion3/second-hand.js +112 -0
  216. package/src/motion3/skills/basic.js +82 -0
  217. package/src/motion3/skills/book.js +76 -0
  218. package/src/motion3/skills/car.js +145 -0
  219. package/src/motion3/skills/frames-auto.js +29 -0
  220. package/src/motion3/skills/index.js +83 -0
  221. package/src/motion3/skills/info.js +65 -0
  222. package/src/motion3/skills/smoke-kit.js +109 -0
  223. package/src/motion3/skills/smoke.js +160 -0
  224. package/src/motion3/spec-store.js +59 -0
  225. package/src/motion3/take-edit.js +155 -0
  226. package/src/motion3/take.js +63 -0
  227. package/src/motion3/timeline.js +127 -0
  228. package/src/motion3/views.js +121 -0
  229. package/src/motion3/walk-plan.js +119 -0
  230. package/src/object-catalog.jsx +7 -1
  231. package/src/object-colors.js +98 -0
  232. package/src/object-gizmo-parts.js +115 -0
  233. package/src/object-gizmo.jsx +37 -122
  234. package/src/object-glow.js +139 -0
  235. package/src/object-hidden.js +33 -0
  236. package/src/object-look.js +196 -0
  237. package/src/object-size.js +10 -0
  238. package/src/object-travel.js +71 -6
  239. package/src/panels/CameraPanel.jsx +7 -7
  240. package/src/panels/HdrEnvironmentControls.jsx +59 -0
  241. package/src/panels/LightPanel.jsx +25 -4
  242. package/src/panels/ObjectLookPanel.jsx +88 -0
  243. package/src/panels/PoseImagePanel.jsx +1 -1
  244. package/src/panels/PosePanel.jsx +2 -17
  245. package/src/panels/RigControlPanel.jsx +5 -53
  246. package/src/panels/SubjectBox.jsx +4 -1
  247. package/src/panels/details.css +14 -0
  248. package/src/posestudio.jsx +4 -27
  249. package/src/project-browser.jsx +3 -4
  250. package/src/project-resources.js +18 -31
  251. package/src/project.js +13 -104
  252. package/src/props.jsx +25 -280
  253. package/src/rail-dot-drag.js +23 -0
  254. package/src/render-passes.js +10 -0
  255. package/src/resource-status.jsx +1 -0
  256. package/src/result-modal.jsx +4 -19
  257. package/src/room.jsx +20 -12
  258. package/src/scene-assets.js +8 -6
  259. package/src/scene-light-place.js +72 -0
  260. package/src/scene-lights.jsx +148 -0
  261. package/src/scene-objects.js +31 -139
  262. package/src/scenes.js +15 -105
  263. package/src/semantic-edit.js +2 -5
  264. package/src/set-pieces.jsx +183 -0
  265. package/src/settings-menu.jsx +3 -11
  266. package/src/shell/BottomDock.jsx +11 -53
  267. package/src/shell/CompositionGuideButton.jsx +20 -0
  268. package/src/shell/DetailsSlot.jsx +16 -200
  269. package/src/shell/GhostsMenuGroup.jsx +74 -0
  270. package/src/shell/LibrarySlot.jsx +1 -3
  271. package/src/shell/MenuBar.jsx +3 -3
  272. package/src/shell/PreferencesDialog.jsx +15 -264
  273. package/src/shell/PreferencesSlot.jsx +2 -8
  274. package/src/shell/ShotPreviewHeader.jsx +17 -0
  275. package/src/shell/TopBar.jsx +5 -83
  276. package/src/shell/ViewportToolbar.jsx +23 -93
  277. package/src/shell/agent-glass.css +19 -6
  278. package/src/shell/details-selection.js +42 -0
  279. package/src/shell/ghosts-menu.css +22 -0
  280. package/src/shell/glass-regions.css +29 -3
  281. package/src/shell/preferences.css +6 -1
  282. package/src/shell/viewport.css +12 -0
  283. package/src/shot-authoring.js +4 -7
  284. package/src/soft-shadows.js +37 -0
  285. package/src/stage-attach-frames.js +12 -12
  286. package/src/stage-canvas-rigs.jsx +3 -3
  287. package/src/stage-capture.jsx +42 -8
  288. package/src/stage-characters.jsx +22 -216
  289. package/src/stage-editor-gizmos.jsx +6 -6
  290. package/src/stage-environment.js +93 -0
  291. package/src/stage-hdr-builtins.js +107 -0
  292. package/src/stage-hdr-image.js +116 -0
  293. package/src/stage-hdr-library.js +70 -0
  294. package/src/stage-hdr-map.js +94 -0
  295. package/src/stage-hdr.js +130 -0
  296. package/src/stage-lighting.js +149 -0
  297. package/src/stage-occlusion.js +210 -0
  298. package/src/stage-sky.jsx +159 -0
  299. package/src/stage-startup-defaults.js +5 -9
  300. package/src/studio-actions.js +12 -14
  301. package/src/studio-agent-commands.js +46 -16
  302. package/src/studio-agent-motion.js +9 -9
  303. package/src/studio-agent-protocol.js +46 -73
  304. package/src/studio-app-binding.js +29 -45
  305. package/src/studio-asset-list.js +17 -0
  306. package/src/studio-contact-sheet.js +37 -5
  307. package/src/studio-elements.js +27 -7
  308. package/src/studio-entity-bounds.js +18 -0
  309. package/src/studio-error-codes.js +8 -0
  310. package/src/studio-export-framing.js +152 -0
  311. package/src/studio-lighting-check.js +103 -0
  312. package/src/studio-motion-visual.js +150 -0
  313. package/src/studio-schema-errors.js +80 -0
  314. package/src/studio-support-gap.js +74 -0
  315. package/src/styles.css +1 -19
  316. package/src/timeline-defaults.js +5 -0
  317. package/src/timeline-extent.js +8 -20
  318. package/src/tutorial-walk.js +17 -0
  319. package/src/ui-scrub.js +1 -1
  320. package/src/use-shot-camera-tracks.js +12 -0
  321. package/src/view-ghosts.js +97 -0
  322. package/src/view-ghosts.jsx +49 -0
  323. package/src/workflow/AgentPanel.jsx +47 -166
  324. package/src/workflow/AgentProgressCard.jsx +107 -0
  325. package/src/workflow/AgentReplyText.jsx +37 -0
  326. package/src/workflow/AgentTurnSteps.jsx +84 -0
  327. package/src/workflow/LoginPanel.jsx +140 -0
  328. package/src/workflow/WorkflowBuilder.jsx +7 -23
  329. package/src/workflow/agent-client.js +54 -51
  330. package/src/workflow/agent-panel.css +71 -1
  331. package/src/workflow/agent-progress-card.css +167 -0
  332. package/src/workflow/agent-progress-view.js +47 -0
  333. package/src/workflow/agent-turn-steps.css +113 -0
  334. package/src/workflow/agent-turn.js +191 -0
  335. package/src/workflow/login-client.js +34 -0
  336. package/src/workflow/motion-input.js +18 -31
  337. package/src/workflow/node-schema.js +1 -1
  338. package/src/workflow/reply-markdown.js +69 -0
  339. package/src/workflow/scene-asset-sync.js +8 -8
  340. package/tools/agent-session-stats.mjs +61 -0
  341. package/tools/dev-full.mjs +5 -56
  342. package/tools/eslint-baseline.json +12 -13
  343. package/tools/headless-studio.mjs +85 -0
  344. package/tools/knip-baseline.json +0 -179
  345. package/tools/loc-baseline.json +33 -83
  346. package/tools/motion3/extract-body.mjs +103 -0
  347. package/tools/motion3/extract-rig.mjs +85 -0
  348. package/tools/process-supervisor.mjs +0 -88
  349. package/tools/run-tests.mjs +9 -124
  350. package/bin/agent/motion-runtime.mjs +0 -76
  351. package/dist/ardy/cskel27-rest.json +0 -557
  352. package/dist/assets/analytics-B41FmPYI.js +0 -1
  353. package/dist/assets/app-Do-Og5Pu.css +0 -1
  354. package/dist/assets/app-DwP-tl2x.js +0 -4920
  355. package/dist/assets/config-Bx7pYsD9.js +0 -1
  356. package/dist/assets/config-DeoFTYzb.css +0 -1
  357. package/dist/assets/demo-Dt0qZfS7.js +0 -1
  358. package/dist/assets/scene-asset-sync-BCWDJB1d.js +0 -19
  359. package/dist/assets/scene-asset-sync-DjJ_cJ2R.css +0 -1
  360. package/dist/assets/ticket-jqMhqL88.js +0 -1
  361. package/dist/assets/workflow-Br-K7Srs.js +0 -179
  362. package/dist/demo/walk-then-stop.npz +0 -0
  363. package/mcp/ardy-prompts.mjs +0 -218
  364. package/src/ardy/auto-fix-panel.css +0 -108
  365. package/src/ardy/auto-physics.js +0 -1194
  366. package/src/ardy/client.js +0 -153
  367. package/src/ardy/convert.js +0 -313
  368. package/src/ardy/cskel27-neutral.js +0 -39
  369. package/src/ardy/cskel27.js +0 -77
  370. package/src/ardy/export.js +0 -165
  371. package/src/ardy/fix-collisions.js +0 -2257
  372. package/src/ardy/foot-lock.js +0 -230
  373. package/src/ardy/motion-calibration.js +0 -118
  374. package/src/ardy/motion-edit.js +0 -213
  375. package/src/ardy/motion-url.js +0 -55
  376. package/src/ardy/mp4-duration.js +0 -102
  377. package/src/ardy/npz.js +0 -595
  378. package/src/ardy/physics-panel.css +0 -21
  379. package/src/ardy/physics-panel.jsx +0 -89
  380. package/src/ardy/physics-refine.js +0 -111
  381. package/src/ardy/physics-review.js +0 -567
  382. package/src/ardy/physics-temporal.js +0 -54
  383. package/src/ardy/platform-fit-panel.css +0 -64
  384. package/src/ardy/platform-fit-panel.jsx +0 -41
  385. package/src/ardy/platform-fit.js +0 -326
  386. package/src/ardy/playback.js +0 -711
  387. package/src/ardy/pose-pin.js +0 -97
  388. package/src/ardy/prompt-clips.js +0 -30
  389. package/src/ardy/retime.js +0 -211
  390. package/src/ardy/root-drop.js +0 -369
  391. package/src/ardy/timeline-resize.js +0 -13
  392. package/src/ardy/to-cskel27.js +0 -225
  393. package/src/ardy/trim.js +0 -33
  394. package/src/ardy/waypoints.js +0 -369
  395. package/src/ardy-bridge-contract.js +0 -281
  396. package/src/domains/motion/motion-video-capture.js +0 -664
  397. package/src/i2v-motion-client.js +0 -77
  398. package/src/i2v-motion-studio.jsx +0 -180
  399. package/src/line-edit.js +0 -1348
  400. package/src/motion/generation.js +0 -105
  401. package/src/motion-readiness-ui.jsx +0 -67
  402. package/src/motion-readiness.js +0 -40
  403. package/src/motion-resources.js +0 -177
  404. package/src/motion-store.js +0 -65
  405. package/src/motion-trail.js +0 -601
  406. package/src/multimodel-ingest.js +0 -394
  407. package/src/panels/PromptBlocksPanel.jsx +0 -444
  408. package/src/panels/VideoCapturePanel.jsx +0 -181
  409. package/src/pose-extract/browser.js +0 -42
  410. package/src/pose-extract/detector.js +0 -100
  411. package/src/pose-extract/fit.js +0 -227
  412. package/src/pose-extract/image-frame.js +0 -51
  413. package/src/pose-extract/index.js +0 -58
  414. package/src/pose-extract/keyframes.js +0 -74
  415. package/src/pose-extract/landmarks.js +0 -162
  416. package/src/pose-extract/mirror.js +0 -153
  417. package/src/pose-extract/take.js +0 -131
  418. package/src/pose-extract/video-frames.js +0 -91
  419. package/src/take-recipe.js +0 -165
  420. package/src/trail-key-conflicts.js +0 -39
  421. package/src/trail-pick.js +0 -59
  422. package/src/use-i2v-capture-modal.js +0 -15
  423. package/tools/ardy/BRIDGE.md +0 -37
  424. package/tools/ardy/GVHMR-NPZ.md +0 -68
  425. package/tools/ardy/README.md +0 -124
  426. package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
  427. package/tools/ardy/artifacts.mjs +0 -34
  428. package/tools/ardy/bridge.mjs +0 -1732
  429. package/tools/ardy/bvh-cskel27.mjs +0 -1265
  430. package/tools/ardy/cclay_constrained_generate.py +0 -561
  431. package/tools/ardy/cclay_gvhmr_worker.py +0 -264
  432. package/tools/ardy/cclay_motion_edit.py +0 -510
  433. package/tools/ardy/cclay_sequence_generate.py +0 -705
  434. package/tools/ardy/cclay_worker.py +0 -304
  435. package/tools/ardy/dump-npz.py +0 -205
  436. package/tools/ardy/extract-rest.mjs +0 -299
  437. package/tools/ardy/extract.mjs +0 -507
  438. package/tools/ardy/footage.mjs +0 -467
  439. package/tools/ardy/gvhmr-floor.mjs +0 -102
  440. package/tools/ardy/gvhmr-to-cskel27.mjs +0 -21
  441. package/tools/ardy/gvhmr_fastpath.py +0 -168
  442. package/tools/ardy/gvhmr_trajectory.py +0 -240
  443. package/tools/ardy/merge-text-encoder.py +0 -160
  444. package/tools/ardy/mocap-metrics.mjs +0 -200
  445. package/tools/ardy/mocap-quality-gate.mjs +0 -110
  446. package/tools/ardy/motion-stabilize.mjs +0 -348
  447. package/tools/ardy/npz.mjs +0 -414
  448. package/tools/ardy/pose-to-npz.mjs +0 -106
  449. package/tools/ardy/prompt-limits.mjs +0 -5
  450. package/tools/ardy/run-edit-on-box.sh +0 -73
  451. package/tools/ardy/run-local.mjs +0 -233
  452. package/tools/ardy/run-on-box.sh +0 -593
  453. package/tools/ardy/run-sequence-on-box.sh +0 -177
  454. package/tools/ardy/runners/gvhmr-worker.mjs +0 -206
  455. package/tools/ardy/runners/index.mjs +0 -18
  456. package/tools/ardy/runners/local.mjs +0 -455
  457. package/tools/ardy/runners/proc.mjs +0 -125
  458. package/tools/ardy/runners/remote.mjs +0 -252
  459. package/tools/ardy/setup-local.mjs +0 -189
  460. package/tools/ardy/setup-text-encoder-on-box.sh +0 -93
  461. package/tools/ardy/setup-text-encoder.py +0 -306
  462. package/tools/ardy/smpl-cskel27.mjs +0 -178
  463. package/tools/ardy/visual-qa.mjs +0 -196
  464. package/tools/ardy/vq-car.mjs +0 -52
  465. package/tools/bench/EXP3.md +0 -61
  466. package/tools/bench/cclay_bench_extract_incam.py +0 -125
  467. package/tools/bench/cclay_bench_extract_obs.py +0 -204
  468. package/tools/bench/cclay_bench_runner.py +0 -42
  469. package/tools/bench/cube-contact.mjs +0 -162
  470. package/tools/bench/exp3.mjs +0 -152
  471. package/tools/bench/extract-bench-lib.mjs +0 -216
  472. package/tools/bench/extract-bench.mjs +0 -302
  473. package/tools/bench/fal-generate.mjs +0 -88
  474. package/tools/bench/fit/README.md +0 -189
  475. package/tools/bench/fit/camera.mjs +0 -59
  476. package/tools/bench/fit/contact.mjs +0 -419
  477. package/tools/bench/fit/footlock.mjs +0 -234
  478. package/tools/bench/fit/motion.mjs +0 -85
  479. package/tools/bench/fit/pin.mjs +0 -27
  480. package/tools/bench/fit/remote.mjs +0 -105
  481. package/tools/bench/fit-bench.mjs +0 -126
  482. package/tools/bench/fit-sanity.mjs +0 -45
  483. package/tools/bench/metrics.mjs +0 -313
  484. package/tools/bench/obs/depth.mjs +0 -258
  485. package/tools/bench/obs/extrinsics.mjs +0 -175
  486. package/tools/bench/obs/fit_mannequin_betas.py +0 -340
  487. package/tools/bench/obs/ground.mjs +0 -294
  488. package/tools/bench/obs/heading.mjs +0 -151
  489. package/tools/bench/obs/ladder.mjs +0 -555
  490. package/tools/bench/obs/mannequin-betas.json +0 -124
  491. package/tools/bench/obs/remote.mjs +0 -83
  492. package/tools/bench/obs/rest_joints.py +0 -52
  493. package/tools/bench/obs/ybot-targets.mjs +0 -49
  494. package/tools/bench/obs-bench.mjs +0 -520
  495. package/tools/bench/score.mjs +0 -343
  496. package/tools/bench/summarize.mjs +0 -86
  497. package/tools/demo-worker/README.md +0 -137
  498. package/tools/demo-worker/api-client.mjs +0 -233
  499. package/tools/demo-worker/generate.mjs +0 -301
  500. package/tools/demo-worker/index.mjs +0 -466
  501. package/tools/demo-worker/sign-core.mjs +0 -112
  502. package/tools/demo-worker/sign.mjs +0 -161
  503. package/tools/gt-render/browser.mjs +0 -159
  504. package/tools/gt-render/camera-math.mjs +0 -276
  505. package/tools/gt-render/page.mjs +0 -267
  506. package/tools/gt-render/render.mjs +0 -514
  507. package/tools/gt-render/scene-box.mjs +0 -31
  508. package/tools/gt-render/take-transform.mjs +0 -87
  509. package/tools/kimodo/compare-seam.mjs +0 -108
  510. package/tools/kimodo/constraints.mjs +0 -244
  511. package/tools/kimodo/edit.mjs +0 -259
  512. package/tools/kimodo/effector-constraints.mjs +0 -200
  513. package/tools/kimodo/generate.mjs +0 -646
  514. package/tools/kimodo/local-output.mjs +0 -265
  515. package/tools/kimodo/measure-pose.mjs +0 -65
  516. package/tools/kimodo/measure-preserve.mjs +0 -899
  517. package/tools/kimodo/measure-waypoints.mjs +0 -55
  518. package/tools/kimodo/mlx-generate.py +0 -111
  519. package/tools/kimodo/pose-constraints.mjs +0 -248
  520. package/tools/kimodo/preserve-mask.mjs +0 -543
  521. package/tools/kimodo/read-npz.mjs +0 -189
  522. package/tools/kimodo/run-edit-on-box.mjs +0 -155
  523. package/tools/kimodo/run-sequence-on-box.mjs +0 -178
  524. package/tools/kimodo/runner.mjs +0 -284
  525. package/tools/kimodo/seam-batch.mjs +0 -77
  526. package/tools/kimodo/setup-local.mjs +0 -30
  527. package/tools/kimodo/setup-on-box.sh +0 -339
  528. package/tools/kimodo/soma77-to-cskel27.mjs +0 -305
  529. package/tools/morphgs/assets/gen_truth.py +0 -39
  530. package/tools/morphgs/assets/mesh_ori_rig.txt +0 -27
  531. package/tools/morphgs/assets/playback-check.mjs +0 -30
  532. package/tools/morphgs/demo-gate.sh +0 -26
  533. package/tools/morphgs/fbx2morphgs.mjs +0 -68
  534. package/tools/morphgs/morphgs-to-cskel27.mjs +0 -105
  535. package/tools/morphgs/patches/preprocess_src-none-mode.patch +0 -76
  536. package/tools/morphgs/setup-on-cluster.sh +0 -126
  537. package/tools/projflow/driver.py +0 -1069
  538. package/tools/projflow/generate.mjs +0 -805
  539. package/tools/projflow/hml22-to-cskel27.mjs +0 -735
  540. package/tools/projflow/line-edit-job.mjs +0 -547
  541. package/tools/projflow/replay.mjs +0 -452
  542. package/tools/projflow/runner.mjs +0 -269
  543. package/tools/projflow/service.mjs +0 -742
  544. package/tools/track/DESIGN.md +0 -513
  545. package/tools/track/backfill-provenance.mjs +0 -116
  546. package/tools/track/budget.mjs +0 -8
  547. package/tools/track/check-rig.mjs +0 -209
  548. package/tools/track/diagnostics.schema.json +0 -60
  549. package/tools/track/export-rig.mjs +0 -256
  550. package/tools/track/fallback.mjs +0 -7
  551. package/tools/track/fk-parity-fixture.mjs +0 -161
  552. package/tools/track/gate.mjs +0 -349
  553. package/tools/track/masks.mjs +0 -318
  554. package/tools/track/metrics.mjs +0 -201
  555. package/tools/track/publish-obs.mjs +0 -108
  556. package/tools/track/py/check_env.py +0 -61
  557. package/tools/track/py/eval_lr.py +0 -229
  558. package/tools/track/py/lr_viterbi.py +0 -294
  559. package/tools/track/py/masks.py +0 -405
  560. package/tools/track/py/objective.py +0 -382
  561. package/tools/track/py/rig.py +0 -508
  562. package/tools/track/py/scene.py +0 -326
  563. package/tools/track/py/test_joint_indices.py +0 -123
  564. package/tools/track/py/test_lr_viterbi.py +0 -195
  565. package/tools/track/py/test_masks.py +0 -229
  566. package/tools/track/py/test_rig.py +0 -288
  567. package/tools/track/py/test_scene.py +0 -354
  568. package/tools/track/py/test_track.py +0 -328
  569. package/tools/track/py/track.py +0 -411
  570. package/tools/track/remote.mjs +0 -140
  571. package/tools/track/rig-dump.mjs +0 -227
  572. package/tools/track/run-box-tests.mjs +0 -14
  573. package/tools/track/run-box.mjs +0 -168
  574. package/tools/track/setup-box.sh +0 -59
  575. package/tools/track/study-2d.mjs +0 -728
  576. /package/src/{ardy → motion}/collision-blockers.js +0 -0
  577. /package/src/{ardy → motion}/ground.js +0 -0
  578. /package/src/{ardy → motion}/ik-drag.js +0 -0
  579. /package/src/{ardy → motion}/ik-key-json.js +0 -0
  580. /package/src/{ardy → motion}/ik.js +0 -0
  581. /package/src/{ardy → motion}/physics-surface.js +0 -0
  582. /package/src/{ardy → motion}/playback-clock.js +0 -0
  583. /package/src/{ardy → motion}/range-pin.js +0 -0
@@ -25,7 +25,9 @@ export function CaptureRig({ apiRef, camRef, width = CAPTURE_W, height = CAPTURE
25
25
  useEffect(() => {
26
26
  // Each attempt owns its target independently of the live output size.
27
27
  // Resizing the editor cannot dispose an in-flight export's resources.
28
- function createExportCapture({ width, height }) {
28
+ // `camera` makes the camera of each render: a clone of the shot camera unless a caller
29
+ // (renderView) brings its own; the shot camera itself is never moved.
30
+ function createExportCapture({ width, height }, camera = () => camRef.current?.clone()) {
29
31
  const target = new THREE.WebGLRenderTarget(width, height, {
30
32
  colorSpace: THREE.SRGBColorSpace,
31
33
  samples: 4,
@@ -35,9 +37,8 @@ export function CaptureRig({ apiRef, camRef, width = CAPTURE_W, height = CAPTURE
35
37
  scene,
36
38
  dispose: () => target.dispose(),
37
39
  render() {
38
- const source = camRef.current;
39
- if (!source) return null;
40
- const cam = source.clone();
40
+ const cam = camera();
41
+ if (!cam) return null;
41
42
  // the transform gizmo is UI: it never reaches an exported frame
42
43
  cam.layers.disable(GIZMO_LAYER);
43
44
  // QA hook: the layer mask the export camera actually renders
@@ -53,13 +54,27 @@ export function CaptureRig({ apiRef, camRef, width = CAPTURE_W, height = CAPTURE
53
54
  // exported frame wants the opposite: the horizon IS the vanishing
54
55
  // point, and without it the floor has no far edge to read depth
55
56
  // against. Push the falloff back for this draw only, then restore
56
- // it so the viewport is untouched.
57
+ // it so the viewport is untouched. Haze (stage.lighting.fog, set by
58
+ // StageAtmosphere) is part of the look, so it brings its own range.
57
59
  const fog = scene.fog;
58
60
  const fogNear = fog?.near;
59
61
  const fogFar = fog?.far;
62
+ const look = scene.userData.stageCapture;
60
63
  if (fog) {
61
- fog.near = CAPTURE_FOG_NEAR;
62
- fog.far = CAPTURE_FOG_FAR;
64
+ fog.near = look?.fog?.near ?? CAPTURE_FOG_NEAR;
65
+ fog.far = look?.fog?.far ?? CAPTURE_FOG_FAR;
66
+ }
67
+ // three tone-maps only the canvas, never a render target, so the
68
+ // viewport's exposure reaches an export as light: every light, and
69
+ // the environment light, is scaled by it for this draw and put back after.
70
+ const scaled = [], environmentIntensity = scene.environmentIntensity, backgroundIntensity = scene.backgroundIntensity, fogColor = fog?.color.clone();
71
+ if (look && look.exposure !== 1) scene.traverse((node) => { if (node.isLight) scaled.push([node, node.intensity]); });
72
+ for (const [light, intensity] of scaled) light.intensity = intensity * look.exposure;
73
+ if (look && look.exposure !== 1) scene.environmentIntensity = environmentIntensity * look.exposure;
74
+ // An HDR backdrop (stage.lighting.hdrBackground) is light too, and so is the horizon the deck fades into.
75
+ if (look?.backdrop && look.exposure !== 1) {
76
+ scene.backgroundIntensity = backgroundIntensity * look.exposure;
77
+ fog?.color.multiplyScalar(look.exposure);
63
78
  }
64
79
  try {
65
80
  gl.setRenderTarget(target);
@@ -71,12 +86,31 @@ export function CaptureRig({ apiRef, camRef, width = CAPTURE_W, height = CAPTURE
71
86
  fog.near = fogNear;
72
87
  fog.far = fogFar;
73
88
  }
89
+ for (const [light, intensity] of scaled) light.intensity = intensity;
90
+ scene.environmentIntensity = environmentIntensity;
91
+ scene.backgroundIntensity = backgroundIntensity;
92
+ if (fogColor) fog.color.copy(fogColor);
74
93
  }
75
94
  return buffer;
76
95
  },
77
96
  };
78
97
  }
79
- const api = { ...createExportCapture({ width, height }), createExportCapture };
98
+ // verify_result's own views (src/motion3/views.js): one read-back, rows bottom-up, from a
99
+ // camera at eye with quaternion [x, y, z, w] and a vertical fov, on a target of its own size.
100
+ function renderView({ eye, quaternion, fovDeg, output }) {
101
+ const own = createExportCapture(output, () => {
102
+ const source = camRef.current;
103
+ if (!source) return null;
104
+ const cam = source.clone();
105
+ cam.clearViewOffset();
106
+ cam.position.set(...eye);
107
+ cam.quaternion.set(...quaternion);
108
+ cam.fov = fovDeg;
109
+ return cam;
110
+ });
111
+ try { return own.render(); } finally { own.dispose(); }
112
+ }
113
+ const api = { ...createExportCapture({ width, height }), createExportCapture, renderView };
80
114
  apiRef.current = api;
81
115
  if (width === MCP_CAPTURE_W && height === MCP_CAPTURE_H) {
82
116
  window.__cozyclayMcpCaptureReady = true;
@@ -1,14 +1,12 @@
1
- import { memo, useEffect, useMemo, useRef, useState } from "react";
1
+ import { memo, useEffect, useLayoutEffect, useMemo, useRef } from "react";
2
+ import { useFBX } from "@react-three/drei";
2
3
  import { useThree } from "@react-three/fiber";
3
- import { Line, useFBX } from "@react-three/drei";
4
4
  import * as THREE from "three";
5
5
  import { SkeletonUtils } from "three/examples/jsm/Addons.js";
6
6
  import { ProxyFigure } from "./proxy-figure.jsx";
7
7
  import { isProxyFigure } from "./scenes.js";
8
8
  import { applyPartColours } from "./part-colours.js";
9
9
  import { applyHipsOffset, applyPose, primeBindPose } from "./poses.js";
10
- import { TRAIL_TRACKS, falloffWeight, jointTrailPoints, trailEditRange } from "./motion-trail.js";
11
- import { pickTrailPoint } from "./trail-pick.js";
12
10
  import { REST_BONES } from "./stage-startup-defaults.js";
13
11
 
14
12
  // Memoized: unchanged cast members skip re-rendering on every playhead tick.
@@ -28,19 +26,11 @@ const RiggedCharacter = memo(function RiggedCharacter({ url, position, rot, tint
28
26
  // inside rig space: playback.js derives prep.scale from the bind pose,
29
27
  // so a scaled bone hierarchy would be measured as a different rig.
30
28
  clone.scale.setScalar(0.01 * scale);
31
- // The joint shells (Alpha_Joints / Beta_Joints — elbows, knees, the
32
- // exoskeleton bands) render in a darkened shade of the body tint so
33
- // the segments read as separate pieces.
34
- // The shipped clay is intentionally pale, but the stage deck is pale too.
35
- // Lift neither with emissive nor a second light; a small value drop keeps
36
- // the same authored hue while restoring silhouette contrast in the editor.
37
- const bodyTint = new THREE.Color(tint).multiplyScalar(0.9);
38
- const jointTint = bodyTint.clone().multiplyScalar(0.62);
39
29
  clone.traverse((child) => {
40
30
  if (child.isMesh) {
41
- // warm clay reads as a maquette; cold grey reads as a broken render
31
+ // warm clay reads as a maquette; cold grey reads as a broken render.
32
+ // Its colour is the tint, set below in place.
42
33
  child.material = new THREE.MeshStandardMaterial({
43
- color: /_Joints$/i.test(child.name) ? jointTint : bodyTint,
44
34
  // A slightly tighter highlight gives the clay enough form to read
45
35
  // at blocking distance without turning it into shiny plastic.
46
36
  roughness: 0.66,
@@ -59,7 +49,24 @@ const RiggedCharacter = memo(function RiggedCharacter({ url, position, rot, tint
59
49
  // below runs immediately after and would otherwise be baked into "rest".
60
50
  primeBindPose(clone);
61
51
  return clone;
62
- }, [fbx, tint, partColoursEnabled, partColoursMode]);
52
+ }, [fbx, partColoursEnabled, partColoursMode]);
53
+
54
+ // The tint recolours the clay in place, before the next frame. It must not rebuild the clone: a colour
55
+ // picker streams a value every drag frame, and a new clone re-binds the rig (playback, IK, contact radii).
56
+ // The joint shells (Alpha_Joints / Beta_Joints — elbows, knees, the exoskeleton bands) take a darkened
57
+ // shade of the body tint so the segments read as separate pieces. The shipped clay is intentionally pale,
58
+ // but the stage deck is pale too: a small value drop keeps the authored hue and restores silhouette
59
+ // contrast in the editor. Part colours paint the vertices themselves and keep their white material.
60
+ const invalidate = useThree((state) => state.invalidate);
61
+ useLayoutEffect(() => {
62
+ if (partColoursEnabled) return;
63
+ const bodyTint = new THREE.Color(tint).multiplyScalar(0.9);
64
+ const jointTint = bodyTint.clone().multiplyScalar(0.62);
65
+ model.traverse((child) => {
66
+ if (child.isMesh) child.material.color.copy(/_Joints$/i.test(child.name) ? jointTint : bodyTint);
67
+ });
68
+ invalidate();
69
+ }, [model, tint, partColoursEnabled, invalidate]);
63
70
 
64
71
  // A new stature (a fresh extraction on this character) must not rebuild the
65
72
  // clone — that would drop the rig the playback effects hold. Only the world
@@ -99,204 +106,3 @@ const RiggedCharacter = memo(function RiggedCharacter({ url, position, rot, tint
99
106
  </group>
100
107
  );
101
108
  });
102
-
103
- /* ------------------------- IK-mode motion trails --------------------------
104
- * World-space trajectory polylines of the loaded take: the selected track,
105
- * or all tracks when the user asks for an overview. The whole clip is
106
- * drawn as a faint line; while a grab is active the falloff window is
107
- * re-drawn on top as a bright highlight. Grabbing any point of a line starts
108
- * a drag on a camera-facing plane through the grab point; the caller deforms
109
- * the take (motion-trail.js falloff math) so the preview updates live. */
110
- export const MotionTrails = memo(function MotionTrails({ motion, rig = null, baseY, charScale, ikFocus, activeTrackId = null, falloffFrames, playheadFrame, pendingEdit, enabled, visible = true, onDragStart, onDragPreview, onDragEnd }) {
111
- const { camera, gl, invalidate } = useThree();
112
- const [drag, setDrag] = useState(null);
113
- const callbacksRef = useRef({ onDragStart, onDragPreview, onDragEnd });
114
- callbacksRef.current = { onDragStart, onDragPreview, onDragEnd };
115
- const toTriples = (flat) => {
116
- const out = [];
117
- for (let index = 0; index + 2 < flat.length; index += 3) out.push([flat[index], flat[index + 1], flat[index + 2]]);
118
- return out;
119
- };
120
- // Every trail track (root + IK endpoints + head) in its handle colour,
121
- // sampled where playback renders each bone on this rig (not the clip's
122
- // own joints, which sit 4-15 cm off the body), so draw and pick agree.
123
- const tracks = useMemo(
124
- () => TRAIL_TRACKS.map((track) => {
125
- const flat = jointTrailPoints(motion, track.joint, { baseY, scale: charScale, rig });
126
- return flat ? { ...track, flat, points: toTriples(flat) } : null;
127
- }).filter(Boolean),
128
- // eslint-disable-next-line react-hooks/exhaustive-deps
129
- [motion, rig, baseY, charScale],
130
- );
131
- const trackById = (id) => tracks.find((track) => track.id === id) ?? null;
132
- const focusTrack = activeTrackId ? tracks.find((track) => track.id === activeTrackId)?.id ?? null : null;
133
- const visibleTracks = focusTrack ? tracks.filter((track) => track.id === focusTrack) : tracks;
134
- // The falloff window rides whichever line is being (or was last) grabbed.
135
- const highlightCandidate = drag ?? pendingEdit;
136
- const highlight = highlightCandidate && (!focusTrack || highlightCandidate.track === focusTrack) ? highlightCandidate : null;
137
- const highlightPoints = useMemo(() => {
138
- if (!highlight || !motion?.frames) return null;
139
- const flat = trackById(highlight.track)?.flat ?? trackById("hips")?.flat;
140
- if (!flat) return null;
141
- const { startFrame, endFrame } = trailEditRange(motion.frames, highlight.grabFrame, falloffFrames);
142
- const out = [];
143
- for (let frame = startFrame; frame < endFrame; frame += 1) {
144
- out.push([flat[frame * 3], flat[frame * 3 + 1], flat[frame * 3 + 2]]);
145
- }
146
- return out.length > 1 ? out : null;
147
- // eslint-disable-next-line react-hooks/exhaustive-deps
148
- }, [highlight, motion, tracks, falloffFrames]);
149
-
150
- // Manual capture-phase picking instead of r3f pointer handlers: handlers on
151
- // a drei Line make react-three-fiber raycast the fat-line shader geometry on
152
- // EVERY pointermove for hover bookkeeping, which is exactly the IK-mode lag.
153
- // A single pointerdown listener that measures point-to-ray distance against
154
- // the cached trail arrays costs nothing while the mouse merely moves.
155
- const pickRef = useRef(null);
156
- pickRef.current = { tracks: visibleTracks, enabled, falloffFrames, playheadFrame };
157
- // Line2 instances for in-place geometry rewrites during a drag.
158
- const lineRefs = useRef({});
159
- const highlightRef = useRef(null);
160
- useEffect(() => {
161
- const dom = gl.domElement;
162
- const raycaster = new THREE.Raycaster();
163
- const hit = new THREE.Vector3();
164
- const down = (event) => {
165
- const pick = pickRef.current;
166
- if (!pick?.enabled || event.button !== 0) return;
167
- const rect = dom.getBoundingClientRect();
168
- const ndc = new THREE.Vector2(
169
- ((event.clientX - rect.left) / rect.width) * 2 - 1,
170
- -((event.clientY - rect.top) / rect.height) * 2 + 1,
171
- );
172
- raycaster.setFromCamera(ndc, camera);
173
- const { origin, direction } = raycaster.ray;
174
- const picked = pickTrailPoint({
175
- tracks: pick.tracks,
176
- playheadFrame: pick.playheadFrame,
177
- falloffFrames: pick.falloffFrames,
178
- rayOrigin: origin,
179
- rayDirection: direction,
180
- maxDistance: 0.2,
181
- });
182
- if (!picked) return;
183
- const { track, grabFrame } = picked;
184
- // The grab wins over the camera controls listening in the bubble phase.
185
- event.stopPropagation();
186
- event.preventDefault();
187
- const flat = pick.tracks.find((candidate) => candidate.id === track)?.flat;
188
- if (!flat) return;
189
- const start = new THREE.Vector3(flat[grabFrame * 3], flat[grabFrame * 3 + 1], flat[grabFrame * 3 + 2]);
190
- const normal = camera.getWorldDirection(new THREE.Vector3()).negate();
191
- const plane = new THREE.Plane().setFromNormalAndCoplanarPoint(normal, start);
192
- setDrag({ track, grabFrame });
193
- callbacksRef.current.onDragStart?.({ track, grabFrame });
194
- // The drag never touches React state: per rAF the grabbed line and the
195
- // falloff highlight are rewritten in place on their Line2 geometries,
196
- // and onDragPreview poses the rig imperatively. Re-rendering the whole
197
- // app per pointermove is exactly the drag lag this avoids; the real
198
- // commit (setMotion + pending edit) happens once, on pointerup.
199
- const frames = flat.length / 3;
200
- const { startFrame, endFrame } = trailEditRange(frames, grabFrame, pickRef.current.falloffFrames);
201
- const deformedFlat = new Float32Array(flat);
202
- const windowFlat = new Float32Array((endFrame - startFrame) * 3);
203
- let lastDelta = null;
204
- let queued = null;
205
- let rafId = 0;
206
- const applyPreview = (delta) => {
207
- for (let frame = startFrame; frame < endFrame; frame += 1) {
208
- const weight = falloffWeight(frame - grabFrame, pickRef.current.falloffFrames);
209
- deformedFlat[frame * 3] = flat[frame * 3] + delta.x * weight;
210
- deformedFlat[frame * 3 + 1] = flat[frame * 3 + 1] + delta.y * weight;
211
- deformedFlat[frame * 3 + 2] = flat[frame * 3 + 2] + delta.z * weight;
212
- }
213
- windowFlat.set(deformedFlat.subarray(startFrame * 3, endFrame * 3));
214
- lineRefs.current[track]?.geometry?.setPositions(deformedFlat);
215
- highlightRef.current?.geometry?.setPositions(windowFlat);
216
- callbacksRef.current.onDragPreview?.({ track, grabFrame, delta });
217
- invalidate();
218
- };
219
- const flush = () => {
220
- rafId = 0;
221
- if (queued) applyPreview(queued);
222
- queued = null;
223
- };
224
- const move = (pointerEvent) => {
225
- const moveRect = dom.getBoundingClientRect();
226
- const moveNdc = new THREE.Vector2(
227
- ((pointerEvent.clientX - moveRect.left) / moveRect.width) * 2 - 1,
228
- -((pointerEvent.clientY - moveRect.top) / moveRect.height) * 2 + 1,
229
- );
230
- raycaster.setFromCamera(moveNdc, camera);
231
- if (!raycaster.ray.intersectPlane(plane, hit)) return;
232
- lastDelta = { x: hit.x - start.x, y: hit.y - start.y, z: hit.z - start.z };
233
- queued = lastDelta;
234
- if (!rafId) rafId = requestAnimationFrame(flush);
235
- };
236
- const up = () => {
237
- window.removeEventListener("pointermove", move);
238
- window.removeEventListener("pointerup", up);
239
- if (rafId) cancelAnimationFrame(rafId);
240
- if (queued) applyPreview(queued);
241
- setDrag(null);
242
- callbacksRef.current.onDragEnd?.({ track, grabFrame, delta: lastDelta });
243
- };
244
- window.addEventListener("pointermove", move);
245
- window.addEventListener("pointerup", up);
246
- };
247
- dom.addEventListener("pointerdown", down, true);
248
- return () => dom.removeEventListener("pointerdown", down, true);
249
- // eslint-disable-next-line react-hooks/exhaustive-deps
250
- }, [gl, camera]);
251
-
252
- if (!tracks.length) return null;
253
- // depthTest off + high renderOrder: the trails read through the character
254
- // and the floor instead of vanishing into them. Every part rides its own
255
- // IK-handle colour; the focused part draws thicker.
256
- return (
257
- <group
258
- visible={visible}
259
- renderOrder={900}
260
- userData={{ editorOverlay: true }}
261
- ref={(group) => {
262
- // QA-only escape hatch (same spirit as window.__cozyclay): lets
263
- // headless perf probes toggle the trails without a rebuild.
264
- if (typeof window !== "undefined") {
265
- window.__cozyclayTrails = group;
266
- window.__cozyclayVisibleTrailTracks = visibleTracks.map((track) => track.id);
267
- }
268
- }}
269
- >
270
- {visibleTracks.map((track) => track.points.length > 1 && (
271
- <Line
272
- key={track.id}
273
- ref={(line) => {
274
- if (line) lineRefs.current[track.id] = line;
275
- else delete lineRefs.current[track.id];
276
- }}
277
- points={track.points}
278
- color={track.color}
279
- lineWidth={track.id === focusTrack ? 4 : 2.5}
280
- depthTest={false}
281
- transparent
282
- opacity={track.id === focusTrack ? 1 : 0.85}
283
- />
284
- ))}
285
- {highlightPoints && (
286
- <Line ref={highlightRef} points={highlightPoints} color={drag ? "#3dff7a" : "#ff8c42"} lineWidth={5} depthTest={false} />
287
- )}
288
- </group>
289
- );
290
- }, (previous, next) => (
291
- previous.motion === next.motion &&
292
- previous.rig === next.rig &&
293
- previous.baseY === next.baseY &&
294
- previous.charScale === next.charScale &&
295
- previous.ikFocus === next.ikFocus &&
296
- previous.activeTrackId === next.activeTrackId &&
297
- previous.falloffFrames === next.falloffFrames &&
298
- previous.playheadFrame === next.playheadFrame &&
299
- previous.pendingEdit === next.pendingEdit &&
300
- previous.enabled === next.enabled &&
301
- previous.visible === next.visible
302
- ));
@@ -4,6 +4,7 @@ import { Line, Text } from "@react-three/drei";
4
4
  import * as THREE from "three";
5
5
  import { GIZMO_LAYER } from "./dualview.jsx";
6
6
  import { craneHeightAt, prepareRailBend, railPoint } from "./camera-follow.js";
7
+ import { floorDragStart } from "./rail-dot-drag.js";
7
8
  import { insertPathMark, MARK_GAP, MAX_PATH_LEAN, MAX_PATH_MARKS, MAX_PATH_POINTS, nearestPathFraction, pathCurve, pathMarkFractions, pathMarks, pathPointAtFraction } from "./object-path.js";
8
9
 
9
10
  /**
@@ -261,8 +262,7 @@ export function CameraRailScenePreview({ points, cumLen, length, crane }) {
261
262
  * The crane's height marks as grabbable dots on the lifted curve. Click a
262
263
  * dot to select it; the selected dot grows a 3-axis gizmo — Y sets the
263
264
  * mark's height, X/Z bend the camera path itself by moving the drawn rail
264
- * control point nearest the mark. A free drag on the dot rides a
265
- * camera-facing vertical plane (height, plus arc slide for interior marks),
265
+ * control point nearest the mark. A free drag moves the dot over the floor, bending the rail (rail-dot-drag.js);
266
266
  * Shift-drag slides along the arc at a fixed height, double-click the curve
267
267
  * adds a mark and Delete removes a non-endpoint. Dots live on GIZMO_LAYER, so the
268
268
  * recording never sees them.
@@ -958,8 +958,8 @@ export function CraneHandles({ rail, crane, controlPoints, selectedIndex, enable
958
958
  event.preventDefault();
959
959
  const index = hit.object.userData.craneIndex;
960
960
  s.onSelect(index);
961
- dragRef.current = { index, slide: event.shiftKey, recorded: false };
962
- setDragInfo({ kind: event.shiftKey ? "slide" : "free" });
961
+ dragRef.current = floorDragStart(s, index, raycaster.ray, event.shiftKey) ?? { index, slide: event.shiftKey, recorded: false };
962
+ setDragInfo(dragRef.current.axis ? { kind: "bend", t0: dragRef.current.t0, radius: dragRef.current.radius } : { kind: event.shiftKey ? "slide" : "free" });
963
963
  invalidate();
964
964
  };
965
965
  const onMove = (event) => {
@@ -982,8 +982,8 @@ export function CraneHandles({ rail, crane, controlPoints, selectedIndex, enable
982
982
  const height = Math.max(0.1, Math.min(12, drag.startHeight + travel));
983
983
  s.onChangePoints(points.map((entry, i) => (i === drag.index ? { ...entry, height } : entry)), { dragging: true });
984
984
  } else if (drag.startControls && drag.bendWeights && s.onChangeRail) {
985
- const dx = drag.axis === "x" ? travel : 0;
986
- const dz = drag.axis === "z" ? travel : 0;
985
+ const dx = drag.axis === "x" ? travel : drag.axis === "floor" ? world.x : 0;
986
+ const dz = drag.axis === "z" ? travel : drag.axis === "floor" ? world.z : 0;
987
987
  const moved = drag.startControls.map((entry, i) => (drag.bendWeights[i] > 0
988
988
  ? { ...entry, x: entry.x + dx * drag.bendWeights[i], z: entry.z + dz * drag.bendWeights[i] }
989
989
  : entry));
@@ -0,0 +1,93 @@
1
+ // The stage look's environment light (stage.lighting.envLight): a sky-and-ground
2
+ // map built from the look's own colours, so clay and kit materials take the sky's
3
+ // tint and a soft fill from every direction instead of a flat two-colour
4
+ // hemisphere. The upper half grades from `sky` at the horizon to `skyTop`
5
+ // overhead (the same easing as the background, stage-sky.jsx), the lower half is
6
+ // the ground bounce (hemiGround), and a glow sits around the key's direction (the
7
+ // sun, the moon). Pure arithmetic in linear RGB, so the tests read the pixels;
8
+ // stage-sky.jsx prefilters the map with PMREMGenerator and sets it as
9
+ // scene.environment.
10
+ //
11
+ // Brightness: the sky half is scaled to carry the hemisphere's sky light (hemiSky)
12
+ // in total, and stageLook() hands the hemisphere's
13
+ // share to the map as envLight rises (0 = hemisphere only, the studio look;
14
+ // 1 = the map alone at the same level; 2 = twice as bright).
15
+
16
+ export const ENV_WIDTH = 128;
17
+ export const ENV_HEIGHT = 64;
18
+ // How much the key's glow adds under crisp shadows, as a share of the sky's own
19
+ // light on a surface facing it, and its spread (1 - cos of the angle where it falls to 1/e, ~24 deg).
20
+ const SUN_SHARE = 0.8;
21
+ const SUN_SPREAD = 0.085;
22
+ // Below the horizon the ground takes over across this band (degrees).
23
+ const HORIZON_BAND = 8;
24
+
25
+ const toLinear = (c) => (c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4);
26
+ /** "#rrggbb" to linear RGB 0..1. */
27
+ export const linearRgb = (hex) => [1, 3, 5].map((i) => toLinear(parseInt(hex.slice(i, i + 2), 16) / 255));
28
+ export const luminance = ([r, g, b]) => 0.2126 * r + 0.7152 * g + 0.0722 * b;
29
+ const mix = (a, b, t) => a.map((value, i) => value + (b[i] - value) * t);
30
+ const scale = (a, k) => a.map((value) => value * k);
31
+ const round = (a) => a.map((value) => Number(value.toFixed(5)));
32
+
33
+ // The sky's easing by elevation (degrees): 0 at and below the horizon, 1 from 60 deg up.
34
+ const skyBlend = (elevation) => (elevation <= 0 ? 0 : 1 - (1 - Math.min(1, elevation / 60)) ** 2);
35
+
36
+ /**
37
+ * What the map is made of, in linear RGB, or null when envLight is 0. `lighting`
38
+ * and `keyLight` are normalized (stage-lighting.js); `sky` is the horizon colour
39
+ * the look resolved (the theme grey when lighting.sky is null).
40
+ */
41
+ export function environmentSpec(lighting, keyLight, sky) {
42
+ if (!(lighting.envLight > 0)) return null;
43
+ const horizon = linearRgb(sky), zenith = linearRgb(lighting.skyTop ?? sky), skyLight = linearRgb(lighting.hemiSky);
44
+ // The sky half's mean luminance over its solid angle, so the sky can be scaled
45
+ // to carry hemiSky's light in total whatever its colours: a sky brighter at the
46
+ // horizon then lights the sides of a figure more and its top less.
47
+ let weighted = 0, weights = 0;
48
+ for (let step = 0; step < 90; step++) {
49
+ const elevation = step + 0.5, cos = Math.cos((elevation * Math.PI) / 180);
50
+ weighted += luminance(mix(horizon, zenith, skyBlend(elevation))) * cos;
51
+ weights += cos;
52
+ }
53
+ const mean = weighted / weights, target = luminance(skyLight);
54
+ // A black sky has no shape to keep: the sky half is then hemiSky itself.
55
+ const [low, high] = mean > 1e-6 ? [scale(horizon, target / mean), scale(zenith, target / mean)] : [skyLight, skyLight];
56
+ const length = Math.hypot(keyLight.x, keyLight.y, keyLight.z) || 1, key = linearRgb(keyLight.color ?? "#ffffff");
57
+ // A soft-shadowed look is a hazy or overcast sky: its sun is a fainter glow.
58
+ const keyLuma = luminance(key) || 1, share = SUN_SHARE * (1 - 0.6 * lighting.shadowSoftness);
59
+ return {
60
+ horizon: round(low), zenith: round(high), ground: round(linearRgb(lighting.hemiGround)),
61
+ sun: { direction: round([keyLight.x / length, keyLight.y / length, keyLight.z / length]),
62
+ // peak radiance: the glow integrates to ~2*pi*spread, so this adds `share` of the sky's light toward it
63
+ color: round(scale(key, (share * target) / (2 * SUN_SPREAD) / keyLuma)) },
64
+ };
65
+ }
66
+
67
+ /** Radiance (linear RGB) of the map in a unit direction [x, y, z] (y up). */
68
+ export function environmentRadiance(spec, [x, y, z]) {
69
+ const elevation = (Math.asin(Math.max(-1, Math.min(1, y))) * 180) / Math.PI;
70
+ const skyAt = mix(spec.horizon, spec.zenith, skyBlend(elevation));
71
+ const base = elevation >= 0 ? skyAt : mix(spec.horizon, spec.ground, Math.min(1, -elevation / HORIZON_BAND));
72
+ const [sx, sy, sz] = spec.sun.direction, cos = x * sx + y * sy + z * sz;
73
+ const glow = Math.exp((cos - 1) / SUN_SPREAD);
74
+ return base.map((value, i) => value + spec.sun.color[i] * glow);
75
+ }
76
+
77
+ /**
78
+ * The equirectangular map as RGBA floats, rows from straight down (row 0) to
79
+ * straight up, columns around the horizon the way three samples an
80
+ * equirectangular texture (u = atan2(z, x) / 2pi + 0.5).
81
+ */
82
+ export function environmentPixels(spec, width = ENV_WIDTH, height = ENV_HEIGHT) {
83
+ const data = new Float32Array(width * height * 4);
84
+ for (let row = 0; row < height; row++) {
85
+ const elevation = ((row + 0.5) / height - 0.5) * Math.PI, cosEl = Math.cos(elevation), y = Math.sin(elevation);
86
+ for (let column = 0; column < width; column++) {
87
+ const azimuth = ((column + 0.5) / width - 0.5) * 2 * Math.PI;
88
+ const radiance = environmentRadiance(spec, [Math.cos(azimuth) * cosEl, y, Math.sin(azimuth) * cosEl]);
89
+ data.set([...radiance, 1], (row * width + column) * 4);
90
+ }
91
+ }
92
+ return data;
93
+ }
@@ -0,0 +1,107 @@
1
+ // The built-in HDR environments (stage-hdr.js HDR_BUILTINS), drawn as
2
+ // equirectangular radiance maps in linear RGB: a night street, an overcast
3
+ // field and a photo studio. Pure arithmetic and an integer hash, so a map is
4
+ // the same on every machine and the tests read its pixels. Rows run from
5
+ // straight down (row 0) to straight up, columns around the horizon the way
6
+ // three samples an equirectangular texture: azimuth 0 is +x, +90 is +z (toward
7
+ // the default camera), -90 is -z.
8
+
9
+ const D = Math.PI / 180;
10
+ const clamp01 = (value) => Math.max(0, Math.min(1, value));
11
+ const smooth = (value) => { const t = clamp01(value); return t * t * (3 - 2 * t); };
12
+ const mix = (a, b, t) => a.map((value, i) => value + (b[i] - value) * t);
13
+ const add = (out, colour, weight) => { if (weight > 0) for (let i = 0; i < 3; i++) out[i] += colour[i] * weight; };
14
+ /** A 0..1 integer hash of two integers and a seed: windows, rooflines and trees. */
15
+ function hash(a, b, seed) {
16
+ let h = Math.imul(a, 0x27d4eb2d) ^ Math.imul(b, 0x165667b1) ^ Math.imul(seed, 0x9e3779b1);
17
+ h = Math.imul(h ^ (h >>> 15), 0x85ebca6b);
18
+ h = Math.imul(h ^ (h >>> 13), 0xc2b2ae35);
19
+ return ((h ^ (h >>> 16)) >>> 0) / 4294967296;
20
+ }
21
+ /** Smooth 0..1 value noise around the horizon: `knots` hashed values, eased between, wrapping at azimuth +-180. */
22
+ const noise = (az, knots, seed) => {
23
+ const x = ((az + 180) / 360) * knots, i = Math.floor(x), t = smooth(x - i);
24
+ return hash(i % knots, 0, seed) * (1 - t) + hash((i + 1) % knots, 0, seed) * t;
25
+ };
26
+ const unit = (az, el) => [Math.cos(el * D) * Math.cos(az * D), Math.sin(el * D), Math.cos(el * D) * Math.sin(az * D)];
27
+ /** A round light of peak radiance 1 at `centre`, falling to 1/e `radius` degrees off it. */
28
+ const spot = (dir, centre, radius) => Math.exp((dir[0] * centre[0] + dir[1] * centre[1] + dir[2] * centre[2] - 1) / (1 - Math.cos(radius * D)));
29
+ /** Signed azimuth difference in degrees, -180..180. */
30
+ const azDelta = (az, centre) => ((((az - centre + 180) % 360) + 360) % 360) - 180;
31
+ /** A soft-edged panel in azimuth/elevation (degrees): 1 inside, 0 outside, `edge` wide. */
32
+ const panel = (az, el, { az: a, el: e, w, h, edge = 1.5 }) => smooth((w - Math.abs(azDelta(az, a)) * Math.cos(e * D)) / edge) * smooth((h - Math.abs(el - e)) / edge);
33
+
34
+ const NIGHT = { lampA: unit(42.5, 11.6), lampB: unit(-140.5, 12.8), moon: unit(-60, 48) };
35
+ function nightStreet(az, el, dir) {
36
+ const out = el >= 0 ? mix([0.03, 0.028, 0.042], [0.004, 0.006, 0.016], Math.sqrt(el / 90)) : [0.006, 0.006, 0.0075];
37
+ if (el >= 0) {
38
+ // A skyline of blocks 5..14 degrees wide and 3..16 tall, with lit windows.
39
+ const block = Math.floor((az + 180) / 9), roof = 3 + 13 * hash(block, 1, 7) * (0.5 + 0.5 * hash(block, 2, 7));
40
+ if (el < roof) {
41
+ out.splice(0, 3, 0.007, 0.007, 0.01);
42
+ const column = Math.floor((az + 180) / 1.1), row = Math.floor(el / 1.5), lit = hash(column, row, 11);
43
+ const inside = Math.abs(((az + 180) / 1.1) % 1 - 0.5) < 0.28 && Math.abs((el / 1.5) % 1 - 0.5) < 0.24;
44
+ if (inside && lit < 0.3) add(out, lit < 0.2 ? [0.42, 0.3, 0.15] : [0.2, 0.22, 0.28], 1);
45
+ }
46
+ add(out, [3.2, 3.4, 3.8], spot(dir, NIGHT.moon, 0.45));
47
+ add(out, [0.03, 0.035, 0.05], spot(dir, NIGHT.moon, 9));
48
+ } else {
49
+ // Wet asphalt: a warm pool under each lamp and a streak reflecting it.
50
+ for (const [lamp, az0] of [[NIGHT.lampA, 42.5], [NIGHT.lampB, -140.5]]) {
51
+ const across = azDelta(az, az0), pool = Math.exp(-(across * across) / (2 * 22 * 22)) * smooth((-el - 2) / 10) * Math.exp(-Math.max(0, -el - 18) / 18);
52
+ add(out, [0.09, 0.05, 0.018], pool);
53
+ add(out, [0.6, 0.33, 0.1], Math.exp(-(across * across) / 4) * smooth((-el) / 2) * Math.exp(el / 10) * (lamp === NIGHT.lampA ? 1 : 0.8));
54
+ }
55
+ }
56
+ add(out, [70, 38, 12], spot(dir, NIGHT.lampA, 1.3));
57
+ add(out, [55, 30, 9.5], spot(dir, NIGHT.lampB, 1.3));
58
+ add(out, [0.9, 0.5, 0.17], spot(dir, NIGHT.lampA, 5) + 0.8 * spot(dir, NIGHT.lampB, 5));
59
+ return out;
60
+ }
61
+
62
+ const OVERCAST = { sun: unit(-118, 40) };
63
+ function overcastField(az, el, dir) {
64
+ if (el < 0) {
65
+ // A green field, hazier toward the horizon.
66
+ const out = mix([0.13, 0.155, 0.1], [0.085, 0.125, 0.045], smooth(-el / 25));
67
+ add(out, [0.012, 0.015, 0.004], hash(Math.floor((az + 180) * 2), Math.floor(-el * 2), 3) - 0.5);
68
+ return out;
69
+ }
70
+ // The overcast sky is three times brighter overhead than at the horizon.
71
+ const out = [0.8, 0.83, 0.88].map((value) => value * (1 + 2 * Math.sin(el * D)) / 3);
72
+ add(out, [0.9, 0.85, 0.74], spot(dir, OVERCAST.sun, 14));
73
+ // A tree line: two octaves of smooth value noise around the horizon, 0.5..3 degrees tall.
74
+ const crown = 0.5 + 1.8 * noise(az, 52, 5) + 0.7 * noise(az, 225, 6);
75
+ if (el < crown) return mix(out, [0.032, 0.046, 0.028], smooth((crown - el) / 0.25));
76
+ return out;
77
+ }
78
+
79
+ const STUDIO = { keyCentre: unit(130.6, 23.5), key: { az: 130.6, el: 23.5, w: 16, h: 12 }, strip: { az: 13, el: 15, w: 4, h: 22 }, rim: { az: -84, el: 19, w: 10, h: 14 } };
80
+ function studioSoftbox(az, el, dir) {
81
+ const out = el >= 0 ? [0.018, 0.018, 0.02] : mix([0.032, 0.032, 0.034], [0.022, 0.022, 0.024], smooth(-el / 60));
82
+ // The softbox is a touch brighter at its centre, like a real diffuser.
83
+ add(out, [5, 4.9, 4.7], panel(az, el, STUDIO.key) * (0.92 + 0.08 * spot(dir, STUDIO.keyCentre, 8)));
84
+ add(out, [2.2, 2.3, 2.5], panel(az, el, STUDIO.strip));
85
+ add(out, [2, 2, 2], panel(az, el, STUDIO.rim));
86
+ add(out, [0.5, 0.5, 0.5], smooth((el - 66) / 6));
87
+ return out;
88
+ }
89
+
90
+ const DRAW = { "builtin:night-street": nightStreet, "builtin:overcast-field": overcastField, "builtin:studio-softbox": studioSoftbox };
91
+ export const BUILTIN_WIDTH = 1024;
92
+ export const BUILTIN_HEIGHT = 512;
93
+
94
+ /** A built-in map as RGBA floats (linear radiance), or null for an unknown id. */
95
+ export function builtinEnvironmentPixels(id, width = BUILTIN_WIDTH, height = BUILTIN_HEIGHT) {
96
+ const draw = DRAW[id];
97
+ if (!draw) return null;
98
+ const data = new Float32Array(width * height * 4);
99
+ for (let row = 0; row < height; row++) {
100
+ const el = ((row + 0.5) / height - 0.5) * 180;
101
+ for (let column = 0; column < width; column++) {
102
+ const az = ((column + 0.5) / width - 0.5) * 360, radiance = draw(az, el, unit(az, el));
103
+ data.set([Math.max(0, radiance[0]), Math.max(0, radiance[1]), Math.max(0, radiance[2]), 1], (row * width + column) * 4);
104
+ }
105
+ }
106
+ return data;
107
+ }