@hraness/slopcamera 3.2.4

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 (441) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +60 -0
  3. package/PRIVACY.md +46 -0
  4. package/README.md +311 -0
  5. package/SECURITY.md +21 -0
  6. package/apps/desktop/README.md +966 -0
  7. package/apps/desktop/analysis/protocol.ts +223 -0
  8. package/apps/desktop/application/capability-binding.ts +594 -0
  9. package/apps/desktop/application/context.ts +97 -0
  10. package/apps/desktop/application/creative-iteration.ts +1385 -0
  11. package/apps/desktop/application/default-registry.ts +146 -0
  12. package/apps/desktop/application/directing-contract.ts +218 -0
  13. package/apps/desktop/application/directing-plan.ts +178 -0
  14. package/apps/desktop/application/errors.ts +18 -0
  15. package/apps/desktop/application/gateway-port.ts +867 -0
  16. package/apps/desktop/application/html-overlay-browser-runtime.ts +687 -0
  17. package/apps/desktop/application/html-overlay-integrity.ts +337 -0
  18. package/apps/desktop/application/html-overlay-renderer.ts +45 -0
  19. package/apps/desktop/application/index.ts +13 -0
  20. package/apps/desktop/application/operation-completion-checkpoint.ts +333 -0
  21. package/apps/desktop/application/operation-effects.ts +94 -0
  22. package/apps/desktop/application/operation.ts +224 -0
  23. package/apps/desktop/application/operations/analysis/faces.ts +244 -0
  24. package/apps/desktop/application/operations/analysis/music.ts +248 -0
  25. package/apps/desktop/application/operations/analysis/project-auto-zooms.ts +302 -0
  26. package/apps/desktop/application/operations/analysis/project-inactivity.ts +381 -0
  27. package/apps/desktop/application/operations/analysis/scenes.ts +272 -0
  28. package/apps/desktop/application/operations/analysis/shared.ts +202 -0
  29. package/apps/desktop/application/operations/derive/edit-batch.ts +967 -0
  30. package/apps/desktop/application/operations/derive/follow-faces.ts +454 -0
  31. package/apps/desktop/application/operations/edit/bind-candidate-revision.ts +91 -0
  32. package/apps/desktop/application/operations/edit/create-candidate-revision.ts +377 -0
  33. package/apps/desktop/application/operations/edit/create-revision.ts +255 -0
  34. package/apps/desktop/application/operations/edit/freeze-revision.ts +182 -0
  35. package/apps/desktop/application/operations/gateway/image.ts +41 -0
  36. package/apps/desktop/application/operations/gateway/index.ts +17 -0
  37. package/apps/desktop/application/operations/gateway/shared.ts +122 -0
  38. package/apps/desktop/application/operations/gateway/speech.ts +41 -0
  39. package/apps/desktop/application/operations/gateway/transcription.ts +42 -0
  40. package/apps/desktop/application/operations/gateway/video.ts +41 -0
  41. package/apps/desktop/application/operations/index.ts +31 -0
  42. package/apps/desktop/application/operations/iteration/create-candidate.ts +240 -0
  43. package/apps/desktop/application/operations/iteration/create-matrix.ts +117 -0
  44. package/apps/desktop/application/operations/iteration/select.ts +155 -0
  45. package/apps/desktop/application/operations/iteration/shared.ts +250 -0
  46. package/apps/desktop/application/operations/media/audio-effects.ts +376 -0
  47. package/apps/desktop/application/operations/media/binding.ts +79 -0
  48. package/apps/desktop/application/operations/media/capabilities.ts +112 -0
  49. package/apps/desktop/application/operations/media/color-grade.ts +343 -0
  50. package/apps/desktop/application/operations/media/html-overlay.ts +802 -0
  51. package/apps/desktop/application/operations/media/index.ts +8 -0
  52. package/apps/desktop/application/operations/media/ingest.ts +303 -0
  53. package/apps/desktop/application/operations/media/overlay.ts +729 -0
  54. package/apps/desktop/application/operations/media/shared.ts +845 -0
  55. package/apps/desktop/application/operations/media/transform-platform.ts +120 -0
  56. package/apps/desktop/application/operations/project/commit-edits.ts +2054 -0
  57. package/apps/desktop/application/operations/project/promote-selection.ts +495 -0
  58. package/apps/desktop/application/operations/project/snapshot.ts +95 -0
  59. package/apps/desktop/application/operations/recording/index.ts +5 -0
  60. package/apps/desktop/application/operations/recording/pause.ts +34 -0
  61. package/apps/desktop/application/operations/recording/resume.ts +34 -0
  62. package/apps/desktop/application/operations/recording/shared.ts +199 -0
  63. package/apps/desktop/application/operations/recording/start.ts +169 -0
  64. package/apps/desktop/application/operations/recording/stop.ts +34 -0
  65. package/apps/desktop/application/operations/render/bind-candidate-output.ts +324 -0
  66. package/apps/desktop/application/operations/render/materialize-selection.ts +322 -0
  67. package/apps/desktop/application/operations/render/project-caption-plan.ts +287 -0
  68. package/apps/desktop/application/operations/render/project-plan.ts +939 -0
  69. package/apps/desktop/application/operations/render/project-spatial-receipt.ts +70 -0
  70. package/apps/desktop/application/operations/render/project.ts +1922 -0
  71. package/apps/desktop/application/operations/shared.ts +67 -0
  72. package/apps/desktop/application/operations/slopcamera-portable.ts +1188 -0
  73. package/apps/desktop/application/operations/slopcamera-visuals.ts +784 -0
  74. package/apps/desktop/application/operations/spatial-project.ts +209 -0
  75. package/apps/desktop/application/operations/spatial-render.ts +415 -0
  76. package/apps/desktop/application/operations/spatial-scene.ts +97 -0
  77. package/apps/desktop/application/operations/studio.ts +143 -0
  78. package/apps/desktop/application/output-publication-lease.ts +163 -0
  79. package/apps/desktop/application/project-publication-lease.ts +233 -0
  80. package/apps/desktop/application/project-store.ts +196 -0
  81. package/apps/desktop/application/receipts.ts +533 -0
  82. package/apps/desktop/application/registry.ts +182 -0
  83. package/apps/desktop/application/spatial-assets.ts +675 -0
  84. package/apps/desktop/application/spatial-compositor-cadence.ts +23 -0
  85. package/apps/desktop/application/spatial-project-authority.ts +35 -0
  86. package/apps/desktop/application/spatial-project-lease.ts +126 -0
  87. package/apps/desktop/application/spatial-project-render-preparation.ts +225 -0
  88. package/apps/desktop/application/spatial-project-store.ts +286 -0
  89. package/apps/desktop/application/spatial-render-projection.ts +201 -0
  90. package/apps/desktop/application/spatial-render.ts +677 -0
  91. package/apps/desktop/application/spatial-spz.ts +44 -0
  92. package/apps/desktop/application/spatial-world-import.ts +135 -0
  93. package/apps/desktop/application/spatial-world-provenance.ts +46 -0
  94. package/apps/desktop/application/studio-port.ts +58 -0
  95. package/apps/desktop/application/verified-receipt-reconciliation.ts +1574 -0
  96. package/apps/desktop/capture/protocol.ts +689 -0
  97. package/apps/desktop/cli/analyzer.ts +406 -0
  98. package/apps/desktop/cli/args.ts +3181 -0
  99. package/apps/desktop/cli/asset-ingest.ts +747 -0
  100. package/apps/desktop/cli/atomic-render-effects.ts +210 -0
  101. package/apps/desktop/cli/atomic-render-platform.ts +205 -0
  102. package/apps/desktop/cli/atomic-render.ts +50 -0
  103. package/apps/desktop/cli/audio-analysis.ts +252 -0
  104. package/apps/desktop/cli/bounded-map.ts +39 -0
  105. package/apps/desktop/cli/bundle-service.ts +256 -0
  106. package/apps/desktop/cli/capabilities.ts +212 -0
  107. package/apps/desktop/cli/capture-bundle.ts +1117 -0
  108. package/apps/desktop/cli/command-host-resources.ts +251 -0
  109. package/apps/desktop/cli/commands.ts +6836 -0
  110. package/apps/desktop/cli/directing-blob.ts +291 -0
  111. package/apps/desktop/cli/directing-media.ts +517 -0
  112. package/apps/desktop/cli/directing-quote.ts +89 -0
  113. package/apps/desktop/cli/directing-service.ts +288 -0
  114. package/apps/desktop/cli/emoji-assets.ts +538 -0
  115. package/apps/desktop/cli/errors.ts +29 -0
  116. package/apps/desktop/cli/face-analysis-service.ts +940 -0
  117. package/apps/desktop/cli/filter-script.ts +76 -0
  118. package/apps/desktop/cli/gateway-application-port.ts +1142 -0
  119. package/apps/desktop/cli/gateway-credential.ts +81 -0
  120. package/apps/desktop/cli/gateway-media-artifacts.ts +723 -0
  121. package/apps/desktop/cli/gateway-media-catalog.ts +922 -0
  122. package/apps/desktop/cli/gateway-media-service.ts +3106 -0
  123. package/apps/desktop/cli/gateway-media-signature.ts +122 -0
  124. package/apps/desktop/cli/gateway-provider-options.ts +562 -0
  125. package/apps/desktop/cli/gateway-scene-provider.ts +391 -0
  126. package/apps/desktop/cli/help.ts +501 -0
  127. package/apps/desktop/cli/html-overlay-gpu.ts +97 -0
  128. package/apps/desktop/cli/html-overlay-renderer.ts +2478 -0
  129. package/apps/desktop/cli/io.ts +290 -0
  130. package/apps/desktop/cli/main.ts +123 -0
  131. package/apps/desktop/cli/media-effects-service.ts +885 -0
  132. package/apps/desktop/cli/media-ingest-model.ts +364 -0
  133. package/apps/desktop/cli/media-ingest-platform.ts +227 -0
  134. package/apps/desktop/cli/media-ingest-program.ts +200 -0
  135. package/apps/desktop/cli/media-ingest.ts +30 -0
  136. package/apps/desktop/cli/music-analysis-service.ts +434 -0
  137. package/apps/desktop/cli/mutation-lock.ts +530 -0
  138. package/apps/desktop/cli/native-assets.d.ts +12 -0
  139. package/apps/desktop/cli/native-media-runtime.macos.ts +7 -0
  140. package/apps/desktop/cli/overlay-playback.ts +177 -0
  141. package/apps/desktop/cli/paths.ts +265 -0
  142. package/apps/desktop/cli/portable-surface.ts +242 -0
  143. package/apps/desktop/cli/project-camera-receipt.ts +112 -0
  144. package/apps/desktop/cli/project-face-camera.ts +394 -0
  145. package/apps/desktop/cli/project-inactivity-service.ts +552 -0
  146. package/apps/desktop/cli/project-media-integrity.ts +121 -0
  147. package/apps/desktop/cli/project-renderer.ts +1526 -0
  148. package/apps/desktop/cli/project-service.ts +519 -0
  149. package/apps/desktop/cli/project-state-transaction.ts +520 -0
  150. package/apps/desktop/cli/query-limits.ts +1 -0
  151. package/apps/desktop/cli/recording-controller.ts +1567 -0
  152. package/apps/desktop/cli/recording-daemon.ts +931 -0
  153. package/apps/desktop/cli/recording-ref.ts +56 -0
  154. package/apps/desktop/cli/renamed-environment.ts +6 -0
  155. package/apps/desktop/cli/renderer.ts +1439 -0
  156. package/apps/desktop/cli/run-cli-test-helper.ts +60 -0
  157. package/apps/desktop/cli/scene-analysis-service.ts +879 -0
  158. package/apps/desktop/cli/spatial-compositor-verifier.ts +213 -0
  159. package/apps/desktop/cli/spatial-project-service.ts +90 -0
  160. package/apps/desktop/cli/spatial-scene-service.ts +105 -0
  161. package/apps/desktop/cli/spatial-world-service.ts +39 -0
  162. package/apps/desktop/cli/speech-analysis-service.ts +682 -0
  163. package/apps/desktop/cli/studio-assemble.ts +58 -0
  164. package/apps/desktop/cli/studio-command.ts +60 -0
  165. package/apps/desktop/cli/studio-custody.ts +60 -0
  166. package/apps/desktop/cli/studio-encode.ts +216 -0
  167. package/apps/desktop/cli/studio-exr.ts +52 -0
  168. package/apps/desktop/cli/studio-files.ts +179 -0
  169. package/apps/desktop/cli/studio-output-validation.ts +162 -0
  170. package/apps/desktop/cli/studio-process.ts +145 -0
  171. package/apps/desktop/cli/studio-runtime.ts +77 -0
  172. package/apps/desktop/cli/studio-scaffold.ts +70 -0
  173. package/apps/desktop/cli/studio-service.ts +301 -0
  174. package/apps/desktop/cli/studio-spatial-asset.ts +248 -0
  175. package/apps/desktop/cli/studio-template-names.ts +3 -0
  176. package/apps/desktop/cli/studio-workflow.ts +19 -0
  177. package/apps/desktop/cli/time.ts +25 -0
  178. package/apps/desktop/cli/workflow-code.ts +584 -0
  179. package/apps/desktop/cli/workflow-runs.ts +576 -0
  180. package/apps/desktop/code/advanced.ts +139 -0
  181. package/apps/desktop/code/application-node-planner.ts +1288 -0
  182. package/apps/desktop/code/compiler.ts +329 -0
  183. package/apps/desktop/code/contracts.ts +13 -0
  184. package/apps/desktop/code/creative-recipes.ts +94 -0
  185. package/apps/desktop/code/define-workflow.ts +115 -0
  186. package/apps/desktop/code/editing.ts +284 -0
  187. package/apps/desktop/code/file-candidate-provenance.ts +420 -0
  188. package/apps/desktop/code/file-candidate.ts +69 -0
  189. package/apps/desktop/code/graph-builder.ts +10 -0
  190. package/apps/desktop/code/host-resource-policy.ts +55 -0
  191. package/apps/desktop/code/plan-contracts.ts +159 -0
  192. package/apps/desktop/code/planning.ts +361 -0
  193. package/apps/desktop/code/public.ts +134 -0
  194. package/apps/desktop/code/reference-projection.ts +62 -0
  195. package/apps/desktop/code/run-contracts.ts +466 -0
  196. package/apps/desktop/code/run-store.ts +2478 -0
  197. package/apps/desktop/code/runtime-identity.ts +328 -0
  198. package/apps/desktop/code/scheduler.ts +3067 -0
  199. package/apps/desktop/code/semantic-builder.ts +1436 -0
  200. package/apps/desktop/code/source-bundle.ts +1641 -0
  201. package/apps/desktop/code/source-typecheck.ts +211 -0
  202. package/apps/desktop/code/testing.ts +79 -0
  203. package/apps/desktop/code/worker-client.ts +1675 -0
  204. package/apps/desktop/code/worker-effects.ts +141 -0
  205. package/apps/desktop/code/worker-entry.ts +435 -0
  206. package/apps/desktop/code/worker-lease-guardian.ts +179 -0
  207. package/apps/desktop/code/worker-process-identity.ts +484 -0
  208. package/apps/desktop/code/worker-protocol.ts +211 -0
  209. package/apps/desktop/code/workflow-effects.ts +132 -0
  210. package/apps/desktop/contracts/analysis.ts +949 -0
  211. package/apps/desktop/contracts/edit.ts +576 -0
  212. package/apps/desktop/contracts/index.ts +9 -0
  213. package/apps/desktop/contracts/media-effects.ts +139 -0
  214. package/apps/desktop/contracts/project-render.ts +644 -0
  215. package/apps/desktop/contracts/project.ts +740 -0
  216. package/apps/desktop/contracts/recording.ts +1434 -0
  217. package/apps/desktop/contracts/render.ts +334 -0
  218. package/apps/desktop/contracts/runtime.ts +155 -0
  219. package/apps/desktop/contracts/spatial-compositor.ts +58 -0
  220. package/apps/desktop/contracts/spatial-project.ts +159 -0
  221. package/apps/desktop/contracts/spatial-world.ts +48 -0
  222. package/apps/desktop/contracts/video-effects.ts +120 -0
  223. package/apps/desktop/core/alignment-apply.ts +400 -0
  224. package/apps/desktop/core/audio-alignment.ts +466 -0
  225. package/apps/desktop/core/auto-zoom.ts +209 -0
  226. package/apps/desktop/core/canonical-json.ts +14 -0
  227. package/apps/desktop/core/emoji.ts +210 -0
  228. package/apps/desktop/core/events.ts +409 -0
  229. package/apps/desktop/core/face-framing.ts +533 -0
  230. package/apps/desktop/core/face-tracks.ts +326 -0
  231. package/apps/desktop/core/inactivity.ts +137 -0
  232. package/apps/desktop/core/index.ts +30 -0
  233. package/apps/desktop/core/intervals.ts +123 -0
  234. package/apps/desktop/core/montage.ts +236 -0
  235. package/apps/desktop/core/music-analysis.ts +518 -0
  236. package/apps/desktop/core/plan.ts +244 -0
  237. package/apps/desktop/core/project-auto-zoom.ts +75 -0
  238. package/apps/desktop/core/project-camera-spatial.ts +676 -0
  239. package/apps/desktop/core/project-camera.ts +403 -0
  240. package/apps/desktop/core/project-captions.ts +888 -0
  241. package/apps/desktop/core/project-inactivity.ts +125 -0
  242. package/apps/desktop/core/project-metadata.ts +927 -0
  243. package/apps/desktop/core/project-plan.ts +375 -0
  244. package/apps/desktop/core/project-render-plan.ts +303 -0
  245. package/apps/desktop/core/project-time.ts +469 -0
  246. package/apps/desktop/core/render-plan.ts +442 -0
  247. package/apps/desktop/core/scene-cache.ts +174 -0
  248. package/apps/desktop/core/scene-sampling.ts +448 -0
  249. package/apps/desktop/core/spatial-compositor.ts +22 -0
  250. package/apps/desktop/core/spatial-durability.ts +99 -0
  251. package/apps/desktop/core/spatial-project.ts +169 -0
  252. package/apps/desktop/core/spatial-shot-clock.ts +32 -0
  253. package/apps/desktop/core/speech-analysis.ts +333 -0
  254. package/apps/desktop/core/storage.ts +970 -0
  255. package/apps/desktop/core/time-map.ts +91 -0
  256. package/apps/desktop/core/time.ts +61 -0
  257. package/apps/desktop/core/video-effects.ts +660 -0
  258. package/apps/desktop/dist/cli/NebulaSans-Bold-26se8aek.otf +0 -0
  259. package/apps/desktop/dist/cli/NebulaSans-Bold-bcz7y08t.woff2 +0 -0
  260. package/apps/desktop/dist/cli/NebulaSans-Book-5ax05zvn.woff2 +0 -0
  261. package/apps/desktop/dist/cli/NebulaSans-Book-8cenzchw.otf +0 -0
  262. package/apps/desktop/dist/cli/main.js +5086 -0
  263. package/apps/desktop/html-overlay/catalog.ts +171 -0
  264. package/apps/desktop/html-overlay/contracts.ts +372 -0
  265. package/apps/desktop/html-overlay/execution-profile.ts +77 -0
  266. package/apps/desktop/html-overlay/index.ts +98 -0
  267. package/apps/desktop/html-overlay/libraries.ts +263 -0
  268. package/apps/desktop/html-overlay/random.ts +49 -0
  269. package/apps/desktop/html-overlay/runtime.ts +813 -0
  270. package/apps/desktop/html-overlay/scaffolds.ts +792 -0
  271. package/apps/desktop/html-overlay/spark-runtime.ts +88 -0
  272. package/apps/desktop/html-overlay/spatial-splat-runtime.ts +60 -0
  273. package/apps/desktop/html-overlay/spatial.ts +720 -0
  274. package/apps/desktop/studio/assets/README.md +39 -0
  275. package/apps/desktop/studio/assets/contracts.ts +129 -0
  276. package/apps/desktop/studio/assets/network.ts +64 -0
  277. package/apps/desktop/studio/assets/poly-haven.ts +148 -0
  278. package/apps/desktop/studio/drivers/blender_driver.py +466 -0
  279. package/apps/desktop/studio/drivers/cadquery_driver.py +176 -0
  280. package/apps/desktop/studio/education/driver.py +308 -0
  281. package/apps/desktop/studio/education/lesson.py +180 -0
  282. package/apps/desktop/studio/education/toolkit.py +249 -0
  283. package/apps/desktop/workflows/chaptered-demo.ts +48 -0
  284. package/apps/desktop/workflows/creative-iteration.ts +107 -0
  285. package/apps/desktop/workflows/creative-selection.ts +92 -0
  286. package/apps/desktop/workflows/fragments.ts +486 -0
  287. package/apps/desktop/workflows/index.ts +93 -0
  288. package/apps/desktop/workflows/polished-screen-demo.ts +128 -0
  289. package/apps/desktop/workflows/scene-direction.ts +12 -0
  290. package/apps/desktop/workflows/social-variants.ts +75 -0
  291. package/apps/desktop/workflows/talking-head-cleanup.ts +53 -0
  292. package/dist/NebulaSans-Bold-26se8aek.otf +0 -0
  293. package/dist/NebulaSans-Bold-bcz7y08t.woff2 +0 -0
  294. package/dist/NebulaSans-Book-5ax05zvn.woff2 +0 -0
  295. package/dist/NebulaSans-Book-8cenzchw.otf +0 -0
  296. package/dist/cli.js +479 -0
  297. package/dist/code/advanced.js +270 -0
  298. package/dist/code/index.js +2240 -0
  299. package/dist/generate.js +24 -0
  300. package/dist/host-resources.js +26 -0
  301. package/dist/index-42zsesc1.js +2257 -0
  302. package/dist/index-7308egqr.js +48 -0
  303. package/dist/index-8txs6fkn.js +712 -0
  304. package/dist/index-mcy8z0br.js +192 -0
  305. package/dist/index-p63wavx0.js +2029 -0
  306. package/dist/index-r7gdhmsp.js +424 -0
  307. package/dist/index-sh6xbav6.js +1219 -0
  308. package/dist/index-z1w83f81.js +4 -0
  309. package/dist/index-z7239b4h.js +2363 -0
  310. package/dist/index-zfeg9hmw.js +2013 -0
  311. package/dist/index.js +241 -0
  312. package/dist/operations.js +29 -0
  313. package/dist/skill-install-dh0ntqba.js +10 -0
  314. package/dist/vectorize/worker.js +142 -0
  315. package/dist/workflow.js +16 -0
  316. package/docs/README.md +38 -0
  317. package/docs/studio.md +215 -0
  318. package/examples/studio/blender/README.md +26 -0
  319. package/examples/studio/blender/character.py +152 -0
  320. package/examples/studio/blender/cloth.py +101 -0
  321. package/examples/studio/blender/color_chart.py +29 -0
  322. package/examples/studio/blender/fluid.py +78 -0
  323. package/examples/studio/blender/import_model.py +74 -0
  324. package/examples/studio/blender/product.py +53 -0
  325. package/examples/studio/blender/shaded_street.py +481 -0
  326. package/examples/studio/blender/studio_scene.py +147 -0
  327. package/examples/studio/cadquery/README.md +13 -0
  328. package/examples/studio/cadquery/bracket.py +36 -0
  329. package/examples/studio/cadquery/import_step.py +12 -0
  330. package/examples/studio/education/README.md +46 -0
  331. package/examples/studio/education/lesson.json +99 -0
  332. package/examples/studio/education/scene.py +7 -0
  333. package/examples/studio/hybrid-scene.ts +96 -0
  334. package/examples/studio/native-workflow.ts +14 -0
  335. package/examples/studio/vgpu/README.md +64 -0
  336. package/examples/studio/vgpu/heat-field.mjs +290 -0
  337. package/package.json +304 -0
  338. package/packages/scene/src/index.ts +1 -0
  339. package/packages/scene/src/scene.ts +215 -0
  340. package/schema/diagram.schema.json +334 -0
  341. package/skills/slopcamera/SKILL.md +45 -0
  342. package/skills/slopcamera/agents/openai.yaml +4 -0
  343. package/skills/slopcamera/references/customization.md +77 -0
  344. package/skills/slopcamera/references/diagrams.md +153 -0
  345. package/skills/slopcamera/references/directed-scenes.md +42 -0
  346. package/skills/slopcamera/references/directing-video.md +18 -0
  347. package/skills/slopcamera/references/educational-video.md +26 -0
  348. package/skills/slopcamera/references/gateway-media.md +134 -0
  349. package/skills/slopcamera/references/install.md +62 -0
  350. package/skills/slopcamera/references/native-studio.md +31 -0
  351. package/skills/slopcamera/references/reference-led-3d.md +140 -0
  352. package/skills/slopcamera/references/rubber-stamp-examples/poster-example-1.jpg +0 -0
  353. package/skills/slopcamera/references/rubber-stamp-examples/poster-example-2.jpg +0 -0
  354. package/skills/slopcamera/references/rubber-stamp-examples/stamp-style-1.png +0 -0
  355. package/skills/slopcamera/references/rubber-stamp-examples/stamp-style-2.png +0 -0
  356. package/skills/slopcamera/references/rubber-stamp-field-notes.md +144 -0
  357. package/skills/slopcamera/references/vectorization.md +29 -0
  358. package/skills/slopcamera/references/video-projects.md +187 -0
  359. package/skills/slopcamera/references/visual-communication.md +120 -0
  360. package/skills/slopcamera/references/workflows-sdk.md +45 -0
  361. package/skills/slopcamera/scripts/compose-rubber-stamp-field-note.ts +229 -0
  362. package/src/artifacts.ts +106 -0
  363. package/src/assets/fonts/nebula-sans/LICENSE.txt +96 -0
  364. package/src/assets/fonts/nebula-sans/NebulaSans-Bold.otf +0 -0
  365. package/src/assets/fonts/nebula-sans/NebulaSans-Bold.woff2 +0 -0
  366. package/src/assets/fonts/nebula-sans/NebulaSans-Book.otf +0 -0
  367. package/src/assets/fonts/nebula-sans/NebulaSans-Book.woff2 +0 -0
  368. package/src/assets/fonts/nebula-sans/PROVENANCE.md +21 -0
  369. package/src/assets.d.ts +9 -0
  370. package/src/cli.ts +601 -0
  371. package/src/cloud-errors.ts +26 -0
  372. package/src/code/advanced.ts +10 -0
  373. package/src/code/boundary.ts +21 -0
  374. package/src/code/canonical-json.ts +129 -0
  375. package/src/code/compiler.ts +953 -0
  376. package/src/code/contracts.ts +576 -0
  377. package/src/code/define-workflow.ts +334 -0
  378. package/src/code/errors.ts +41 -0
  379. package/src/code/graph-builder.ts +945 -0
  380. package/src/code/index.ts +121 -0
  381. package/src/code/json-snapshot.ts +899 -0
  382. package/src/code/json-utf8.ts +88 -0
  383. package/src/code/portable-builder.ts +136 -0
  384. package/src/code/projection.ts +378 -0
  385. package/src/code/public-operations.ts +450 -0
  386. package/src/code/runtime.ts +580 -0
  387. package/src/code/sha256.ts +36 -0
  388. package/src/config.ts +180 -0
  389. package/src/desktop.ts +253 -0
  390. package/src/fs.ts +10 -0
  391. package/src/generate.ts +704 -0
  392. package/src/host-resource-posix.ts +161 -0
  393. package/src/host-resources.ts +1749 -0
  394. package/src/icons.ts +58 -0
  395. package/src/index.ts +200 -0
  396. package/src/label-layout.ts +130 -0
  397. package/src/layout.ts +413 -0
  398. package/src/lint.ts +127 -0
  399. package/src/mcp/boundary.ts +354 -0
  400. package/src/mcp/index.ts +33 -0
  401. package/src/mcp/server.ts +272 -0
  402. package/src/mcp/tools.ts +963 -0
  403. package/src/mcp/types.ts +71 -0
  404. package/src/operations.ts +775 -0
  405. package/src/parse.ts +861 -0
  406. package/src/process-environment.ts +17 -0
  407. package/src/render.ts +437 -0
  408. package/src/skill-install.ts +49 -0
  409. package/src/spatial-scene/authoring.ts +27 -0
  410. package/src/spatial-scene/camera-track.ts +83 -0
  411. package/src/spatial-scene/contracts.ts +277 -0
  412. package/src/spatial-scene/evaluate.ts +145 -0
  413. package/src/spatial-scene/gltf.ts +558 -0
  414. package/src/spatial-scene/identity.ts +260 -0
  415. package/src/spatial-scene/index.ts +10 -0
  416. package/src/spatial-scene/inspect.ts +68 -0
  417. package/src/spatial-scene/math.ts +280 -0
  418. package/src/spatial-scene/patch.ts +131 -0
  419. package/src/spatial-scene/time.ts +53 -0
  420. package/src/studio/contracts.ts +129 -0
  421. package/src/studio/index.ts +2 -0
  422. package/src/studio/plan.ts +156 -0
  423. package/src/studio/shared.ts +41 -0
  424. package/src/theme.ts +68 -0
  425. package/src/tldr.ts +474 -0
  426. package/src/types.ts +212 -0
  427. package/src/vectorize/archive.ts +101 -0
  428. package/src/vectorize/command.ts +797 -0
  429. package/src/vectorize/index.ts +27 -0
  430. package/src/vectorize/limits.ts +61 -0
  431. package/src/vectorize/metrics.ts +351 -0
  432. package/src/vectorize/pixels.ts +323 -0
  433. package/src/vectorize/supervisor.ts +339 -0
  434. package/src/vectorize/svg.ts +188 -0
  435. package/src/vectorize/tool.ts +542 -0
  436. package/src/vectorize/types.ts +113 -0
  437. package/src/vectorize/vectorize.ts +634 -0
  438. package/src/vectorize/worker-protocol.ts +40 -0
  439. package/src/vectorize/worker.ts +209 -0
  440. package/src/version.ts +1 -0
  441. package/src/workflow.ts +409 -0
@@ -0,0 +1,558 @@
1
+ import { z } from "zod"
2
+ import { createBoundedJsonValueSnapshot, deepFreezeJson } from "../code/json-snapshot.js"
3
+ import { SpatialSceneError } from "./identity.js"
4
+ import { composeTransform, invertTransform, multiplyTransforms, normalizeQuaternion, slerpQuaternion, transformPoint, type Bounds, type Mat4, type Quaternion, type Vec3 } from "./math.js"
5
+
6
+ /** Deliberately closed GLB 2.0 profile. It is not a general glTF loader. */
7
+ export const SPATIAL_GLB_PROFILE = "slopcamera.glb-triangles-trs-pbr-basecolor-v1"
8
+ export const SPATIAL_GLB_LIMITS = Object.freeze({
9
+ bytes: 134_217_728, jsonBytes: 2_097_152, jsonValues: 200_000, jsonDepth: 32,
10
+ nodes: 4096, meshes: 256, primitives: 256, verticesPerPrimitive: 65_536,
11
+ triangles: 100_000, decodedAccessorValues: 2_000_000,
12
+ accessors: 4096, bufferViews: 4096, materials: 256, images: 128,
13
+ imageBytes: 16_777_216, imageTotalBytes: 33_554_432, imagePixels: 67_108_864,
14
+ clips: 256, channels: 4096, animationKeys: 4096, durationSeconds: 3600,
15
+ })
16
+
17
+ const finite = z.number().finite().min(-1e6).max(1e6)
18
+ const index = z.number().int().min(0).max(65_535)
19
+ const unit = z.number().finite().min(0).max(1)
20
+ const vec3 = z.tuple([finite, finite, finite])
21
+ const signedUnit = z.number().finite().min(-1).max(1)
22
+ const quaternion = z.tuple([signedUnit, signedUnit, signedUnit, signedUnit])
23
+ const metadata = { name: z.string().max(1024).optional(), extras: z.unknown().optional(), extensions: z.never().optional() }
24
+ const byteOffset = z.number().int().min(0).max(SPATIAL_GLB_LIMITS.bytes)
25
+ const textureInfo = z.strictObject({ ...metadata, index, texCoord: z.literal(0).optional() })
26
+ const samplerSchema = z.strictObject({
27
+ ...metadata, magFilter: z.union([z.literal(9728), z.literal(9729)]).optional(),
28
+ minFilter: z.union([z.literal(9728), z.literal(9729), z.literal(9984), z.literal(9985), z.literal(9986), z.literal(9987)]).optional(),
29
+ wrapS: z.union([z.literal(33071), z.literal(33648), z.literal(10497)]).default(10497),
30
+ wrapT: z.union([z.literal(33071), z.literal(33648), z.literal(10497)]).default(10497),
31
+ })
32
+ const nodeSchema = z.strictObject({
33
+ ...metadata, children: z.array(index).max(SPATIAL_GLB_LIMITS.nodes).default([]), mesh: index.optional(),
34
+ translation: vec3.optional(), rotation: quaternion.optional(), scale: vec3.optional(),
35
+ matrix: z.array(finite).length(16).optional(),
36
+ })
37
+ const accessorSchema = z.strictObject({
38
+ ...metadata, bufferView: index, byteOffset: byteOffset.default(0),
39
+ componentType: z.union([z.literal(5121), z.literal(5123), z.literal(5125), z.literal(5126)]),
40
+ normalized: z.boolean().default(false), count: z.number().int().min(1).max(SPATIAL_GLB_LIMITS.triangles * 3),
41
+ type: z.enum(["SCALAR", "VEC2", "VEC3", "VEC4"]), min: z.array(finite).min(1).max(4).optional(), max: z.array(finite).min(1).max(4).optional(),
42
+ })
43
+ const gltfSchema = z.strictObject({
44
+ ...metadata,
45
+ asset: z.strictObject({ version: z.literal("2.0"), minVersion: z.literal("2.0").optional(), generator: z.string().max(1024).optional(), copyright: z.string().max(4096).optional(), extras: z.unknown().optional(), extensions: z.never().optional() }),
46
+ extensionsUsed: z.array(z.never()).optional(), extensionsRequired: z.array(z.never()).optional(),
47
+ buffers: z.array(z.strictObject({ ...metadata, byteLength: z.number().int().min(1).max(SPATIAL_GLB_LIMITS.bytes) })).length(1),
48
+ bufferViews: z.array(z.strictObject({ ...metadata, buffer: z.literal(0), byteOffset: byteOffset.default(0), byteLength: z.number().int().min(1).max(SPATIAL_GLB_LIMITS.bytes), byteStride: z.number().int().min(4).max(252).optional(), target: z.union([z.literal(34962), z.literal(34963)]).optional() })).max(SPATIAL_GLB_LIMITS.bufferViews),
49
+ accessors: z.array(accessorSchema).max(SPATIAL_GLB_LIMITS.accessors),
50
+ scene: index.optional(), scenes: z.array(z.strictObject({ ...metadata, nodes: z.array(index).min(1).max(SPATIAL_GLB_LIMITS.nodes) })).min(1).max(128),
51
+ nodes: z.array(nodeSchema).min(1).max(SPATIAL_GLB_LIMITS.nodes),
52
+ meshes: z.array(z.strictObject({ ...metadata, primitives: z.array(z.strictObject({
53
+ ...metadata, attributes: z.strictObject({ POSITION: index, NORMAL: index.optional(), TEXCOORD_0: index.optional() }),
54
+ indices: index.optional(), material: index.optional(), mode: z.literal(4).default(4),
55
+ })).min(1).max(SPATIAL_GLB_LIMITS.primitives) })).min(1).max(SPATIAL_GLB_LIMITS.meshes),
56
+ materials: z.array(z.strictObject({
57
+ ...metadata, pbrMetallicRoughness: z.strictObject({ ...metadata, baseColorFactor: z.tuple([unit, unit, unit, unit]).default([1, 1, 1, 1]), metallicFactor: unit.default(1), roughnessFactor: unit.default(1), baseColorTexture: textureInfo.optional() }).optional(),
58
+ alphaMode: z.enum(["OPAQUE", "MASK", "BLEND"]).default("OPAQUE"), alphaCutoff: unit.default(0.5), doubleSided: z.boolean().default(false),
59
+ emissiveFactor: z.tuple([z.literal(0), z.literal(0), z.literal(0)]).optional(),
60
+ })).max(SPATIAL_GLB_LIMITS.materials).default([]),
61
+ images: z.array(z.strictObject({ ...metadata, bufferView: index, mimeType: z.enum(["image/png", "image/jpeg"]) })).max(SPATIAL_GLB_LIMITS.images).default([]),
62
+ textures: z.array(z.strictObject({ ...metadata, source: index, sampler: index.optional() })).max(SPATIAL_GLB_LIMITS.images).default([]),
63
+ samplers: z.array(samplerSchema).max(SPATIAL_GLB_LIMITS.images).default([]),
64
+ animations: z.array(z.strictObject({ ...metadata,
65
+ samplers: z.array(z.strictObject({ ...metadata, input: index, output: index, interpolation: z.enum(["STEP", "LINEAR"]).default("LINEAR") })).min(1).max(SPATIAL_GLB_LIMITS.channels),
66
+ channels: z.array(z.strictObject({ ...metadata, sampler: index, target: z.strictObject({ ...metadata, node: index, path: z.enum(["translation", "rotation", "scale"]) }) })).min(1).max(SPATIAL_GLB_LIMITS.channels),
67
+ })).max(SPATIAL_GLB_LIMITS.clips).default([]),
68
+ })
69
+ type Gltf = z.infer<typeof gltfSchema>
70
+ type Accessor = z.infer<typeof accessorSchema>
71
+ type Sampler = z.infer<typeof samplerSchema>
72
+
73
+ export interface SpatialGlbSampler {
74
+ readonly wrapS: 33071 | 33648 | 10497
75
+ readonly wrapT: 33071 | 33648 | 10497
76
+ readonly magFilter?: 9728 | 9729
77
+ readonly minFilter?: 9728 | 9729 | 9984 | 9985 | 9986 | 9987
78
+ }
79
+ export interface SpatialGlbMaterial {
80
+ readonly baseColorLinear: readonly [number, number, number, number]
81
+ readonly metalness: number
82
+ readonly roughness: number
83
+ readonly alphaMode: "OPAQUE" | "MASK" | "BLEND"
84
+ readonly alphaCutoff: number
85
+ readonly doubleSided: boolean
86
+ readonly baseColorTexture?: { readonly imageIndex: number; readonly sampler: SpatialGlbSampler }
87
+ }
88
+ export interface SpatialGlbPrimitive {
89
+ readonly positions: readonly number[]
90
+ readonly normals?: readonly number[]
91
+ readonly uvs?: readonly number[]
92
+ readonly indices?: readonly number[]
93
+ readonly matrix: Mat4
94
+ readonly sourceNodeIndex: number
95
+ readonly sourcePrimitiveIndex: number
96
+ readonly bounds: Bounds
97
+ readonly material?: SpatialGlbMaterial
98
+ }
99
+ export interface SpatialGlbImage {
100
+ readonly imageIndex: number
101
+ readonly mimeType: "image/png" | "image/jpeg"
102
+ readonly width: number
103
+ readonly height: number
104
+ /** A fresh detached copy on each evaluation; the host must fully decode before admission. */
105
+ readonly bytes: Uint8Array
106
+ }
107
+ export interface SpatialGlbGeometry {
108
+ readonly profile: typeof SPATIAL_GLB_PROFILE
109
+ readonly primitives: readonly SpatialGlbPrimitive[]
110
+ readonly images: readonly SpatialGlbImage[]
111
+ readonly bounds: Bounds
112
+ readonly sourceTimeSeconds: number | null
113
+ }
114
+ export interface SpatialGlbEvaluateOptions {
115
+ readonly metersPerUnit: number
116
+ readonly sourceUp: "x" | "y" | "z"
117
+ readonly nodeIndex?: number
118
+ readonly materialMode?: "source" | "entity"
119
+ readonly timeUs: number
120
+ readonly clip?: { readonly index: number; readonly offsetUs: number; readonly playback: "once" | "loop" | "freeze" }
121
+ }
122
+ const optionsSchema = z.strictObject({
123
+ metersPerUnit: z.number().finite().min(0.000001).max(1e6), sourceUp: z.enum(["x", "y", "z"]), nodeIndex: index.optional(), materialMode: z.enum(["source", "entity"]).default("entity"),
124
+ timeUs: z.number().int().min(0).max(3_600_000_000),
125
+ clip: z.strictObject({ index, offsetUs: z.number().int().min(0).max(3_600_000_000), playback: z.enum(["once", "loop", "freeze"]) }).optional(),
126
+ })
127
+
128
+ function fail(message: string, path = "glb"): never { throw new SpatialSceneError("invalid-data", `${SPATIAL_GLB_PROFILE}: ${message}`, path) }
129
+ function at<T>(array: readonly T[], index: number, path: string): T {
130
+ return array[index] ?? fail(`Missing index ${index}.`, path)
131
+ }
132
+ function schemaValue<S extends z.ZodType>(schema: S, input: unknown, name: string): z.infer<S> {
133
+ const captured = createBoundedJsonValueSnapshot(input, SPATIAL_GLB_LIMITS.jsonBytes, name, { maximumDepth: SPATIAL_GLB_LIMITS.jsonDepth, maximumValues: SPATIAL_GLB_LIMITS.jsonValues })
134
+ const result = schema.safeParse(captured.value)
135
+ if (!result.success) {
136
+ const issue = result.error.issues[0]
137
+ return fail(`Unsupported or invalid field: ${issue?.message ?? "invalid data"}.`, `${name}.${issue?.path.join(".") ?? ""}`)
138
+ }
139
+ return result.data
140
+ }
141
+ function normalizedRotation(value: readonly number[], path: string): Quaternion {
142
+ if (value.length !== 4 || Math.abs(Math.hypot(...value) - 1) > 1e-5) fail("Rotation must be a unit XYZW quaternion.", path)
143
+ return normalizeQuaternion(value as Quaternion)
144
+ }
145
+ function safeMatrix(value: Mat4, path: string): Mat4 {
146
+ try { invertTransform(value) } catch { fail("Node transform must be an invertible affine matrix.", path) }
147
+ return value
148
+ }
149
+ function nodeTransform(node: { readonly translation?: Vec3 | undefined; readonly rotation?: Quaternion | undefined; readonly scale?: Vec3 | undefined; readonly matrix?: readonly number[] | undefined }, path: string): Mat4 {
150
+ if (node.matrix) {
151
+ if (node.translation || node.rotation || node.scale) fail("Node matrix and TRS cannot be combined.", path)
152
+ const matrix = safeMatrix(Object.freeze([...node.matrix]) as Mat4, path)
153
+ // glTF node matrices must decompose into TRS. Reject shear instead of changing it.
154
+ const axes = [[matrix[0], matrix[1], matrix[2]], [matrix[4], matrix[5], matrix[6]], [matrix[8], matrix[9], matrix[10]]]
155
+ for (let a = 0; a < 3; a++) for (let b = a + 1; b < 3; b++) {
156
+ const left = axes[a]!, right = axes[b]!
157
+ const dot = left.reduce((sum, value, i) => sum + value * right[i]!, 0)
158
+ if (Math.abs(dot) > Math.hypot(...left) * Math.hypot(...right) * 1e-6) fail("Node matrix contains unsupported shear.", path)
159
+ }
160
+ return matrix
161
+ }
162
+ const rotation = normalizedRotation(node.rotation ?? [0, 0, 0, 1], path)
163
+ return safeMatrix(composeTransform({ position: node.translation ?? [0, 0, 0], rotation, scale: node.scale ?? [1, 1, 1] }), path)
164
+ }
165
+
166
+ function imageHeader(bytes: Uint8Array, mimeType: SpatialGlbImage["mimeType"]): { width: number; height: number } {
167
+ const data = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength)
168
+ let width = 0, height = 0
169
+ if (mimeType === "image/png") {
170
+ if (bytes.length < 45 || [137, 80, 78, 71, 13, 10, 26, 10].some((value, i) => bytes[i] !== value)) fail("Embedded image MIME does not match a PNG signature.")
171
+ let cursor = 8, chunks = 0, hasData = false, ended = false
172
+ while (cursor < bytes.length) {
173
+ if (++chunks > 65_536 || cursor + 12 > bytes.length) fail("Malformed PNG chunk envelope.")
174
+ const length = data.getUint32(cursor), type = data.getUint32(cursor + 4)
175
+ if (length > bytes.length - cursor - 12) fail("PNG chunk exceeds its image view.")
176
+ if (cursor === 8) {
177
+ if (type !== 0x49484452 || length !== 13) fail("PNG must begin with IHDR.")
178
+ width = data.getUint32(cursor + 8); height = data.getUint32(cursor + 12)
179
+ } else if (type === 0x49484452) fail("Duplicate PNG IHDR.")
180
+ if (type === 0x6163544c) fail("Animated PNG textures are unsupported.")
181
+ if (type === 0x49444154) hasData = true
182
+ cursor += 12 + length
183
+ if (type === 0x49454e44) {
184
+ if (length !== 0 || cursor !== bytes.length) fail("PNG IEND must end its image view.")
185
+ ended = true; break
186
+ }
187
+ }
188
+ if (!hasData || !ended) fail("PNG requires IDAT and IEND chunks.")
189
+ } else {
190
+ if (bytes.length < 10 || bytes[0] !== 0xff || bytes[1] !== 0xd8 || bytes.at(-2) !== 0xff || bytes.at(-1) !== 0xd9) fail("Embedded image MIME does not match a complete JPEG envelope.")
191
+ let cursor = 2, segments = 0
192
+ while (cursor + 4 <= bytes.length) {
193
+ if (++segments > 65_536 || bytes[cursor++] !== 0xff) fail("Malformed JPEG marker.")
194
+ while (bytes[cursor] === 0xff) cursor++
195
+ const marker = bytes[cursor++]!
196
+ if (marker === 0xda || marker === 0xd9) break
197
+ if (marker === 0 || marker === 0xd8 || (marker >= 0xd0 && marker <= 0xd7)) fail("Unexpected JPEG standalone marker.")
198
+ if (cursor + 2 > bytes.length) fail("Truncated JPEG segment.")
199
+ const length = data.getUint16(cursor)
200
+ if (length < 2 || cursor + length > bytes.length) fail("JPEG segment exceeds its image view.")
201
+ if ([0xc0, 0xc1, 0xc2].includes(marker)) {
202
+ if (width !== 0 || length < 8 || bytes[cursor + 2] !== 8 || ![1, 3].includes(bytes[cursor + 7]!)) fail("JPEG requires one 8-bit grayscale or RGB frame.")
203
+ height = data.getUint16(cursor + 3); width = data.getUint16(cursor + 5)
204
+ } else if (marker >= 0xc0 && marker <= 0xcf && ![0xc4, 0xc8, 0xcc].includes(marker)) fail("JPEG frame encoding is unsupported.")
205
+ cursor += length
206
+ }
207
+ }
208
+ if (width < 1 || height < 1 || width > 8192 || height > 8192 || width * height > SPATIAL_GLB_LIMITS.imagePixels) fail("Embedded image dimensions exceed the decoded pixel profile.")
209
+ return { width, height }
210
+ }
211
+
212
+ interface DecodedAccessor { readonly source: Accessor; readonly values: readonly number[]; readonly components: number }
213
+ interface PrimitiveData { readonly positions: readonly number[]; readonly normals?: readonly number[]; readonly uvs?: readonly number[]; readonly indices?: readonly number[]; readonly material: SpatialGlbMaterial }
214
+ interface ModelState {
215
+ readonly document: Gltf
216
+ readonly accessors: readonly DecodedAccessor[]
217
+ readonly meshPrimitives: readonly (readonly PrimitiveData[])[]
218
+ readonly images: readonly SpatialGlbImage[]
219
+ readonly parents: readonly (number | null)[]
220
+ readonly order: readonly number[]
221
+ readonly reachable: ReadonlySet<number>
222
+ readonly clipDurations: readonly number[]
223
+ }
224
+
225
+ function readAccessors(document: Gltf, binary: Uint8Array): readonly DecodedAccessor[] {
226
+ let totalValues = 0
227
+ const data = new DataView(binary.buffer, binary.byteOffset, binary.byteLength)
228
+ for (const [index, view] of document.bufferViews.entries()) {
229
+ if (view.byteOffset + view.byteLength > document.buffers[0]!.byteLength) fail("Buffer view exceeds the declared BIN payload.", `bufferViews.${index}`)
230
+ if (view.byteStride !== undefined && view.byteStride % 4 !== 0) fail("Vertex stride must be a multiple of four.", `bufferViews.${index}`)
231
+ }
232
+ return Object.freeze(document.accessors.map((accessor, index) => {
233
+ const path = `accessors.${index}`
234
+ const view = at(document.bufferViews, accessor.bufferView, path)
235
+ const bytes = accessor.componentType === 5121 ? 1 : accessor.componentType === 5123 ? 2 : 4
236
+ const components = accessor.type === "SCALAR" ? 1 : Number(accessor.type.slice(3))
237
+ const stride = view.byteStride ?? components * bytes
238
+ if (accessor.byteOffset % bytes !== 0 || (view.byteOffset + accessor.byteOffset) % bytes !== 0 || stride < components * bytes || stride % bytes !== 0) fail("Accessor alignment or stride is invalid.", path)
239
+ if (accessor.byteOffset + (accessor.count - 1) * stride + components * bytes > view.byteLength) fail("Accessor exceeds its buffer view.", path)
240
+ if (accessor.normalized && (accessor.componentType === 5125 || accessor.componentType === 5126)) fail("Only unsigned byte/short UVs support normalized storage in this profile.", path)
241
+ totalValues += accessor.count * components
242
+ if (totalValues > SPATIAL_GLB_LIMITS.decodedAccessorValues) fail("Decoded accessor budget exceeded.", path)
243
+ const values: number[] = []
244
+ const low = new Array<number>(components).fill(Infinity), high = new Array<number>(components).fill(-Infinity)
245
+ for (let element = 0; element < accessor.count; element++) for (let component = 0; component < components; component++) {
246
+ const offset = view.byteOffset + accessor.byteOffset + element * stride + component * bytes
247
+ const raw = accessor.componentType === 5121 ? data.getUint8(offset) : accessor.componentType === 5123 ? data.getUint16(offset, true) : accessor.componentType === 5125 ? data.getUint32(offset, true) : data.getFloat32(offset, true)
248
+ if (!Number.isFinite(raw)) fail("Accessor contains nonfinite data.", path)
249
+ low[component] = Math.min(low[component]!, raw); high[component] = Math.max(high[component]!, raw)
250
+ values.push(accessor.normalized ? raw / (accessor.componentType === 5121 ? 255 : 65535) : raw)
251
+ }
252
+ for (const [declared, computed, label] of [[accessor.min, low, "min"], [accessor.max, high, "max"]] as const) {
253
+ if (declared !== undefined && (declared.length !== components || declared.some((value, component) => Math.abs(value - computed[component]!) > 1e-6 * Math.max(1, Math.abs(value))))) fail(`Accessor ${label} does not match decoded values.`, path)
254
+ }
255
+ return Object.freeze({ source: accessor, components, values: Object.freeze(values) })
256
+ }))
257
+ }
258
+
259
+ function validateViewRoles(document: Gltf): void {
260
+ const roles = new Map<number, string>(), vertexAccessors = new Map<number, Set<number>>()
261
+ const assign = (viewIndex: number, role: string) => {
262
+ const previous = roles.get(viewIndex)
263
+ if (previous !== undefined && previous !== role) fail(`Buffer view mixes ${previous} and ${role} data.`, `bufferViews.${viewIndex}`)
264
+ roles.set(viewIndex, role)
265
+ }
266
+ const accessor = (index: number, role: string) => {
267
+ const source = at(document.accessors, index, "accessors")
268
+ assign(source.bufferView, role)
269
+ if (role === "vertex") {
270
+ const ids = vertexAccessors.get(source.bufferView) ?? new Set<number>()
271
+ ids.add(index); vertexAccessors.set(source.bufferView, ids)
272
+ }
273
+ }
274
+ for (const mesh of document.meshes) for (const primitive of mesh.primitives) {
275
+ for (const index of Object.values(primitive.attributes)) if (index !== undefined) accessor(index, "vertex")
276
+ if (primitive.indices !== undefined) accessor(primitive.indices, "index")
277
+ }
278
+ for (const clip of document.animations) for (const sampler of clip.samplers) { accessor(sampler.input, "animation"); accessor(sampler.output, "animation") }
279
+ for (const image of document.images) assign(image.bufferView, "image")
280
+ for (const [viewIndex, ids] of vertexAccessors) if (ids.size > 1 && at(document.bufferViews, viewIndex, "bufferViews").byteStride === undefined) fail("Shared vertex-attribute views require an explicit stride.", `bufferViews.${viewIndex}`)
281
+ }
282
+
283
+ function cleanSampler(sampler: Sampler | undefined): SpatialGlbSampler {
284
+ return Object.freeze({ wrapS: sampler?.wrapS ?? 10497, wrapT: sampler?.wrapT ?? 10497,
285
+ ...(sampler?.magFilter === undefined ? {} : { magFilter: sampler.magFilter }), ...(sampler?.minFilter === undefined ? {} : { minFilter: sampler.minFilter }) })
286
+ }
287
+ function materials(document: Gltf): readonly SpatialGlbMaterial[] {
288
+ for (const texture of document.textures) {
289
+ at(document.images, texture.source, "textures.source")
290
+ if (texture.sampler !== undefined) at(document.samplers, texture.sampler, "textures.sampler")
291
+ }
292
+ return deepFreezeJson(document.materials.map(material => {
293
+ const pbr = material.pbrMetallicRoughness
294
+ const texture = pbr?.baseColorTexture === undefined ? undefined : at(document.textures, pbr.baseColorTexture.index, "baseColorTexture")
295
+ return {
296
+ baseColorLinear: pbr?.baseColorFactor ?? [1, 1, 1, 1] as const, metalness: pbr?.metallicFactor ?? 1, roughness: pbr?.roughnessFactor ?? 1,
297
+ alphaMode: material.alphaMode, alphaCutoff: material.alphaCutoff, doubleSided: material.doubleSided,
298
+ ...(texture === undefined ? {} : { baseColorTexture: { imageIndex: texture.source, sampler: cleanSampler(texture.sampler === undefined ? undefined : document.samplers[texture.sampler]) } }),
299
+ }
300
+ }))
301
+ }
302
+
303
+ function readMeshes(document: Gltf, accessors: readonly DecodedAccessor[]): readonly (readonly PrimitiveData[])[] {
304
+ const sources = materials(document)
305
+ const defaultMaterial: SpatialGlbMaterial = { baseColorLinear: [1, 1, 1, 1], metalness: 1, roughness: 1, alphaMode: "OPAQUE", alphaCutoff: 0.5, doubleSided: false }
306
+ let primitiveCount = 0, triangles = 0
307
+ return deepFreezeJson(document.meshes.map((mesh, meshIndex) => mesh.primitives.map((primitive, primitiveIndex) => {
308
+ const path = `meshes.${meshIndex}.primitives.${primitiveIndex}`
309
+ if (++primitiveCount > SPATIAL_GLB_LIMITS.primitives) fail("Source primitive count exceeds this profile.", path)
310
+ const positions = at(accessors, primitive.attributes.POSITION, path)
311
+ const normals = primitive.attributes.NORMAL === undefined ? undefined : at(accessors, primitive.attributes.NORMAL, path)
312
+ const uvs = primitive.attributes.TEXCOORD_0 === undefined ? undefined : at(accessors, primitive.attributes.TEXCOORD_0, path)
313
+ const indices = primitive.indices === undefined ? undefined : at(accessors, primitive.indices, path)
314
+ if (positions.source.type !== "VEC3" || positions.source.componentType !== 5126 || positions.source.normalized || positions.source.min === undefined || positions.source.max === undefined || positions.source.count > SPATIAL_GLB_LIMITS.verticesPerPrimitive) fail("POSITION requires bounded float32 VEC3 with declared min/max.", path)
315
+ for (const attribute of [positions, normals, uvs]) if (attribute !== undefined) {
316
+ const view = document.bufferViews[attribute.source.bufferView]!
317
+ if ((view.byteOffset + attribute.source.byteOffset) % 4 !== 0 || attribute.source.byteOffset % 4 !== 0 || (view.target !== undefined && view.target !== 34962)) fail("Vertex attributes require four-byte alignment and ARRAY_BUFFER target.", path)
318
+ const componentBytes = attribute.source.componentType === 5121 ? 1 : attribute.source.componentType === 5123 ? 2 : 4
319
+ if ((view.byteStride ?? attribute.components * componentBytes) % 4 !== 0) fail("Every vertex attribute element must remain four-byte aligned.", path)
320
+ if (attribute.source.count !== positions.source.count || attribute.values.some(value => Math.abs(value) > 1e6)) fail("Vertex attributes require matching counts and bounded coordinates.", path)
321
+ }
322
+ if (normals) {
323
+ if (normals.source.type !== "VEC3" || normals.source.componentType !== 5126 || normals.source.normalized) fail("NORMAL requires float32 VEC3.", path)
324
+ for (let i = 0; i < normals.values.length; i += 3) if (Math.abs(Math.hypot(...normals.values.slice(i, i + 3)) - 1) > 1e-4) fail("Normals must be unit vectors.", path)
325
+ }
326
+ if (uvs && (uvs.source.type !== "VEC2" || (uvs.source.componentType !== 5126 && !([5121, 5123].includes(uvs.source.componentType) && uvs.source.normalized)))) fail("TEXCOORD_0 requires float32 or normalized unsigned byte/short VEC2.", path)
327
+ if (indices) {
328
+ const view = document.bufferViews[indices.source.bufferView]!
329
+ if (indices.source.type !== "SCALAR" || ![5121, 5123, 5125].includes(indices.source.componentType) || indices.source.normalized || view.byteStride !== undefined || (view.target !== undefined && view.target !== 34963)) fail("Triangle indices require tightly packed unsigned scalar storage.", path)
330
+ const restart = indices.source.componentType === 5121 ? 255 : indices.source.componentType === 5123 ? 65535 : 0xffffffff
331
+ if (indices.values.some(value => value >= positions.source.count || value === restart)) fail("Triangle index is out of range or reserved for primitive restart.", path)
332
+ }
333
+ const vertices = indices?.values.length ?? positions.source.count
334
+ if (vertices % 3 !== 0) fail("TRIANGLES require complete index or vertex triples.", path)
335
+ triangles += vertices / 3
336
+ if (triangles > SPATIAL_GLB_LIMITS.triangles) fail("Source triangle budget exceeded.", path)
337
+ const material = primitive.material === undefined ? defaultMaterial : at(sources, primitive.material, path)
338
+ if (material.baseColorTexture && uvs === undefined) fail("Base-color textures require TEXCOORD_0.", path)
339
+ return { positions: positions.values, ...(normals === undefined ? {} : { normals: normals.values }), ...(uvs === undefined ? {} : { uvs: uvs.values }), ...(indices === undefined ? {} : { indices: indices.values }), material }
340
+ })))
341
+ }
342
+
343
+ function hierarchy(document: Gltf) {
344
+ const parents: (number | null)[] = document.nodes.map(() => null)
345
+ for (const [index, node] of document.nodes.entries()) {
346
+ nodeTransform(node, `nodes.${index}`)
347
+ if (node.mesh !== undefined) at(document.meshes, node.mesh, `nodes.${index}.mesh`)
348
+ const seen = new Set<number>()
349
+ for (const child of node.children) {
350
+ at(document.nodes, child, `nodes.${index}.children`)
351
+ if (seen.has(child) || parents[child] !== null) fail("Nodes may have only one parent and unique child references.", `nodes.${index}`)
352
+ seen.add(child); parents[child] = index
353
+ }
354
+ }
355
+ const roots = document.nodes.map((_, index) => index).filter(index => parents[index] === null)
356
+ const order: number[] = [], seen = new Set<number>(), pending = [...roots]
357
+ while (pending.length) {
358
+ const index = pending.pop()!
359
+ if (seen.has(index)) fail("Node hierarchy contains a cycle.")
360
+ seen.add(index); order.push(index); pending.push(...document.nodes[index]!.children)
361
+ }
362
+ if (seen.size !== document.nodes.length) fail("Node hierarchy contains a cycle.")
363
+ for (const scene of document.scenes) {
364
+ const unique = new Set<number>()
365
+ for (const index of scene.nodes) {
366
+ at(document.nodes, index, "scenes.nodes")
367
+ if (parents[index] !== null || unique.has(index)) fail("Scene roots must be unique nodes without parents.")
368
+ unique.add(index)
369
+ }
370
+ }
371
+ if (document.scene === undefined && document.scenes.length !== 1) fail("Multiple scenes require an explicit glTF default scene.")
372
+ const scene = at(document.scenes, document.scene ?? 0, "scene")
373
+ const reachable = new Set<number>(), visit = [...scene.nodes]
374
+ while (visit.length) { const index = visit.pop()!; reachable.add(index); visit.push(...document.nodes[index]!.children) }
375
+ return { parents: Object.freeze(parents), order: Object.freeze(order), reachable }
376
+ }
377
+
378
+ function animationDurations(document: Gltf, accessors: readonly DecodedAccessor[]): readonly number[] {
379
+ let totalChannels = 0
380
+ return Object.freeze(document.animations.map((clip, clipIndex) => {
381
+ totalChannels += clip.channels.length
382
+ if (totalChannels > SPATIAL_GLB_LIMITS.channels) fail("Animation channel budget exceeded.")
383
+ let duration = 0
384
+ for (const sampler of clip.samplers) {
385
+ const input = at(accessors, sampler.input, "animation input"), output = at(accessors, sampler.output, "animation output")
386
+ if (input.source.type !== "SCALAR" || input.source.componentType !== 5126 || input.source.normalized || input.source.count > SPATIAL_GLB_LIMITS.animationKeys || input.source.min === undefined || input.source.max === undefined) fail("Animation input requires bounded float32 scalar seconds with min/max.")
387
+ if (output.source.componentType !== 5126 || output.source.normalized || output.source.count !== input.source.count) fail("Animation output must be float32 with matching key count.")
388
+ for (const accessor of [input, output]) {
389
+ const view = document.bufferViews[accessor.source.bufferView]!
390
+ if (view.byteStride !== undefined || view.target !== undefined) fail("Animation data must be tightly packed without a GPU buffer target.")
391
+ }
392
+ let previous = -1
393
+ for (const time of input.values) {
394
+ if (time < 0 || time <= previous || time > SPATIAL_GLB_LIMITS.durationSeconds) fail("Animation times must be strictly ordered nonnegative seconds within the duration profile.")
395
+ previous = time
396
+ }
397
+ duration = Math.max(duration, previous)
398
+ }
399
+ const writers = new Set<string>()
400
+ for (const channel of clip.channels) {
401
+ const node = at(document.nodes, channel.target.node, "animation target")
402
+ if (node.matrix) fail("Animated nodes must use TRS, never a matrix.")
403
+ const key = `${channel.target.node}:${channel.target.path}`
404
+ if (writers.has(key)) fail("Animation has multiple writers for one node property.", `animations.${clipIndex}`)
405
+ writers.add(key)
406
+ const sampler = at(clip.samplers, channel.sampler, "animation sampler")
407
+ const output = accessors[sampler.output]!
408
+ if (output.source.type !== (channel.target.path === "rotation" ? "VEC4" : "VEC3")) fail("Animation output arity does not match its target property.")
409
+ if (channel.target.path === "rotation") for (let i = 0; i < output.values.length; i += 4) normalizedRotation(output.values.slice(i, i + 4), "animation rotation")
410
+ else if (output.values.some(value => Math.abs(value) > 1e6 || (channel.target.path === "scale" && value === 0))) fail("Animation transform values are unbounded or singular.")
411
+ if (channel.target.path === "scale" && sampler.interpolation === "LINEAR") for (let i = 3; i < output.values.length; i++) if (Math.sign(output.values[i]!) !== Math.sign(output.values[i - 3]!)) fail("Linear scale animation crosses a singular transform.")
412
+ }
413
+ return duration
414
+ }))
415
+ }
416
+
417
+ /** Opaque model retains decoded authored data; callers cannot mutate its geometry or source images. */
418
+ export class SpatialGlbModel {
419
+ readonly profile = SPATIAL_GLB_PROFILE
420
+ readonly nodeCount: number
421
+ readonly clipDurationsSeconds: readonly number[]
422
+ readonly #state: ModelState
423
+
424
+ private constructor(state: ModelState) {
425
+ this.#state = state
426
+ this.nodeCount = state.document.nodes.length
427
+ this.clipDurationsSeconds = state.clipDurations
428
+ Object.freeze(this)
429
+ }
430
+
431
+ static parse(input: Uint8Array): SpatialGlbModel {
432
+ if (!(input instanceof Uint8Array) || input.byteLength < 28 || input.byteLength > SPATIAL_GLB_LIMITS.bytes || input.buffer instanceof SharedArrayBuffer) fail("Expected bounded, non-shared GLB bytes.")
433
+ const bytes = Uint8Array.from(input)
434
+ const header = new DataView(bytes.buffer)
435
+ if (header.getUint32(0, true) !== 0x46546c67 || header.getUint32(4, true) !== 2 || header.getUint32(8, true) !== bytes.length) fail("Invalid GLB 2.0 header or total length.")
436
+ const jsonLength = header.getUint32(12, true)
437
+ if (header.getUint32(16, true) !== 0x4e4f534a || jsonLength % 4 !== 0 || jsonLength < 4 || jsonLength > SPATIAL_GLB_LIMITS.jsonBytes || 20 + jsonLength + 8 > bytes.length) fail("Expected bounded first JSON chunk and following BIN chunk.")
438
+ const binHeader = 20 + jsonLength, binLength = header.getUint32(binHeader, true)
439
+ if (header.getUint32(binHeader + 4, true) !== 0x004e4942 || binLength % 4 !== 0 || binHeader + 8 + binLength !== bytes.length) fail("Expected exactly one BIN chunk and no trailing chunks.")
440
+ let json: unknown
441
+ try { json = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes.subarray(20, binHeader))) } catch { return fail("GLB JSON must be valid UTF-8 JSON.") }
442
+ const document = schemaValue(gltfSchema, json, "gltf")
443
+ const payloadLength = document.buffers[0]!.byteLength
444
+ if (payloadLength > binLength || binLength - payloadLength > 3) fail("Declared BIN length does not match its padding.")
445
+ const binary = bytes.subarray(binHeader + 8)
446
+ if (binary.subarray(payloadLength).some(byte => byte !== 0)) fail("BIN padding must contain zero bytes.")
447
+ validateViewRoles(document)
448
+ const accessors = readAccessors(document, binary)
449
+ const meshPrimitives = readMeshes(document, accessors)
450
+ const graph = hierarchy(document)
451
+ const clipDurations = animationDurations(document, accessors)
452
+ let totalImageBytes = 0, totalPixels = 0
453
+ const images = document.images.map((image, imageIndex) => {
454
+ const view = at(document.bufferViews, image.bufferView, "images.bufferView")
455
+ if (view.byteStride !== undefined || view.target !== undefined || view.byteLength > SPATIAL_GLB_LIMITS.imageBytes) fail("Embedded images require bounded untargeted byte views.")
456
+ totalImageBytes += view.byteLength
457
+ if (totalImageBytes > SPATIAL_GLB_LIMITS.imageTotalBytes) fail("Embedded image byte budget exceeded.")
458
+ const imageBytes = binary.slice(view.byteOffset, view.byteOffset + view.byteLength)
459
+ const dimensions = imageHeader(imageBytes, image.mimeType)
460
+ totalPixels += dimensions.width * dimensions.height
461
+ if (totalPixels > SPATIAL_GLB_LIMITS.imagePixels) fail("Embedded decoded image pixel budget exceeded.")
462
+ return Object.freeze({ imageIndex, mimeType: image.mimeType, ...dimensions, bytes: imageBytes })
463
+ })
464
+ return new SpatialGlbModel({ document: deepFreezeJson(document), accessors, meshPrimitives, images: Object.freeze(images), ...graph, clipDurations })
465
+ }
466
+
467
+ evaluate(input: SpatialGlbEvaluateOptions): SpatialGlbGeometry {
468
+ const options = schemaValue(optionsSchema, input, "glb evaluation")
469
+ const state = this.#state, document = state.document
470
+ if (options.nodeIndex !== undefined && !state.reachable.has(options.nodeIndex)) fail("Selected node is absent from the default scene.", "nodeIndex")
471
+ let sourceTimeSeconds: number | null = null
472
+ const animated = new Map<number, { translation?: Vec3; rotation?: Quaternion; scale?: Vec3 }>()
473
+ if (options.clip !== undefined) {
474
+ const clip = at(document.animations, options.clip.index, "clip.index")
475
+ const duration = state.clipDurations[options.clip.index]!
476
+ const requested = (options.timeUs + options.clip.offsetUs) / 1_000_000
477
+ if (options.clip.offsetUs / 1_000_000 > duration) fail("Clip source offset exceeds its duration.")
478
+ if (options.clip.playback === "once" && requested > duration) fail("Once clip playback exceeds its duration.")
479
+ sourceTimeSeconds = options.clip.playback === "loop" ? duration === 0 ? 0 : requested % duration : Math.min(requested, duration)
480
+ for (const channel of clip.channels) {
481
+ const sampler = clip.samplers[channel.sampler]!, times = state.accessors[sampler.input]!.values, output = state.accessors[sampler.output]!
482
+ let lower = 0, upper = times.length - 1
483
+ if (sourceTimeSeconds <= times[0]!) upper = 0
484
+ else if (sourceTimeSeconds >= times[upper]!) lower = upper
485
+ else while (upper - lower > 1) { const middle = Math.floor((lower + upper) / 2); if (times[middle]! <= sourceTimeSeconds) lower = middle; else upper = middle }
486
+ const left = output.values.slice(lower * output.components, (lower + 1) * output.components)
487
+ let value: readonly number[] = left
488
+ if (sampler.interpolation === "LINEAR" && upper !== lower) {
489
+ const right = output.values.slice(upper * output.components, (upper + 1) * output.components)
490
+ const t = (sourceTimeSeconds - times[lower]!) / (times[upper]! - times[lower]!)
491
+ value = channel.target.path === "rotation" ? slerpQuaternion(left as unknown as Quaternion, right as unknown as Quaternion, t) : left.map((part, index) => part + (right[index]! - part) * t)
492
+ }
493
+ const pose = animated.get(channel.target.node) ?? {}
494
+ if (channel.target.path === "rotation") pose.rotation = normalizedRotation(value, "evaluated clip rotation")
495
+ else pose[channel.target.path] = value as Vec3
496
+ animated.set(channel.target.node, pose)
497
+ }
498
+ }
499
+ // Source X-up rotates +90° around Z; source Z-up rotates -90° around X.
500
+ const rotation: Quaternion = options.sourceUp === "x" ? [0, 0, Math.SQRT1_2, Math.SQRT1_2] : options.sourceUp === "z" ? [-Math.SQRT1_2, 0, 0, Math.SQRT1_2] : [0, 0, 0, 1]
501
+ const conversion = composeTransform({ position: [0, 0, 0], rotation, scale: [options.metersPerUnit, options.metersPerUnit, options.metersPerUnit] })
502
+ const matrices = new Map<number, Mat4>()
503
+ for (const index of state.order) {
504
+ const node = document.nodes[index]!, overrides = animated.get(index)
505
+ const local = overrides ? nodeTransform({ ...node, ...overrides }, `nodes.${index}`) : nodeTransform(node, `nodes.${index}`)
506
+ const parent = state.parents[index]
507
+ matrices.set(index, multiplyTransforms(parent === null ? conversion : matrices.get(parent!)!, local))
508
+ }
509
+ const selected = new Set<number>()
510
+ if (options.nodeIndex === undefined) for (const index of state.reachable) selected.add(index)
511
+ else {
512
+ const pending = [options.nodeIndex]
513
+ while (pending.length) { const index = pending.pop()!; selected.add(index); pending.push(...document.nodes[index]!.children) }
514
+ }
515
+ const primitives: SpatialGlbPrimitive[] = [], imageIds = new Set<number>()
516
+ let triangles = 0
517
+ for (const sourceNodeIndex of [...selected].sort((a, b) => a - b)) {
518
+ const node = document.nodes[sourceNodeIndex]!
519
+ if (node.mesh === undefined) continue
520
+ const matrix = safeMatrix(matrices.get(sourceNodeIndex)!, `nodes.${sourceNodeIndex}`)
521
+ for (const [sourcePrimitiveIndex, primitive] of state.meshPrimitives[node.mesh]!.entries()) {
522
+ if (primitives.length >= SPATIAL_GLB_LIMITS.primitives) fail("Instanced primitive budget exceeded.")
523
+ triangles += (primitive.indices?.length ?? primitive.positions.length / 3) / 3
524
+ if (triangles > SPATIAL_GLB_LIMITS.triangles) fail("Instanced triangle budget exceeded.")
525
+ const bounds = vertexBounds(primitive.positions, primitive.indices, matrix)
526
+ const { material, ...geometry } = primitive
527
+ if (options.materialMode === "source" && material.baseColorTexture) imageIds.add(material.baseColorTexture.imageIndex)
528
+ primitives.push({ ...geometry, matrix, bounds, sourceNodeIndex, sourcePrimitiveIndex, ...(options.materialMode === "source" ? { material } : {}) })
529
+ }
530
+ }
531
+ if (primitives.length === 0) fail("Selected scene or subtree contains no triangle geometry.")
532
+ const bounds = combineBounds(primitives.map(primitive => primitive.bounds))
533
+ const images = [...imageIds].sort((a, b) => a - b).map(index => { const image = state.images[index]!; return Object.freeze({ ...image, bytes: image.bytes.slice() }) })
534
+ return Object.freeze({ profile: SPATIAL_GLB_PROFILE, primitives: deepFreezeJson(primitives), images: Object.freeze(images), bounds, sourceTimeSeconds })
535
+ }
536
+ }
537
+
538
+ function vertexBounds(positions: readonly number[], indices: readonly number[] | undefined, matrix: Mat4): Bounds {
539
+ const low = [Infinity, Infinity, Infinity], high = [-Infinity, -Infinity, -Infinity]
540
+ const count = indices?.length ?? positions.length / 3
541
+ for (let index = 0; index < count; index++) {
542
+ const offset = (indices?.[index] ?? index) * 3
543
+ const point = transformPoint(matrix, [positions[offset]!, positions[offset + 1]!, positions[offset + 2]!])
544
+ for (let axis = 0; axis < 3; axis++) { low[axis] = Math.min(low[axis]!, point[axis]!); high[axis] = Math.max(high[axis]!, point[axis]!) }
545
+ }
546
+ return deepFreezeJson({ min: low as unknown as Vec3, max: high as unknown as Vec3 })
547
+ }
548
+ function combineBounds(bounds: readonly Bounds[]): Bounds {
549
+ const min = [Infinity, Infinity, Infinity], max = [-Infinity, -Infinity, -Infinity]
550
+ for (const bound of bounds) for (let axis = 0; axis < 3; axis++) { min[axis] = Math.min(min[axis]!, bound.min[axis]!); max[axis] = Math.max(max[axis]!, bound.max[axis]!) }
551
+ return deepFreezeJson({ min: min as unknown as Vec3, max: max as unknown as Vec3 })
552
+ }
553
+
554
+ export function parseSpatialGlb(bytes: Uint8Array): SpatialGlbModel { return SpatialGlbModel.parse(bytes) }
555
+ export function evaluateSpatialGlb(model: SpatialGlbModel, options: SpatialGlbEvaluateOptions): SpatialGlbGeometry {
556
+ if (!(model instanceof SpatialGlbModel)) fail("Evaluation requires a parsed GLB model.")
557
+ return model.evaluate(options)
558
+ }