@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,448 @@
1
+ import type { SourceInterval } from "../contracts/edit";
2
+ import { canonicalJsonSha256 } from "./canonical-json";
3
+
4
+ export const SCENE_SAMPLING_VERSION = "slopcamera-scene-sampling-v1";
5
+
6
+ export const SCENE_BATCH_LIMITS = {
7
+ imageBytes: 6_000_000,
8
+ imageCount: 12,
9
+ sceneCount: 4,
10
+ } as const;
11
+
12
+ const SHA256_PATTERN = /^[a-f0-9]{64}$/u;
13
+ const PERCEPTUAL_HASH_PATTERN = /^[a-f0-9]{16}$/u;
14
+ const REASON_ORDER = ["boundary", "middle", "event", "maximum-gap", "motion"] as const;
15
+
16
+ export type SceneSampleReason = typeof REASON_ORDER[number];
17
+ export type SceneBoundaryKind = "event" | "motion" | "visual";
18
+
19
+ export interface SceneBoundaryCandidate {
20
+ readonly confidence: number;
21
+ readonly kind: SceneBoundaryKind;
22
+ readonly timeUs: number;
23
+ }
24
+
25
+ export interface SceneSamplingInput {
26
+ readonly boundaries: readonly SceneBoundaryCandidate[];
27
+ readonly inputDigest: string;
28
+ readonly maximumSceneDurationUs: number;
29
+ readonly ranges: readonly SourceInterval[];
30
+ }
31
+
32
+ export interface PlannedSceneSample {
33
+ readonly reasons: readonly SceneSampleReason[];
34
+ readonly requestedAssetTimeUs: number;
35
+ readonly sampleId: string;
36
+ readonly sceneId: string;
37
+ }
38
+
39
+ export interface PlannedScene {
40
+ readonly boundaryConfidence: number;
41
+ readonly range: SourceInterval;
42
+ readonly sampleIds: readonly string[];
43
+ readonly sceneId: string;
44
+ }
45
+
46
+ export interface SceneSamplingPlan {
47
+ readonly planDigest: string;
48
+ readonly samples: readonly PlannedSceneSample[];
49
+ readonly samplingVersion: typeof SCENE_SAMPLING_VERSION;
50
+ readonly scenes: readonly PlannedScene[];
51
+ }
52
+
53
+ interface BoundaryPoint {
54
+ readonly confidence: number;
55
+ readonly reasons: readonly SceneSampleReason[];
56
+ readonly timeUs: number;
57
+ }
58
+
59
+ function requireSafeMicroseconds(value: number, label: string, allowZero: boolean): void {
60
+ if (!Number.isSafeInteger(value) || value < (allowZero ? 0 : 1)) {
61
+ throw new TypeError(`${label} must be a ${allowZero ? "nonnegative" : "positive"} safe integer.`);
62
+ }
63
+ }
64
+
65
+ function requireSha256(value: string, label: string): void {
66
+ if (!SHA256_PATTERN.test(value)) throw new TypeError(`${label} must be a lowercase SHA-256 digest.`);
67
+ }
68
+
69
+ function orderedReasons(values: Iterable<SceneSampleReason>): readonly SceneSampleReason[] {
70
+ const selected = new Set(values);
71
+ return REASON_ORDER.filter(reason => selected.has(reason));
72
+ }
73
+
74
+ function normalizeRanges(ranges: readonly SourceInterval[]): readonly SourceInterval[] {
75
+ const ordered = ranges.map((range) => {
76
+ requireSafeMicroseconds(range.startUs, "Scene range startUs", true);
77
+ requireSafeMicroseconds(range.endUs, "Scene range endUs", false);
78
+ if (range.endUs <= range.startUs) throw new TypeError("Scene ranges must be nonempty.");
79
+ return { endUs: range.endUs, startUs: range.startUs };
80
+ }).sort((left, right) => left.startUs - right.startUs || left.endUs - right.endUs);
81
+
82
+ const normalized: SourceInterval[] = [];
83
+ for (const current of ordered) {
84
+ const prior = normalized.at(-1);
85
+ if (prior === undefined || current.startUs > prior.endUs) {
86
+ normalized.push(current);
87
+ continue;
88
+ }
89
+ if (current.startUs < prior.endUs) {
90
+ throw new TypeError("Scene ranges must not overlap.");
91
+ }
92
+ normalized[normalized.length - 1] = { endUs: current.endUs, startUs: prior.startUs };
93
+ }
94
+ return normalized;
95
+ }
96
+
97
+ function candidateReason(kind: SceneBoundaryKind): SceneSampleReason {
98
+ if (kind === "event") return "event";
99
+ if (kind === "motion") return "motion";
100
+ return "boundary";
101
+ }
102
+
103
+ function normalizeBoundaryCandidates(
104
+ boundaries: readonly SceneBoundaryCandidate[],
105
+ range: SourceInterval,
106
+ ): ReadonlyMap<number, BoundaryPoint> {
107
+ const grouped = new Map<number, { confidence: number; reasons: Set<SceneSampleReason> }>();
108
+ for (const boundary of boundaries) {
109
+ requireSafeMicroseconds(boundary.timeUs, "Scene boundary timeUs", true);
110
+ if (!Number.isFinite(boundary.confidence) || boundary.confidence < 0 || boundary.confidence > 1) {
111
+ throw new TypeError("Scene boundary confidence must be between zero and one.");
112
+ }
113
+ if (boundary.timeUs <= range.startUs || boundary.timeUs >= range.endUs) continue;
114
+ const existing = grouped.get(boundary.timeUs) ?? { confidence: 0, reasons: new Set() };
115
+ existing.confidence = Math.max(existing.confidence, boundary.confidence);
116
+ existing.reasons.add("boundary");
117
+ existing.reasons.add(candidateReason(boundary.kind));
118
+ grouped.set(boundary.timeUs, existing);
119
+ }
120
+ return new Map([...grouped.entries()].sort(([left], [right]) => left - right).map(([timeUs, value]) => [
121
+ timeUs,
122
+ { confidence: value.confidence, reasons: orderedReasons(value.reasons), timeUs },
123
+ ]));
124
+ }
125
+
126
+ function boundaryPoints(
127
+ range: SourceInterval,
128
+ candidates: ReadonlyMap<number, BoundaryPoint>,
129
+ maximumSceneDurationUs: number,
130
+ ): readonly BoundaryPoint[] {
131
+ const points = new Map<number, BoundaryPoint>();
132
+ points.set(range.startUs, { confidence: 1, reasons: ["boundary"], timeUs: range.startUs });
133
+ points.set(range.endUs, { confidence: 1, reasons: ["boundary"], timeUs: range.endUs });
134
+ for (const point of candidates.values()) points.set(point.timeUs, point);
135
+
136
+ const anchors = [...points.keys()].sort((left, right) => left - right);
137
+ for (let index = 1; index < anchors.length; index += 1) {
138
+ const prior = anchors[index - 1]!;
139
+ const next = anchors[index]!;
140
+ for (let timeUs = prior + maximumSceneDurationUs; timeUs < next; timeUs += maximumSceneDurationUs) {
141
+ points.set(timeUs, {
142
+ confidence: 0,
143
+ reasons: ["boundary", "maximum-gap"],
144
+ timeUs,
145
+ });
146
+ }
147
+ }
148
+ return [...points.values()].sort((left, right) => left.timeUs - right.timeUs);
149
+ }
150
+
151
+ function sceneId(inputDigest: string, range: SourceInterval): string {
152
+ return `scene_${canonicalJsonSha256({ inputDigest, range, version: SCENE_SAMPLING_VERSION }).slice(0, 24)}`;
153
+ }
154
+
155
+ function sampleId(
156
+ inputDigest: string,
157
+ ownerSceneId: string,
158
+ requestedAssetTimeUs: number,
159
+ reasons: readonly SceneSampleReason[],
160
+ ): string {
161
+ return `sample_${canonicalJsonSha256({
162
+ inputDigest,
163
+ ownerSceneId,
164
+ reasons,
165
+ requestedAssetTimeUs,
166
+ version: SCENE_SAMPLING_VERSION,
167
+ }).slice(0, 24)}`;
168
+ }
169
+
170
+ function samplesForScene(
171
+ inputDigest: string,
172
+ range: SourceInterval,
173
+ ownerSceneId: string,
174
+ startPoint: BoundaryPoint,
175
+ endPoint: BoundaryPoint,
176
+ ): readonly PlannedSceneSample[] {
177
+ const byTime = new Map<number, Set<SceneSampleReason>>();
178
+ const add = (timeUs: number, reasons: readonly SceneSampleReason[]) => {
179
+ const selected = byTime.get(timeUs) ?? new Set<SceneSampleReason>();
180
+ for (const reason of reasons) selected.add(reason);
181
+ byTime.set(timeUs, selected);
182
+ };
183
+
184
+ add(range.startUs, startPoint.reasons);
185
+ add(Math.floor(range.startUs + (range.endUs - range.startUs - 1) / 2), ["middle"]);
186
+ add(range.endUs - 1, endPoint.reasons);
187
+
188
+ return [...byTime.entries()].sort(([left], [right]) => left - right).map(([requestedAssetTimeUs, reasons]) => {
189
+ const normalizedReasons = orderedReasons(reasons);
190
+ return {
191
+ reasons: normalizedReasons,
192
+ requestedAssetTimeUs,
193
+ sampleId: sampleId(inputDigest, ownerSceneId, requestedAssetTimeUs, normalizedReasons),
194
+ sceneId: ownerSceneId,
195
+ };
196
+ });
197
+ }
198
+
199
+ export function planSceneSampling(input: SceneSamplingInput): SceneSamplingPlan {
200
+ requireSha256(input.inputDigest, "Scene inputDigest");
201
+ requireSafeMicroseconds(input.maximumSceneDurationUs, "maximumSceneDurationUs", false);
202
+ const ranges = normalizeRanges(input.ranges);
203
+ const scenes: PlannedScene[] = [];
204
+ const samples: PlannedSceneSample[] = [];
205
+
206
+ for (const range of ranges) {
207
+ const candidates = normalizeBoundaryCandidates(input.boundaries, range);
208
+ const points = boundaryPoints(range, candidates, input.maximumSceneDurationUs);
209
+ for (let index = 1; index < points.length; index += 1) {
210
+ const startPoint = points[index - 1]!;
211
+ const endPoint = points[index]!;
212
+ const sceneRange = { endUs: endPoint.timeUs, startUs: startPoint.timeUs };
213
+ const id = sceneId(input.inputDigest, sceneRange);
214
+ const sceneSamples = samplesForScene(input.inputDigest, sceneRange, id, startPoint, endPoint);
215
+ scenes.push({
216
+ boundaryConfidence: startPoint.confidence,
217
+ range: sceneRange,
218
+ sampleIds: sceneSamples.map(sample => sample.sampleId),
219
+ sceneId: id,
220
+ });
221
+ samples.push(...sceneSamples);
222
+ }
223
+ }
224
+
225
+ const planValue = {
226
+ samples,
227
+ samplingVersion: SCENE_SAMPLING_VERSION as typeof SCENE_SAMPLING_VERSION,
228
+ scenes,
229
+ };
230
+ return { ...planValue, planDigest: canonicalJsonSha256(planValue) };
231
+ }
232
+
233
+ function parsePerceptualHash(value: string): bigint {
234
+ if (!PERCEPTUAL_HASH_PATTERN.test(value)) {
235
+ throw new TypeError("Perceptual hashes must be sixteen lowercase hexadecimal characters.");
236
+ }
237
+ return BigInt(`0x${value}`);
238
+ }
239
+
240
+ export function differenceHash64(grayscale: Uint8Array): string {
241
+ if (grayscale.length !== 9 * 8) {
242
+ throw new TypeError("A 64-bit difference hash requires an eight-row, nine-column grayscale image.");
243
+ }
244
+ let hash = 0n;
245
+ for (let row = 0; row < 8; row += 1) {
246
+ for (let column = 0; column < 8; column += 1) {
247
+ hash <<= 1n;
248
+ const left = grayscale[row * 9 + column]!;
249
+ const right = grayscale[row * 9 + column + 1]!;
250
+ if (left > right) hash |= 1n;
251
+ }
252
+ }
253
+ return hash.toString(16).padStart(16, "0");
254
+ }
255
+
256
+ export function perceptualHashDistance(left: string, right: string): number {
257
+ let difference = parsePerceptualHash(left) ^ parsePerceptualHash(right);
258
+ let distance = 0;
259
+ while (difference !== 0n) {
260
+ difference &= difference - 1n;
261
+ distance += 1;
262
+ }
263
+ return distance;
264
+ }
265
+
266
+ export interface FrameFingerprint {
267
+ readonly perceptualHash: string;
268
+ readonly sampleId: string;
269
+ readonly sha256: string;
270
+ }
271
+
272
+ export interface FrameDeduplication {
273
+ readonly canonicalSampleId: string;
274
+ readonly exactContent: boolean;
275
+ readonly perceptualDistance: number;
276
+ readonly sampleId: string;
277
+ }
278
+
279
+ export function deduplicateFrameFingerprints(
280
+ fingerprints: readonly FrameFingerprint[],
281
+ maximumPerceptualDistance = 0,
282
+ ): readonly FrameDeduplication[] {
283
+ if (!Number.isSafeInteger(maximumPerceptualDistance) || maximumPerceptualDistance < 0 || maximumPerceptualDistance > 64) {
284
+ throw new TypeError("maximumPerceptualDistance must be an integer between zero and 64.");
285
+ }
286
+ const ordered = [...fingerprints].sort((left, right) => left.sampleId.localeCompare(right.sampleId));
287
+ if (new Set(ordered.map(frame => frame.sampleId)).size !== ordered.length) {
288
+ throw new TypeError("Frame fingerprint sample IDs must be unique.");
289
+ }
290
+ for (const frame of ordered) {
291
+ requireSha256(frame.sha256, "Frame sha256");
292
+ parsePerceptualHash(frame.perceptualHash);
293
+ }
294
+
295
+ const prior: FrameFingerprint[] = [];
296
+ const canonicalBySampleId = new Map<string, string>();
297
+ return ordered.map((frame) => {
298
+ const exactMatch = prior.find(candidate => frame.sha256 === candidate.sha256);
299
+ const perceptualMatch = exactMatch === undefined
300
+ ? prior.find(candidate =>
301
+ perceptualHashDistance(frame.perceptualHash, candidate.perceptualHash) <= maximumPerceptualDistance)
302
+ : undefined;
303
+ const match = exactMatch ?? perceptualMatch;
304
+ if (match === undefined) {
305
+ prior.push(frame);
306
+ canonicalBySampleId.set(frame.sampleId, frame.sampleId);
307
+ return {
308
+ canonicalSampleId: frame.sampleId,
309
+ exactContent: true,
310
+ perceptualDistance: 0,
311
+ sampleId: frame.sampleId,
312
+ };
313
+ }
314
+ const distance = perceptualHashDistance(frame.perceptualHash, match.perceptualHash);
315
+ const canonicalSampleId = canonicalBySampleId.get(match.sampleId);
316
+ if (canonicalSampleId === undefined) throw new TypeError("Frame deduplication state is inconsistent.");
317
+ prior.push(frame);
318
+ canonicalBySampleId.set(frame.sampleId, canonicalSampleId);
319
+ return {
320
+ canonicalSampleId,
321
+ exactContent: exactMatch !== undefined,
322
+ perceptualDistance: distance,
323
+ sampleId: frame.sampleId,
324
+ };
325
+ });
326
+ }
327
+
328
+ export interface BatchableSceneFrame {
329
+ readonly actualAssetTimeUs: number;
330
+ readonly bytes: number;
331
+ readonly sampleId: string;
332
+ readonly sha256: string;
333
+ }
334
+
335
+ export interface BatchableScene {
336
+ readonly frames: readonly BatchableSceneFrame[];
337
+ readonly rangeStartUs: number;
338
+ readonly sceneId: string;
339
+ }
340
+
341
+ export interface PlannedSceneBatch {
342
+ readonly batch: {
343
+ readonly batchKey: string;
344
+ readonly errorCode: null;
345
+ readonly imageBytes: number;
346
+ readonly imageCount: number;
347
+ readonly sceneIds: readonly string[];
348
+ readonly state: "planned";
349
+ };
350
+ readonly frames: readonly (BatchableSceneFrame & { readonly sceneId: string })[];
351
+ }
352
+
353
+ function validateBatchableScene(scene: BatchableScene): void {
354
+ requireSafeMicroseconds(scene.rangeStartUs, "Scene rangeStartUs", true);
355
+ if (!/^scene_[a-z0-9][a-z0-9_-]{7,63}$/u.test(scene.sceneId)) {
356
+ throw new TypeError("Batch scene IDs must be opaque scene identifiers.");
357
+ }
358
+ if (scene.frames.length < 1 || scene.frames.length > SCENE_BATCH_LIMITS.imageCount) {
359
+ throw new TypeError("Each batchable scene must have between one and twelve frames.");
360
+ }
361
+ let bytes = 0;
362
+ const sampleIds = new Set<string>();
363
+ for (const frame of scene.frames) {
364
+ if (!/^sample_[a-z0-9][a-z0-9_-]{7,63}$/u.test(frame.sampleId)) {
365
+ throw new TypeError("Batch sample IDs must be opaque sample identifiers.");
366
+ }
367
+ if (sampleIds.has(frame.sampleId)) throw new TypeError("Batch sample IDs must be unique within a scene.");
368
+ sampleIds.add(frame.sampleId);
369
+ requireSha256(frame.sha256, "Batch frame sha256");
370
+ requireSafeMicroseconds(frame.actualAssetTimeUs, "Batch frame actualAssetTimeUs", true);
371
+ requireSafeMicroseconds(frame.bytes, "Batch frame bytes", false);
372
+ bytes += frame.bytes;
373
+ }
374
+ if (bytes > SCENE_BATCH_LIMITS.imageBytes) {
375
+ throw new TypeError("A scene exceeds the six-megabyte gateway batch limit.");
376
+ }
377
+ }
378
+
379
+ function makeBatch(
380
+ contextDigest: string,
381
+ scenes: readonly BatchableScene[],
382
+ ): PlannedSceneBatch {
383
+ const frames = scenes.flatMap(scene => [...scene.frames]
384
+ .sort((left, right) =>
385
+ left.actualAssetTimeUs - right.actualAssetTimeUs || left.sampleId.localeCompare(right.sampleId))
386
+ .map(frame => ({ ...frame, sceneId: scene.sceneId })));
387
+ const sceneIds = scenes.map(scene => scene.sceneId);
388
+ const imageBytes = frames.reduce((total, frame) => total + frame.bytes, 0);
389
+ return {
390
+ batch: {
391
+ batchKey: canonicalJsonSha256({
392
+ contextDigest,
393
+ frames: frames.map(frame => ({
394
+ actualAssetTimeUs: frame.actualAssetTimeUs,
395
+ bytes: frame.bytes,
396
+ sampleId: frame.sampleId,
397
+ sceneId: frame.sceneId,
398
+ sha256: frame.sha256,
399
+ })),
400
+ sceneIds,
401
+ version: 1,
402
+ }),
403
+ errorCode: null,
404
+ imageBytes,
405
+ imageCount: frames.length,
406
+ sceneIds,
407
+ state: "planned",
408
+ },
409
+ frames,
410
+ };
411
+ }
412
+
413
+ export function planSceneBatches(
414
+ contextDigest: string,
415
+ inputScenes: readonly BatchableScene[],
416
+ ): readonly PlannedSceneBatch[] {
417
+ requireSha256(contextDigest, "Batch contextDigest");
418
+ const scenes = [...inputScenes].sort((left, right) =>
419
+ left.rangeStartUs - right.rangeStartUs || left.sceneId.localeCompare(right.sceneId));
420
+ if (new Set(scenes.map(scene => scene.sceneId)).size !== scenes.length) {
421
+ throw new TypeError("Batch scene IDs must be unique.");
422
+ }
423
+ for (const scene of scenes) validateBatchableScene(scene);
424
+
425
+ const batches: PlannedSceneBatch[] = [];
426
+ let pending: BatchableScene[] = [];
427
+ let pendingBytes = 0;
428
+ let pendingImages = 0;
429
+ const flush = () => {
430
+ if (pending.length > 0) batches.push(makeBatch(contextDigest, pending));
431
+ pending = [];
432
+ pendingBytes = 0;
433
+ pendingImages = 0;
434
+ };
435
+
436
+ for (const scene of scenes) {
437
+ const sceneBytes = scene.frames.reduce((total, frame) => total + frame.bytes, 0);
438
+ const wouldOverflow = pending.length >= SCENE_BATCH_LIMITS.sceneCount
439
+ || pendingImages + scene.frames.length > SCENE_BATCH_LIMITS.imageCount
440
+ || pendingBytes + sceneBytes > SCENE_BATCH_LIMITS.imageBytes;
441
+ if (wouldOverflow) flush();
442
+ pending.push(scene);
443
+ pendingBytes += sceneBytes;
444
+ pendingImages += scene.frames.length;
445
+ }
446
+ flush();
447
+ return batches;
448
+ }
@@ -0,0 +1,22 @@
1
+ import { createBoundedJsonValueSnapshot, deepFreezeJson } from "../../../src/code/json-snapshot";
2
+ import { SpatialCompositorCadenceBindingV1Schema, type SpatialCompositorCadenceBindingV1 } from "../contracts/spatial-compositor";
3
+ import type { ProjectRenderPlanV1 } from "../contracts/project-render";
4
+ import { canonicalJsonSha256 } from "./canonical-json";
5
+ import { assertProjectRenderPlanComposition } from "./project-render-plan";
6
+
7
+ export function spatialCompositorCadenceSha256(cadence: SpatialCompositorCadenceBindingV1["cadence"]): string {
8
+ return canonicalJsonSha256({ domain: "slopcamera.spatial-compositor-cadence/v1", cadence });
9
+ }
10
+
11
+ /** Recheck at the native boundary even when the caller previously bound a projection. */
12
+ export function assertSpatialCompositorCadence(input: unknown, plan?: ProjectRenderPlanV1): SpatialCompositorCadenceBindingV1 {
13
+ const binding = SpatialCompositorCadenceBindingV1Schema.parse(createBoundedJsonValueSnapshot(input, 4096, "spatial compositor cadence", { maximumDepth: 8, maximumValues: 100 }).value);
14
+ if (binding.cadenceSha256 !== spatialCompositorCadenceSha256(binding.cadence)) throw new Error("Spatial compositor cadence digest mismatch.");
15
+ if (plan !== undefined) {
16
+ assertProjectRenderPlanComposition(plan);
17
+ if (plan.planSha256 !== binding.cadence.compositionPlanSha256
18
+ || plan.output.durationUs !== binding.cadence.durationUs
19
+ || plan.output.frameRate !== binding.cadence.frameRate.numerator / binding.cadence.frameRate.denominator) throw new Error("Spatial compositor cadence does not bind the exact V1 composition plan.");
20
+ }
21
+ return deepFreezeJson(binding);
22
+ }
@@ -0,0 +1,99 @@
1
+ import { createHash } from "node:crypto";
2
+ import { constants } from "node:fs";
3
+ import { lstat, open, realpath, type FileHandle } from "node:fs/promises";
4
+ import { dirname, join, relative, resolve, sep } from "node:path";
5
+
6
+ import { RepositoryRelativePathSchema } from "../contracts/recording";
7
+ import { SPATIAL_PROJECT_LIMITS } from "../contracts/spatial-project";
8
+ import type { BundleFileIntegrity } from "./storage";
9
+
10
+ /** A successful result proves exact file bytes and their ancestor entries durable. */
11
+ export interface SpatialDurabilityPort {
12
+ syncExactFile(path: string, expected: BundleFileIntegrity): Promise<void>;
13
+ }
14
+
15
+ /**
16
+ * The caller holds the project publication lease through this operation. No
17
+ * rewrite is used to obtain durability: an already-published inode is flushed
18
+ * and rechecked, including every newly-created ancestor through the bundle root.
19
+ */
20
+ export function createNodeSpatialDurability(bundleRoot: string): SpatialDurabilityPort {
21
+ const root = resolve(bundleRoot);
22
+ return {
23
+ async syncExactFile(pathInput, expected) {
24
+ const path = RepositoryRelativePathSchema.parse(pathInput);
25
+ if (!Number.isSafeInteger(expected.bytes) || expected.bytes < 1
26
+ || expected.bytes > SPATIAL_PROJECT_LIMITS.payloadBytes || !/^[a-f0-9]{64}$/u.test(expected.sha256)) {
27
+ throw new Error("Invalid spatial durability byte contract.");
28
+ }
29
+ const target = resolve(root, path);
30
+ const within = relative(root, target);
31
+ if (within === "" || within.startsWith(`..${sep}`) || within === "..") throw new Error("Spatial file escapes its root.");
32
+ const parents: Array<{ path: string; handle: FileHandle; dev: number; ino: number }> = [];
33
+ let file: FileHandle | undefined;
34
+ let primaryFailure: unknown;
35
+ let failed = false;
36
+ try {
37
+ const parts = relative(root, dirname(target)).split(sep).filter(Boolean);
38
+ let current = root;
39
+ for (const part of ["", ...parts]) {
40
+ if (part !== "") current = join(current, part);
41
+ const lexical = await lstat(current);
42
+ if (lexical.isSymbolicLink() || !lexical.isDirectory()) throw new Error("Spatial durability requires physical ancestor directories.");
43
+ const handle = await open(current, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_DIRECTORY);
44
+ parents.push({ path: current, handle, dev: lexical.dev, ino: lexical.ino });
45
+ const physical = await handle.stat();
46
+ if (!physical.isDirectory() || physical.dev !== lexical.dev || physical.ino !== lexical.ino) {
47
+ throw new Error("Spatial ancestor changed during durability admission.");
48
+ }
49
+ }
50
+ const actualRoot = await realpath(root);
51
+ const actualTarget = await realpath(target);
52
+ if (actualTarget !== join(actualRoot, path)) throw new Error("Spatial file ancestry was redirected.");
53
+ const lexical = await lstat(target);
54
+ if (lexical.isSymbolicLink() || !lexical.isFile() || lexical.size !== expected.bytes) throw new Error("Spatial durability requires exact bounded regular bytes.");
55
+ file = await open(target, constants.O_RDONLY | constants.O_NOFOLLOW);
56
+ const before = await file.stat();
57
+ if (!before.isFile() || before.dev !== lexical.dev || before.ino !== lexical.ino || before.size !== expected.bytes) throw new Error("Spatial file changed during durability admission.");
58
+ const hash = createHash("sha256");
59
+ const buffer = Buffer.allocUnsafe(64 * 1024);
60
+ let offset = 0;
61
+ while (offset < expected.bytes) {
62
+ const read = await file.read(buffer, 0, Math.min(buffer.length, expected.bytes - offset), offset);
63
+ if (read.bytesRead === 0) throw new Error("Spatial durability file was truncated.");
64
+ offset += read.bytesRead;
65
+ hash.update(buffer.subarray(0, read.bytesRead));
66
+ }
67
+ if (hash.digest("hex") !== expected.sha256) throw new Error("Spatial durability digest mismatch.");
68
+ await file.sync();
69
+ for (const parent of [...parents].reverse()) await parent.handle.sync();
70
+ const after = await file.stat();
71
+ const named = await lstat(target);
72
+ if (after.dev !== before.dev || after.ino !== before.ino || after.size !== before.size
73
+ || after.mtimeMs !== before.mtimeMs || after.ctimeMs !== before.ctimeMs
74
+ || named.isSymbolicLink() || named.dev !== before.dev || named.ino !== before.ino
75
+ || named.size !== before.size || named.mtimeMs !== before.mtimeMs || named.ctimeMs !== before.ctimeMs) {
76
+ throw new Error("Spatial file changed during durability proof.");
77
+ }
78
+ for (const parent of parents) {
79
+ const namedParent = await lstat(parent.path);
80
+ if (namedParent.isSymbolicLink() || !namedParent.isDirectory()
81
+ || namedParent.dev !== parent.dev || namedParent.ino !== parent.ino) throw new Error("Spatial ancestor changed during durability proof.");
82
+ }
83
+ if (await realpath(target) !== actualTarget) throw new Error("Spatial ancestry changed during durability proof.");
84
+ } catch (error) {
85
+ failed = true;
86
+ primaryFailure = error;
87
+ } finally {
88
+ // Settle all descriptor ownership even if a preceding close fails.
89
+ const closed = await Promise.allSettled([...(file === undefined ? [] : [file.close()]), ...parents.map(parent => parent.handle.close())]);
90
+ const failures = closed.filter((result): result is PromiseRejectedResult => result.status === "rejected");
91
+ if (failures.length > 0) {
92
+ primaryFailure = new AggregateError([...(failed ? [primaryFailure] : []), ...failures.map(result => result.reason)], "Spatial durability descriptor close failed.");
93
+ failed = true;
94
+ }
95
+ }
96
+ if (failed) throw primaryFailure;
97
+ },
98
+ };
99
+ }