@photivra/engine 1.5.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1078) hide show
  1. package/CHANGELOG.md +393 -6
  2. package/CITATION.cff +1 -1
  3. package/NODE_SUPPORT.md +64 -12
  4. package/README.md +301 -182
  5. package/SECURITY.md +37 -28
  6. package/THIRD_PARTY.md +148 -4
  7. package/dist/camera/equivalent-focal-length.d.ts +13 -14
  8. package/dist/camera/equivalent-focal-length.js +15 -13
  9. package/dist/camera/equivalent-focal-length.js.map +1 -1
  10. package/dist/camera/field-of-view.d.ts +30 -19
  11. package/dist/camera/field-of-view.js +67 -18
  12. package/dist/camera/field-of-view.js.map +1 -1
  13. package/dist/camera/lens-projection.d.ts +170 -0
  14. package/dist/camera/lens-projection.js +444 -0
  15. package/dist/camera/lens-projection.js.map +1 -0
  16. package/dist/camera/projected-object-size.d.ts +10 -11
  17. package/dist/camera/projected-object-size.js +12 -10
  18. package/dist/camera/projected-object-size.js.map +1 -1
  19. package/dist/capture/capture-export-metadata.d.ts +9 -10
  20. package/dist/capture/capture-export-metadata.js +8 -5
  21. package/dist/capture/capture-export-metadata.js.map +1 -1
  22. package/dist/capture/dng-import.d.ts +91 -0
  23. package/dist/capture/dng-import.js +343 -0
  24. package/dist/capture/dng-import.js.map +1 -0
  25. package/dist/capture/environment-raw-producer.d.ts +37 -17
  26. package/dist/capture/environment-raw-producer.js +43 -19
  27. package/dist/capture/environment-raw-producer.js.map +1 -1
  28. package/dist/capture/export-jpeg.d.ts +35 -4
  29. package/dist/capture/export-jpeg.js +133 -47
  30. package/dist/capture/export-jpeg.js.map +1 -1
  31. package/dist/capture/export-tiff.d.ts +48 -24
  32. package/dist/capture/export-tiff.js +91 -31
  33. package/dist/capture/export-tiff.js.map +1 -1
  34. package/dist/capture/focus-control.d.ts +42 -65
  35. package/dist/capture/focus-control.js +56 -75
  36. package/dist/capture/focus-control.js.map +1 -1
  37. package/dist/capture/host-yield-cadence.d.ts +25 -0
  38. package/dist/capture/host-yield-cadence.js +25 -0
  39. package/dist/capture/host-yield-cadence.js.map +1 -0
  40. package/dist/capture/linear-encoding.d.ts +14 -12
  41. package/dist/capture/linear-encoding.js +14 -13
  42. package/dist/capture/linear-encoding.js.map +1 -1
  43. package/dist/capture/lossless-jpeg.d.ts +27 -0
  44. package/dist/capture/lossless-jpeg.js +237 -0
  45. package/dist/capture/lossless-jpeg.js.map +1 -0
  46. package/dist/capture/native-environment-lean.d.ts +227 -0
  47. package/dist/capture/native-environment-lean.js +974 -0
  48. package/dist/capture/native-environment-lean.js.map +1 -0
  49. package/dist/capture/native-environment-optical-stage.d.ts +97 -0
  50. package/dist/capture/native-environment-optical-stage.js +340 -0
  51. package/dist/capture/native-environment-optical-stage.js.map +1 -0
  52. package/dist/capture/native-environment-raw.d.ts +140 -6
  53. package/dist/capture/native-environment-raw.js +435 -34
  54. package/dist/capture/native-environment-raw.js.map +1 -1
  55. package/dist/capture/native-raw-development.d.ts +116 -9
  56. package/dist/capture/native-raw-development.js +221 -62
  57. package/dist/capture/native-raw-development.js.map +1 -1
  58. package/dist/capture/native-raw-dng.d.ts +60 -0
  59. package/dist/capture/native-raw-dng.js +167 -0
  60. package/dist/capture/native-raw-dng.js.map +1 -0
  61. package/dist/capture/native-raw.d.ts +65 -12
  62. package/dist/capture/native-raw.js +81 -19
  63. package/dist/capture/native-raw.js.map +1 -1
  64. package/dist/capture/packed-sensor-raw.d.ts +160 -0
  65. package/dist/capture/packed-sensor-raw.js +221 -0
  66. package/dist/capture/packed-sensor-raw.js.map +1 -0
  67. package/dist/capture/photographic-export.d.ts +58 -35
  68. package/dist/capture/photographic-export.js +177 -70
  69. package/dist/capture/photographic-export.js.map +1 -1
  70. package/dist/capture/raw-development-kernel.d.ts +52 -0
  71. package/dist/capture/raw-development-kernel.js +88 -0
  72. package/dist/capture/raw-development-kernel.js.map +1 -0
  73. package/dist/capture/raw-frame-limits.d.ts +7 -7
  74. package/dist/capture/raw-frame-limits.js +7 -6
  75. package/dist/capture/raw-frame-limits.js.map +1 -1
  76. package/dist/capture/raw-frame-reconstruction.d.ts +8 -9
  77. package/dist/capture/raw-frame-reconstruction.js +9 -8
  78. package/dist/capture/raw-frame-reconstruction.js.map +1 -1
  79. package/dist/capture/raw-white-level.d.ts +55 -0
  80. package/dist/capture/raw-white-level.js +165 -0
  81. package/dist/capture/raw-white-level.js.map +1 -0
  82. package/dist/capture/release-sequence.d.ts +5 -7
  83. package/dist/capture/release-sequence.js +15 -13
  84. package/dist/capture/release-sequence.js.map +1 -1
  85. package/dist/capture/sensor-raw-frame.d.ts +5 -6
  86. package/dist/capture/sensor-raw-frame.js +26 -9
  87. package/dist/capture/sensor-raw-frame.js.map +1 -1
  88. package/dist/capture/sensor-raw-producer.d.ts +21 -13
  89. package/dist/capture/sensor-raw-producer.js +86 -25
  90. package/dist/capture/sensor-raw-producer.js.map +1 -1
  91. package/dist/capture/simulated-capture.d.ts +15 -16
  92. package/dist/capture/simulated-capture.js +22 -12
  93. package/dist/capture/simulated-capture.js.map +1 -1
  94. package/dist/color/camera-color-matrix.d.ts +69 -0
  95. package/dist/color/camera-color-matrix.js +132 -0
  96. package/dist/color/camera-color-matrix.js.map +1 -0
  97. package/dist/color/capture-color.d.ts +17 -10
  98. package/dist/color/capture-color.js +50 -17
  99. package/dist/color/capture-color.js.map +1 -1
  100. package/dist/color/chromatic-adaptation.d.ts +63 -0
  101. package/dist/color/chromatic-adaptation.js +91 -0
  102. package/dist/color/chromatic-adaptation.js.map +1 -0
  103. package/dist/color/color-difference.d.ts +104 -0
  104. package/dist/color/color-difference.js +205 -0
  105. package/dist/color/color-difference.js.map +1 -0
  106. package/dist/color/color-matching-functions.d.ts +168 -0
  107. package/dist/color/color-matching-functions.js +309 -0
  108. package/dist/color/color-matching-functions.js.map +1 -0
  109. package/dist/color/colorimetry-math.d.ts +52 -0
  110. package/dist/color/colorimetry-math.js +144 -0
  111. package/dist/color/colorimetry-math.js.map +1 -0
  112. package/dist/color/gamut-mapping.d.ts +54 -0
  113. package/dist/color/gamut-mapping.js +107 -0
  114. package/dist/color/gamut-mapping.js.map +1 -0
  115. package/dist/color/illuminants.d.ts +182 -0
  116. package/dist/color/illuminants.js +434 -0
  117. package/dist/color/illuminants.js.map +1 -0
  118. package/dist/color/rgb-color-spaces.d.ts +79 -0
  119. package/dist/color/rgb-color-spaces.js +115 -0
  120. package/dist/color/rgb-color-spaces.js.map +1 -0
  121. package/dist/color/sensor-color-development.d.ts +33 -11
  122. package/dist/color/sensor-color-development.js +104 -30
  123. package/dist/color/sensor-color-development.js.map +1 -1
  124. package/dist/color/srgb-icc.d.ts +9 -2
  125. package/dist/color/srgb-icc.js +16 -5
  126. package/dist/color/srgb-icc.js.map +1 -1
  127. package/dist/color/srgb-transfer.d.ts +1 -2
  128. package/dist/color/srgb-transfer.js +1 -1
  129. package/dist/color/srgb-transfer.js.map +1 -1
  130. package/dist/color/white-balance.d.ts +68 -19
  131. package/dist/color/white-balance.js +168 -35
  132. package/dist/color/white-balance.js.map +1 -1
  133. package/dist/composition/image-formation-plan.d.ts +152 -128
  134. package/dist/composition/image-formation-plan.js +292 -138
  135. package/dist/composition/image-formation-plan.js.map +1 -1
  136. package/dist/composition/plan-consumer.d.ts +6 -7
  137. package/dist/composition/plan-consumer.js +14 -11
  138. package/dist/composition/plan-consumer.js.map +1 -1
  139. package/dist/core/calculation-result.d.ts +21 -14
  140. package/dist/core/calculation-result.js +209 -11
  141. package/dist/core/calculation-result.js.map +1 -1
  142. package/dist/core/canonical-json.d.ts +13 -8
  143. package/dist/core/canonical-json.js +34 -15
  144. package/dist/core/canonical-json.js.map +1 -1
  145. package/dist/core/configuration-error.d.ts +5 -4
  146. package/dist/core/configuration-error.js +5 -3
  147. package/dist/core/configuration-error.js.map +1 -1
  148. package/dist/core/evidence-provenance.d.ts +12 -12
  149. package/dist/core/evidence-provenance.js +10 -9
  150. package/dist/core/evidence-provenance.js.map +1 -1
  151. package/dist/core/image-formation.d.ts +11 -10
  152. package/dist/core/image-formation.js +11 -9
  153. package/dist/core/image-formation.js.map +1 -1
  154. package/dist/core/owned-data.d.ts +9 -17
  155. package/dist/core/owned-data.js +9 -16
  156. package/dist/core/owned-data.js.map +1 -1
  157. package/dist/core/portable-math.d.ts +15 -0
  158. package/dist/core/portable-math.js +56 -0
  159. package/dist/core/portable-math.js.map +1 -0
  160. package/dist/core/record-validation.d.ts +48 -9
  161. package/dist/core/record-validation.js +93 -19
  162. package/dist/core/record-validation.js.map +1 -1
  163. package/dist/core/scientific-assurance.d.ts +16 -14
  164. package/dist/core/scientific-assurance.js +22 -18
  165. package/dist/core/scientific-assurance.js.map +1 -1
  166. package/dist/core/spectral-composition.d.ts +13 -14
  167. package/dist/core/spectral-composition.js +30 -27
  168. package/dist/core/spectral-composition.js.map +1 -1
  169. package/dist/core/spectral.d.ts +5 -6
  170. package/dist/core/spectral.js +9 -7
  171. package/dist/core/spectral.js.map +1 -1
  172. package/dist/core/validation.d.ts +3 -6
  173. package/dist/core/validation.js +3 -5
  174. package/dist/core/validation.js.map +1 -1
  175. package/dist/core/version.d.ts +6 -6
  176. package/dist/core/version.js +6 -5
  177. package/dist/core/version.js.map +1 -1
  178. package/dist/equipment/exposure-capabilities.d.ts +10 -10
  179. package/dist/equipment/exposure-capabilities.js +40 -30
  180. package/dist/equipment/exposure-capabilities.js.map +1 -1
  181. package/dist/equipment/generic-tier-assets.d.ts +13 -6
  182. package/dist/equipment/generic-tier-assets.js +34 -15
  183. package/dist/equipment/generic-tier-assets.js.map +1 -1
  184. package/dist/equipment/generic-tier-presets.d.ts +5 -6
  185. package/dist/equipment/generic-tier-presets.js +15 -13
  186. package/dist/equipment/generic-tier-presets.js.map +1 -1
  187. package/dist/equipment/iso-capabilities.d.ts +21 -22
  188. package/dist/equipment/iso-capabilities.js +49 -44
  189. package/dist/equipment/iso-capabilities.js.map +1 -1
  190. package/dist/equipment/metering-capabilities.d.ts +5 -6
  191. package/dist/equipment/metering-capabilities.js +22 -19
  192. package/dist/equipment/metering-capabilities.js.map +1 -1
  193. package/dist/equipment/release-capabilities.d.ts +3 -4
  194. package/dist/equipment/release-capabilities.js +18 -14
  195. package/dist/equipment/release-capabilities.js.map +1 -1
  196. package/dist/exposure/exposure-duration-control.d.ts +19 -17
  197. package/dist/exposure/exposure-duration-control.js +27 -23
  198. package/dist/exposure/exposure-duration-control.js.map +1 -1
  199. package/dist/exposure/exposure-mode-resolver.d.ts +42 -39
  200. package/dist/exposure/exposure-mode-resolver.js +127 -82
  201. package/dist/exposure/exposure-mode-resolver.js.map +1 -1
  202. package/dist/exposure/exposure.d.ts +17 -16
  203. package/dist/exposure/exposure.js +22 -15
  204. package/dist/exposure/exposure.js.map +1 -1
  205. package/dist/exposure/flash-ttl.d.ts +135 -0
  206. package/dist/exposure/flash-ttl.js +193 -0
  207. package/dist/exposure/flash-ttl.js.map +1 -0
  208. package/dist/exposure/flash.d.ts +144 -36
  209. package/dist/exposure/flash.js +259 -59
  210. package/dist/exposure/flash.js.map +1 -1
  211. package/dist/exposure/metering-scene-radiance.d.ts +10 -10
  212. package/dist/exposure/metering-scene-radiance.js +42 -23
  213. package/dist/exposure/metering-scene-radiance.js.map +1 -1
  214. package/dist/exposure/metering-target.d.ts +8 -8
  215. package/dist/exposure/metering-target.js +30 -20
  216. package/dist/exposure/metering-target.js.map +1 -1
  217. package/dist/exposure/metering-temporal.d.ts +16 -15
  218. package/dist/exposure/metering-temporal.js +41 -19
  219. package/dist/exposure/metering-temporal.js.map +1 -1
  220. package/dist/exposure/metering.d.ts +64 -17
  221. package/dist/exposure/metering.js +159 -30
  222. package/dist/exposure/metering.js.map +1 -1
  223. package/dist/exposure/program-line.d.ts +5 -5
  224. package/dist/exposure/program-line.js +20 -13
  225. package/dist/exposure/program-line.js.map +1 -1
  226. package/dist/facade/camera-preset.d.ts +8 -0
  227. package/dist/facade/camera-preset.js +102 -0
  228. package/dist/facade/camera-preset.js.map +1 -0
  229. package/dist/facade/chart-radiance-batch.d.ts +57 -0
  230. package/dist/facade/chart-radiance-batch.js +121 -0
  231. package/dist/facade/chart-radiance-batch.js.map +1 -0
  232. package/dist/facade/chart-scenes.d.ts +177 -0
  233. package/dist/facade/chart-scenes.js +232 -0
  234. package/dist/facade/chart-scenes.js.map +1 -0
  235. package/dist/facade/host-yield.d.ts +7 -0
  236. package/dist/facade/host-yield.js +23 -0
  237. package/dist/facade/host-yield.js.map +1 -0
  238. package/dist/facade/scene-radiance.d.ts +117 -0
  239. package/dist/facade/scene-radiance.js +205 -0
  240. package/dist/facade/scene-radiance.js.map +1 -0
  241. package/dist/facade/simulate-capture.d.ts +284 -0
  242. package/dist/facade/simulate-capture.js +1026 -0
  243. package/dist/facade/simulate-capture.js.map +1 -0
  244. package/dist/gpu/chart-radiance-gpu.d.ts +17 -0
  245. package/dist/gpu/chart-radiance-gpu.js +176 -0
  246. package/dist/gpu/chart-radiance-gpu.js.map +1 -0
  247. package/dist/gpu/image-kernel-accelerator.d.ts +132 -0
  248. package/dist/gpu/image-kernel-accelerator.js +720 -0
  249. package/dist/gpu/image-kernel-accelerator.js.map +1 -0
  250. package/dist/gpu/image-kernel-execution.d.ts +92 -0
  251. package/dist/gpu/image-kernel-execution.js +84 -0
  252. package/dist/gpu/image-kernel-execution.js.map +1 -0
  253. package/dist/gpu/sensor-stage-gpu.d.ts +44 -0
  254. package/dist/gpu/sensor-stage-gpu.js +317 -0
  255. package/dist/gpu/sensor-stage-gpu.js.map +1 -0
  256. package/dist/gpu/temporal-sum-gpu.d.ts +23 -0
  257. package/dist/gpu/temporal-sum-gpu.js +150 -0
  258. package/dist/gpu/temporal-sum-gpu.js.map +1 -0
  259. package/dist/gpu/webgpu-runtime.d.ts +63 -0
  260. package/dist/gpu/webgpu-runtime.js +215 -0
  261. package/dist/gpu/webgpu-runtime.js.map +1 -0
  262. package/dist/gpu/wgsl-chart-kernel.d.ts +30 -0
  263. package/dist/gpu/wgsl-chart-kernel.js +165 -0
  264. package/dist/gpu/wgsl-chart-kernel.js.map +1 -0
  265. package/dist/gpu/wgsl-kernels.d.ts +53 -0
  266. package/dist/gpu/wgsl-kernels.js +365 -0
  267. package/dist/gpu/wgsl-kernels.js.map +1 -0
  268. package/dist/gpu/wgsl-sensor-kernel.d.ts +64 -0
  269. package/dist/gpu/wgsl-sensor-kernel.js +409 -0
  270. package/dist/gpu/wgsl-sensor-kernel.js.map +1 -0
  271. package/dist/index.d.ts +89 -25
  272. package/dist/index.js +59 -15
  273. package/dist/index.js.map +1 -1
  274. package/dist/motion/camera-rotation.d.ts +92 -40
  275. package/dist/motion/camera-rotation.js +84 -47
  276. package/dist/motion/camera-rotation.js.map +1 -1
  277. package/dist/motion/capture-rotation-inverse-mapping.d.ts +37 -34
  278. package/dist/motion/capture-rotation-inverse-mapping.js +32 -24
  279. package/dist/motion/capture-rotation-inverse-mapping.js.map +1 -1
  280. package/dist/motion/capture-rotation-temporal-quadrature.d.ts +39 -35
  281. package/dist/motion/capture-rotation-temporal-quadrature.js +37 -28
  282. package/dist/motion/capture-rotation-temporal-quadrature.js.map +1 -1
  283. package/dist/motion/capture-rotation-trajectory.d.ts +32 -28
  284. package/dist/motion/capture-rotation-trajectory.js +36 -26
  285. package/dist/motion/capture-rotation-trajectory.js.map +1 -1
  286. package/dist/motion/capture-translation-parallax-temporal-quadrature.d.ts +23 -21
  287. package/dist/motion/capture-translation-parallax-temporal-quadrature.js +33 -14
  288. package/dist/motion/capture-translation-parallax-temporal-quadrature.js.map +1 -1
  289. package/dist/motion/extended-object-projection.d.ts +19 -16
  290. package/dist/motion/extended-object-projection.js +38 -18
  291. package/dist/motion/extended-object-projection.js.map +1 -1
  292. package/dist/motion/projected-motion.d.ts +18 -17
  293. package/dist/motion/projected-motion.js +25 -17
  294. package/dist/motion/projected-motion.js.map +1 -1
  295. package/dist/motion/temporal-exposure-sum.d.ts +55 -0
  296. package/dist/motion/temporal-exposure-sum.js +141 -0
  297. package/dist/motion/temporal-exposure-sum.js.map +1 -0
  298. package/dist/motion/video-sequence.d.ts +111 -0
  299. package/dist/motion/video-sequence.js +179 -0
  300. package/dist/motion/video-sequence.js.map +1 -0
  301. package/dist/optics/aperture.d.ts +14 -15
  302. package/dist/optics/aperture.js +13 -11
  303. package/dist/optics/aperture.js.map +1 -1
  304. package/dist/optics/atmospheric-haze.d.ts +83 -0
  305. package/dist/optics/atmospheric-haze.js +113 -0
  306. package/dist/optics/atmospheric-haze.js.map +1 -0
  307. package/dist/optics/brown-conrady-distortion.d.ts +116 -0
  308. package/dist/optics/brown-conrady-distortion.js +291 -0
  309. package/dist/optics/brown-conrady-distortion.js.map +1 -0
  310. package/dist/optics/circle-of-confusion.d.ts +10 -10
  311. package/dist/optics/circle-of-confusion.js +15 -11
  312. package/dist/optics/circle-of-confusion.js.map +1 -1
  313. package/dist/optics/complex-pupil-psf.d.ts +64 -33
  314. package/dist/optics/complex-pupil-psf.js +190 -98
  315. package/dist/optics/complex-pupil-psf.js.map +1 -1
  316. package/dist/optics/depth-of-field.d.ts +25 -15
  317. package/dist/optics/depth-of-field.js +67 -27
  318. package/dist/optics/depth-of-field.js.map +1 -1
  319. package/dist/optics/diffraction.d.ts +14 -13
  320. package/dist/optics/diffraction.js +14 -10
  321. package/dist/optics/diffraction.js.map +1 -1
  322. package/dist/optics/focus-breathing.d.ts +88 -27
  323. package/dist/optics/focus-breathing.js +114 -23
  324. package/dist/optics/focus-breathing.js.map +1 -1
  325. package/dist/optics/focus-state.d.ts +18 -18
  326. package/dist/optics/focus-state.js +21 -15
  327. package/dist/optics/focus-state.js.map +1 -1
  328. package/dist/optics/front-of-lens-filter.d.ts +45 -14
  329. package/dist/optics/front-of-lens-filter.js +259 -88
  330. package/dist/optics/front-of-lens-filter.js.map +1 -1
  331. package/dist/optics/illumination-vignetting.d.ts +16 -17
  332. package/dist/optics/illumination-vignetting.js +40 -17
  333. package/dist/optics/illumination-vignetting.js.map +1 -1
  334. package/dist/optics/irradiance-convolution.d.ts +59 -0
  335. package/dist/optics/irradiance-convolution.js +138 -0
  336. package/dist/optics/irradiance-convolution.js.map +1 -0
  337. package/dist/optics/lateral-chromatic-aberration.d.ts +33 -30
  338. package/dist/optics/lateral-chromatic-aberration.js +33 -23
  339. package/dist/optics/lateral-chromatic-aberration.js.map +1 -1
  340. package/dist/optics/lens-psf-kernels.d.ts +89 -0
  341. package/dist/optics/lens-psf-kernels.js +175 -0
  342. package/dist/optics/lens-psf-kernels.js.map +1 -0
  343. package/dist/optics/lens-psf-profile.d.ts +78 -46
  344. package/dist/optics/lens-psf-profile.js +335 -124
  345. package/dist/optics/lens-psf-profile.js.map +1 -1
  346. package/dist/optics/longitudinal-chromatic-aberration.d.ts +102 -0
  347. package/dist/optics/longitudinal-chromatic-aberration.js +210 -0
  348. package/dist/optics/longitudinal-chromatic-aberration.js.map +1 -0
  349. package/dist/optics/polygon-diffraction.d.ts +80 -23
  350. package/dist/optics/polygon-diffraction.js +130 -25
  351. package/dist/optics/polygon-diffraction.js.map +1 -1
  352. package/dist/optics/profile-contract.d.ts +5 -6
  353. package/dist/optics/profile-contract.js +14 -11
  354. package/dist/optics/profile-contract.js.map +1 -1
  355. package/dist/optics/psf-foundation.d.ts +23 -23
  356. package/dist/optics/psf-foundation.js +24 -21
  357. package/dist/optics/psf-foundation.js.map +1 -1
  358. package/dist/optics/pupil-vignetting.d.ts +118 -0
  359. package/dist/optics/pupil-vignetting.js +264 -0
  360. package/dist/optics/pupil-vignetting.js.map +1 -0
  361. package/dist/optics/radial-distortion.d.ts +34 -33
  362. package/dist/optics/radial-distortion.js +37 -30
  363. package/dist/optics/radial-distortion.js.map +1 -1
  364. package/dist/optics/scene-to-sensor-irradiance.d.ts +47 -33
  365. package/dist/optics/scene-to-sensor-irradiance.js +73 -45
  366. package/dist/optics/scene-to-sensor-irradiance.js.map +1 -1
  367. package/dist/optics/scene-to-sensor-quadrature.d.ts +23 -18
  368. package/dist/optics/scene-to-sensor-quadrature.js +16 -11
  369. package/dist/optics/scene-to-sensor-quadrature.js.map +1 -1
  370. package/dist/optics/sensor-aperture-rays.d.ts +20 -4
  371. package/dist/optics/sensor-aperture-rays.js +32 -14
  372. package/dist/optics/sensor-aperture-rays.js.map +1 -1
  373. package/dist/optics/sensor-environment-query.d.ts +81 -16
  374. package/dist/optics/sensor-environment-query.js +145 -12
  375. package/dist/optics/sensor-environment-query.js.map +1 -1
  376. package/dist/optics/sensor-psf-quadrature.d.ts +12 -10
  377. package/dist/optics/sensor-psf-quadrature.js +18 -11
  378. package/dist/optics/sensor-psf-quadrature.js.map +1 -1
  379. package/dist/optics/sensor-psf-support.d.ts +8 -6
  380. package/dist/optics/sensor-psf-support.js +8 -5
  381. package/dist/optics/sensor-psf-support.js.map +1 -1
  382. package/dist/optics/stray-light.d.ts +22 -19
  383. package/dist/optics/stray-light.js +18 -12
  384. package/dist/optics/stray-light.js.map +1 -1
  385. package/dist/optics/thin-lens.d.ts +10 -9
  386. package/dist/optics/thin-lens.js +12 -8
  387. package/dist/optics/thin-lens.js.map +1 -1
  388. package/dist/optics/zernike-wavefront.d.ts +154 -0
  389. package/dist/optics/zernike-wavefront.js +352 -0
  390. package/dist/optics/zernike-wavefront.js.map +1 -0
  391. package/dist/output/capture-corrected-sdr.d.ts +16 -8
  392. package/dist/output/capture-corrected-sdr.js +16 -8
  393. package/dist/output/capture-corrected-sdr.js.map +1 -1
  394. package/dist/output/capture-geometry.d.ts +34 -36
  395. package/dist/output/capture-geometry.js +52 -32
  396. package/dist/output/capture-geometry.js.map +1 -1
  397. package/dist/output/capture-sdr.d.ts +7 -6
  398. package/dist/output/capture-sdr.js +11 -8
  399. package/dist/output/capture-sdr.js.map +1 -1
  400. package/dist/output/crop.d.ts +22 -6
  401. package/dist/output/crop.js +40 -6
  402. package/dist/output/crop.js.map +1 -1
  403. package/dist/output/geometric-transforms.d.ts +60 -23
  404. package/dist/output/geometric-transforms.js +111 -24
  405. package/dist/output/geometric-transforms.js.map +1 -1
  406. package/dist/output/lens-corrections.d.ts +27 -19
  407. package/dist/output/lens-corrections.js +29 -13
  408. package/dist/output/lens-corrections.js.map +1 -1
  409. package/dist/output/native-capture-sdr.d.ts +71 -16
  410. package/dist/output/native-capture-sdr.js +190 -33
  411. package/dist/output/native-capture-sdr.js.map +1 -1
  412. package/dist/output/png-encoder.d.ts +65 -0
  413. package/dist/output/png-encoder.js +461 -0
  414. package/dist/output/png-encoder.js.map +1 -0
  415. package/dist/output/print-contrast-reference.d.ts +10 -9
  416. package/dist/output/print-contrast-reference.js +10 -8
  417. package/dist/output/print-contrast-reference.js.map +1 -1
  418. package/dist/output/print-detail.d.ts +18 -17
  419. package/dist/output/print-detail.js +12 -10
  420. package/dist/output/print-detail.js.map +1 -1
  421. package/dist/output/print-jpeg.d.ts +26 -5
  422. package/dist/output/print-jpeg.js +117 -50
  423. package/dist/output/print-jpeg.js.map +1 -1
  424. package/dist/output/print-plan.d.ts +19 -19
  425. package/dist/output/print-plan.js +11 -10
  426. package/dist/output/print-plan.js.map +1 -1
  427. package/dist/output/print-region-statistics.d.ts +14 -15
  428. package/dist/output/print-region-statistics.js +11 -11
  429. package/dist/output/print-region-statistics.js.map +1 -1
  430. package/dist/output/sdr-rendering.d.ts +48 -10
  431. package/dist/output/sdr-rendering.js +114 -25
  432. package/dist/output/sdr-rendering.js.map +1 -1
  433. package/dist/output/subject-framing-crop.d.ts +6 -10
  434. package/dist/output/subject-framing-crop.js +23 -13
  435. package/dist/output/subject-framing-crop.js.map +1 -1
  436. package/dist/processing/demosaic.d.ts +90 -0
  437. package/dist/processing/demosaic.js +275 -0
  438. package/dist/processing/demosaic.js.map +1 -0
  439. package/dist/processing/denoise.d.ts +68 -0
  440. package/dist/processing/denoise.js +214 -0
  441. package/dist/processing/denoise.js.map +1 -0
  442. package/dist/processing/focus-stacking.d.ts +17 -0
  443. package/dist/processing/focus-stacking.js +80 -0
  444. package/dist/processing/focus-stacking.js.map +1 -0
  445. package/dist/processing/hdr.d.ts +54 -0
  446. package/dist/processing/hdr.js +172 -0
  447. package/dist/processing/hdr.js.map +1 -0
  448. package/dist/processing/image-ops.d.ts +66 -0
  449. package/dist/processing/image-ops.js +192 -0
  450. package/dist/processing/image-ops.js.map +1 -0
  451. package/dist/processing/panorama.d.ts +51 -0
  452. package/dist/processing/panorama.js +305 -0
  453. package/dist/processing/panorama.js.map +1 -0
  454. package/dist/processing/processing-task.d.ts +85 -0
  455. package/dist/processing/processing-task.js +164 -0
  456. package/dist/processing/processing-task.js.map +1 -0
  457. package/dist/processing/tone-mapping.d.ts +69 -0
  458. package/dist/processing/tone-mapping.js +310 -0
  459. package/dist/processing/tone-mapping.js.map +1 -0
  460. package/dist/schema/camera.d.ts +5 -4
  461. package/dist/schema/illumination-spectral.d.ts +13 -19
  462. package/dist/schema/illumination-spectral.js +23 -19
  463. package/dist/schema/illumination-spectral.js.map +1 -1
  464. package/dist/schema/illumination-temporal.d.ts +12 -15
  465. package/dist/schema/illumination-temporal.js +43 -28
  466. package/dist/schema/illumination-temporal.js.map +1 -1
  467. package/dist/schema/illumination.d.ts +5 -4
  468. package/dist/schema/illumination.js +55 -20
  469. package/dist/schema/illumination.js.map +1 -1
  470. package/dist/schema/scene-radiance-temporal.d.ts +9 -11
  471. package/dist/schema/scene-radiance-temporal.js +26 -23
  472. package/dist/schema/scene-radiance-temporal.js.map +1 -1
  473. package/dist/schema/scene-radiance.d.ts +17 -17
  474. package/dist/schema/scene-radiance.js +56 -34
  475. package/dist/schema/scene-radiance.js.map +1 -1
  476. package/dist/schema/scene.d.ts +14 -13
  477. package/dist/schema/validation.d.ts +0 -1
  478. package/dist/schema/validation.js +17 -15
  479. package/dist/schema/validation.js.map +1 -1
  480. package/dist/sensor/accumulated-charge.d.ts +45 -64
  481. package/dist/sensor/accumulated-charge.js +41 -47
  482. package/dist/sensor/accumulated-charge.js.map +1 -1
  483. package/dist/sensor/architecture.d.ts +22 -25
  484. package/dist/sensor/architecture.js +17 -16
  485. package/dist/sensor/architecture.js.map +1 -1
  486. package/dist/sensor/camera-saturation-capacity.d.ts +21 -28
  487. package/dist/sensor/camera-saturation-capacity.js +36 -34
  488. package/dist/sensor/camera-saturation-capacity.js.map +1 -1
  489. package/dist/sensor/capture-color-sampling-binding.d.ts +46 -51
  490. package/dist/sensor/capture-color-sampling-binding.js +39 -41
  491. package/dist/sensor/capture-color-sampling-binding.js.map +1 -1
  492. package/dist/sensor/capture-mode-timing.d.ts +8 -9
  493. package/dist/sensor/capture-mode-timing.js +24 -21
  494. package/dist/sensor/capture-mode-timing.js.map +1 -1
  495. package/dist/sensor/capture-mode.d.ts +28 -30
  496. package/dist/sensor/capture-mode.js +33 -31
  497. package/dist/sensor/capture-mode.js.map +1 -1
  498. package/dist/sensor/color-sampling.d.ts +30 -33
  499. package/dist/sensor/color-sampling.js +32 -37
  500. package/dist/sensor/color-sampling.js.map +1 -1
  501. package/dist/sensor/constant-rate-temporal-integration.d.ts +19 -23
  502. package/dist/sensor/constant-rate-temporal-integration.js +28 -26
  503. package/dist/sensor/constant-rate-temporal-integration.js.map +1 -1
  504. package/dist/sensor/dark-current.d.ts +53 -49
  505. package/dist/sensor/dark-current.js +87 -46
  506. package/dist/sensor/dark-current.js.map +1 -1
  507. package/dist/sensor/environment-photo-signal.d.ts +71 -30
  508. package/dist/sensor/environment-photo-signal.js +127 -46
  509. package/dist/sensor/environment-photo-signal.js.map +1 -1
  510. package/dist/sensor/eqe-electron-rate.d.ts +14 -13
  511. package/dist/sensor/eqe-electron-rate.js +34 -14
  512. package/dist/sensor/eqe-electron-rate.js.map +1 -1
  513. package/dist/sensor/eqe-local-exposure.d.ts +24 -24
  514. package/dist/sensor/eqe-local-exposure.js +15 -11
  515. package/dist/sensor/eqe-local-exposure.js.map +1 -1
  516. package/dist/sensor/eqe-temporal-exposure.d.ts +11 -11
  517. package/dist/sensor/eqe-temporal-exposure.js +15 -10
  518. package/dist/sensor/eqe-temporal-exposure.js.map +1 -1
  519. package/dist/sensor/exposure-window.d.ts +48 -34
  520. package/dist/sensor/exposure-window.js +77 -36
  521. package/dist/sensor/exposure-window.js.map +1 -1
  522. package/dist/sensor/fixed-pattern-noise.d.ts +108 -0
  523. package/dist/sensor/fixed-pattern-noise.js +166 -0
  524. package/dist/sensor/fixed-pattern-noise.js.map +1 -0
  525. package/dist/sensor/iso-gain.d.ts +44 -0
  526. package/dist/sensor/iso-gain.js +119 -0
  527. package/dist/sensor/iso-gain.js.map +1 -0
  528. package/dist/sensor/iso-signal-chain.d.ts +25 -48
  529. package/dist/sensor/iso-signal-chain.js +52 -66
  530. package/dist/sensor/iso-signal-chain.js.map +1 -1
  531. package/dist/sensor/local-exposure-binding.d.ts +21 -21
  532. package/dist/sensor/local-exposure-binding.js +22 -18
  533. package/dist/sensor/local-exposure-binding.js.map +1 -1
  534. package/dist/sensor/optical-stack.d.ts +36 -40
  535. package/dist/sensor/optical-stack.js +34 -37
  536. package/dist/sensor/optical-stack.js.map +1 -1
  537. package/dist/sensor/photon-energy.d.ts +22 -28
  538. package/dist/sensor/photon-energy.js +42 -34
  539. package/dist/sensor/photon-energy.js.map +1 -1
  540. package/dist/sensor/physical-charge-capacity.d.ts +20 -30
  541. package/dist/sensor/physical-charge-capacity.js +35 -36
  542. package/dist/sensor/physical-charge-capacity.js.map +1 -1
  543. package/dist/sensor/pixel-pitch.d.ts +2 -3
  544. package/dist/sensor/pixel-pitch.js +4 -2
  545. package/dist/sensor/pixel-pitch.js.map +1 -1
  546. package/dist/sensor/radiometry-readiness.d.ts +13 -15
  547. package/dist/sensor/radiometry-readiness.js +20 -17
  548. package/dist/sensor/radiometry-readiness.js.map +1 -1
  549. package/dist/sensor/raw-readout.d.ts +173 -83
  550. package/dist/sensor/raw-readout.js +425 -160
  551. package/dist/sensor/raw-readout.js.map +1 -1
  552. package/dist/sensor/raw-reconstruction.d.ts +112 -44
  553. package/dist/sensor/raw-reconstruction.js +200 -47
  554. package/dist/sensor/raw-reconstruction.js.map +1 -1
  555. package/dist/sensor/readout-exposure-linkage.d.ts +103 -34
  556. package/dist/sensor/readout-exposure-linkage.js +125 -27
  557. package/dist/sensor/readout-exposure-linkage.js.map +1 -1
  558. package/dist/sensor/readout-timing.d.ts +43 -44
  559. package/dist/sensor/readout-timing.js +38 -30
  560. package/dist/sensor/readout-timing.js.map +1 -1
  561. package/dist/sensor/response-application-compatibility.d.ts +21 -28
  562. package/dist/sensor/response-application-compatibility.js +35 -34
  563. package/dist/sensor/response-application-compatibility.js.map +1 -1
  564. package/dist/sensor/response-operating-range.d.ts +26 -35
  565. package/dist/sensor/response-operating-range.js +40 -39
  566. package/dist/sensor/response-operating-range.js.map +1 -1
  567. package/dist/sensor/responsivity-photocurrent.d.ts +21 -29
  568. package/dist/sensor/responsivity-photocurrent.js +57 -40
  569. package/dist/sensor/responsivity-photocurrent.js.map +1 -1
  570. package/dist/sensor/sampling-aperture.d.ts +21 -25
  571. package/dist/sensor/sampling-aperture.js +29 -28
  572. package/dist/sensor/sampling-aperture.js.map +1 -1
  573. package/dist/sensor/scene-eqe-temporal-exposure.d.ts +14 -12
  574. package/dist/sensor/scene-eqe-temporal-exposure.js +13 -8
  575. package/dist/sensor/scene-eqe-temporal-exposure.js.map +1 -1
  576. package/dist/sensor/sensor-geometry.d.ts +28 -32
  577. package/dist/sensor/sensor-geometry.js +35 -20
  578. package/dist/sensor/sensor-geometry.js.map +1 -1
  579. package/dist/sensor/signal-noise.d.ts +14 -16
  580. package/dist/sensor/signal-noise.js +15 -13
  581. package/dist/sensor/signal-noise.js.map +1 -1
  582. package/dist/sensor/spatial-sample-reduction.d.ts +45 -60
  583. package/dist/sensor/spatial-sample-reduction.js +39 -48
  584. package/dist/sensor/spatial-sample-reduction.js.map +1 -1
  585. package/dist/sensor/spatial-sampling-quadrature.d.ts +46 -54
  586. package/dist/sensor/spatial-sampling-quadrature.js +31 -36
  587. package/dist/sensor/spatial-sampling-quadrature.js.map +1 -1
  588. package/dist/sensor/spatio-spectral-reduction.d.ts +32 -42
  589. package/dist/sensor/spatio-spectral-reduction.js +24 -29
  590. package/dist/sensor/spatio-spectral-reduction.js.map +1 -1
  591. package/dist/sensor/spectral-coverage.d.ts +6 -9
  592. package/dist/sensor/spectral-coverage.js +9 -6
  593. package/dist/sensor/spectral-coverage.js.map +1 -1
  594. package/dist/sensor/spectral-quadrature.d.ts +46 -60
  595. package/dist/sensor/spectral-quadrature.js +38 -48
  596. package/dist/sensor/spectral-quadrature.js.map +1 -1
  597. package/dist/sensor/spectral-response.d.ts +53 -41
  598. package/dist/sensor/spectral-response.js +80 -47
  599. package/dist/sensor/spectral-response.js.map +1 -1
  600. package/dist/sensor/stochastic-seed.d.ts +50 -0
  601. package/dist/sensor/stochastic-seed.js +123 -0
  602. package/dist/sensor/stochastic-seed.js.map +1 -0
  603. package/dist/sensor/temporal-eqe-sum.d.ts +2 -4
  604. package/dist/sensor/temporal-eqe-sum.js +5 -6
  605. package/dist/sensor/temporal-eqe-sum.js.map +1 -1
  606. package/dist/sensor/temporal-photo-signal.d.ts +5 -6
  607. package/dist/sensor/temporal-photo-signal.js +11 -10
  608. package/dist/sensor/temporal-photo-signal.js.map +1 -1
  609. package/dist/sensor/temporal-sample-binding.d.ts +1 -2
  610. package/dist/sensor/temporal-sample-binding.js +3 -3
  611. package/dist/sensor/temporal-sample-binding.js.map +1 -1
  612. package/dist/simulation/poc-simulation.d.ts +16 -16
  613. package/dist/simulation/poc-simulation.js +20 -15
  614. package/dist/simulation/poc-simulation.js.map +1 -1
  615. package/dist/stabilization/camera-shake.d.ts +42 -18
  616. package/dist/stabilization/camera-shake.js +43 -20
  617. package/dist/stabilization/camera-shake.js.map +1 -1
  618. package/dist/stabilization/system.d.ts +44 -41
  619. package/dist/stabilization/system.js +196 -108
  620. package/dist/stabilization/system.js.map +1 -1
  621. package/docs/AGENT_GUIDE.md +183 -0
  622. package/docs/API_REFERENCE.md +761 -2
  623. package/docs/QUICK_START.md +495 -0
  624. package/docs/USAGE.md +1719 -714
  625. package/docs/api/camera-equivalent-focal-length.md +6 -7
  626. package/docs/api/camera-field-of-view.md +16 -8
  627. package/docs/api/camera-lens-projection.md +246 -0
  628. package/docs/api/camera-projected-object-size.md +6 -5
  629. package/docs/api/capture-capture-export-metadata.md +10 -10
  630. package/docs/api/capture-dng-import.md +134 -0
  631. package/docs/api/capture-environment-raw-producer.md +17 -11
  632. package/docs/api/capture-export-jpeg.md +16 -0
  633. package/docs/api/capture-focus-control.md +25 -47
  634. package/docs/api/capture-linear-encoding.md +21 -12
  635. package/docs/api/capture-lossless-jpeg.md +49 -0
  636. package/docs/api/capture-native-environment-optical-stage.md +75 -0
  637. package/docs/api/capture-native-environment-raw.md +161 -4
  638. package/docs/api/capture-native-raw-development.md +69 -8
  639. package/docs/api/capture-native-raw-dng.md +84 -0
  640. package/docs/api/capture-native-raw.md +21 -10
  641. package/docs/api/capture-packed-sensor-raw.md +156 -0
  642. package/docs/api/capture-photographic-export.md +69 -31
  643. package/docs/api/capture-raw-frame-reconstruction.md +8 -8
  644. package/docs/api/capture-raw-white-level.md +37 -0
  645. package/docs/api/capture-release-sequence.md +6 -7
  646. package/docs/api/capture-sensor-raw-frame.md +6 -6
  647. package/docs/api/capture-sensor-raw-producer.md +17 -11
  648. package/docs/api/capture-simulated-capture.md +15 -15
  649. package/docs/api/color-camera-color-matrix.md +103 -0
  650. package/docs/api/color-capture-color.md +22 -11
  651. package/docs/api/color-chromatic-adaptation.md +82 -0
  652. package/docs/api/color-color-difference.md +134 -0
  653. package/docs/api/color-color-matching-functions.md +259 -0
  654. package/docs/api/color-colorimetry-math.md +54 -0
  655. package/docs/api/color-gamut-mapping.md +72 -0
  656. package/docs/api/color-illuminants.md +280 -0
  657. package/docs/api/color-rgb-color-spaces.md +151 -0
  658. package/docs/api/color-sensor-color-development.md +27 -0
  659. package/docs/api/color-srgb-icc.md +2 -4
  660. package/docs/api/color-white-balance.md +95 -19
  661. package/docs/api/composition-image-formation-plan.md +124 -95
  662. package/docs/api/composition-plan-consumer.md +7 -7
  663. package/docs/api/core-calculation-result.md +18 -14
  664. package/docs/api/core-configuration-error.md +1 -1
  665. package/docs/api/core-image-formation.md +7 -7
  666. package/docs/api/core-scientific-assurance.md +12 -10
  667. package/docs/api/core-spectral-composition.md +14 -14
  668. package/docs/api/core-spectral.md +6 -6
  669. package/docs/api/core-validation.md +3 -3
  670. package/docs/api/core-version.md +7 -6
  671. package/docs/api/equipment-exposure-capabilities.md +11 -10
  672. package/docs/api/equipment-generic-tier-assets.md +7 -2
  673. package/docs/api/equipment-generic-tier-presets.md +6 -6
  674. package/docs/api/equipment-iso-capabilities.md +18 -18
  675. package/docs/api/equipment-metering-capabilities.md +6 -6
  676. package/docs/api/equipment-release-capabilities.md +4 -4
  677. package/docs/api/exports.json +2213 -8
  678. package/docs/api/exposure-exposure-duration-control.md +9 -9
  679. package/docs/api/exposure-exposure-mode-resolver.md +35 -31
  680. package/docs/api/exposure-exposure.md +10 -10
  681. package/docs/api/exposure-flash-ttl.md +182 -0
  682. package/docs/api/exposure-flash.md +183 -28
  683. package/docs/api/exposure-metering-scene-radiance.md +11 -10
  684. package/docs/api/exposure-metering-target.md +9 -8
  685. package/docs/api/exposure-metering-temporal.md +8 -8
  686. package/docs/api/exposure-metering.md +69 -9
  687. package/docs/api/exposure-program-line.md +6 -5
  688. package/docs/api/facade-chart-radiance-batch.md +96 -0
  689. package/docs/api/facade-chart-scenes.md +240 -0
  690. package/docs/api/facade-scene-radiance.md +122 -0
  691. package/docs/api/facade-simulate-capture.md +324 -0
  692. package/docs/api/gpu-image-kernel-accelerator.md +157 -0
  693. package/docs/api/gpu-image-kernel-execution.md +152 -0
  694. package/docs/api/motion-camera-rotation.md +43 -26
  695. package/docs/api/motion-capture-rotation-inverse-mapping.md +24 -23
  696. package/docs/api/motion-capture-rotation-temporal-quadrature.md +22 -22
  697. package/docs/api/motion-capture-rotation-trajectory.md +15 -15
  698. package/docs/api/motion-capture-translation-parallax-temporal-quadrature.md +15 -14
  699. package/docs/api/motion-extended-object-projection.md +10 -8
  700. package/docs/api/motion-projected-motion.md +8 -8
  701. package/docs/api/motion-temporal-exposure-sum.md +98 -0
  702. package/docs/api/motion-video-sequence.md +159 -0
  703. package/docs/api/optics-aperture.md +8 -8
  704. package/docs/api/optics-atmospheric-haze.md +104 -0
  705. package/docs/api/optics-brown-conrady-distortion.md +123 -0
  706. package/docs/api/optics-circle-of-confusion.md +5 -4
  707. package/docs/api/optics-complex-pupil-psf.md +57 -16
  708. package/docs/api/optics-depth-of-field.md +12 -6
  709. package/docs/api/optics-diffraction.md +8 -7
  710. package/docs/api/optics-focus-breathing.md +104 -16
  711. package/docs/api/optics-focus-state.md +9 -10
  712. package/docs/api/optics-front-of-lens-filter.md +72 -14
  713. package/docs/api/optics-illumination-vignetting.md +10 -10
  714. package/docs/api/optics-irradiance-convolution.md +95 -0
  715. package/docs/api/optics-lateral-chromatic-aberration.md +22 -21
  716. package/docs/api/optics-lens-psf-kernels.md +72 -0
  717. package/docs/api/optics-lens-psf-profile.md +48 -26
  718. package/docs/api/optics-longitudinal-chromatic-aberration.md +128 -0
  719. package/docs/api/optics-polygon-diffraction.md +78 -13
  720. package/docs/api/optics-profile-contract.md +4 -4
  721. package/docs/api/optics-psf-foundation.md +12 -12
  722. package/docs/api/optics-pupil-vignetting.md +132 -0
  723. package/docs/api/optics-radial-distortion.md +12 -12
  724. package/docs/api/optics-scene-to-sensor-irradiance.md +25 -14
  725. package/docs/api/optics-scene-to-sensor-quadrature.md +15 -12
  726. package/docs/api/optics-sensor-aperture-rays.md +3 -2
  727. package/docs/api/optics-sensor-environment-query.md +52 -9
  728. package/docs/api/optics-sensor-psf-quadrature.md +13 -10
  729. package/docs/api/optics-stray-light.md +12 -11
  730. package/docs/api/optics-thin-lens.md +4 -3
  731. package/docs/api/optics-zernike-wavefront.md +229 -0
  732. package/docs/api/output-capture-corrected-sdr.md +10 -7
  733. package/docs/api/output-capture-geometry.md +35 -36
  734. package/docs/api/output-capture-sdr.md +4 -4
  735. package/docs/api/output-crop.md +7 -4
  736. package/docs/api/output-geometric-transforms.md +65 -19
  737. package/docs/api/output-lens-corrections.md +17 -15
  738. package/docs/api/output-native-capture-sdr.md +26 -13
  739. package/docs/api/output-png-encoder.md +105 -0
  740. package/docs/api/output-print-contrast-reference.md +8 -8
  741. package/docs/api/output-print-detail.md +14 -14
  742. package/docs/api/output-print-jpeg.md +22 -5
  743. package/docs/api/output-print-plan.md +18 -18
  744. package/docs/api/output-print-region-statistics.md +14 -14
  745. package/docs/api/output-sdr-rendering.md +52 -10
  746. package/docs/api/output-subject-framing-crop.md +5 -6
  747. package/docs/api/processing-demosaic.md +120 -0
  748. package/docs/api/processing-denoise.md +148 -0
  749. package/docs/api/processing-focus-stacking.md +41 -0
  750. package/docs/api/processing-hdr.md +113 -0
  751. package/docs/api/processing-panorama.md +82 -0
  752. package/docs/api/processing-processing-task.md +107 -0
  753. package/docs/api/processing-tone-mapping.md +142 -0
  754. package/docs/api/schema-camera.md +6 -4
  755. package/docs/api/schema-illumination-spectral.md +10 -11
  756. package/docs/api/schema-illumination-temporal.md +13 -15
  757. package/docs/api/schema-illumination.md +4 -4
  758. package/docs/api/schema-scene-radiance-temporal.md +10 -11
  759. package/docs/api/schema-scene-radiance.md +18 -17
  760. package/docs/api/schema-scene.md +9 -9
  761. package/docs/api/schema-validation.md +1 -1
  762. package/docs/api/sensor-accumulated-charge.md +12 -20
  763. package/docs/api/sensor-architecture.md +15 -16
  764. package/docs/api/sensor-camera-saturation-capacity.md +9 -11
  765. package/docs/api/sensor-capture-color-sampling-binding.md +47 -51
  766. package/docs/api/sensor-capture-mode-timing.md +9 -9
  767. package/docs/api/sensor-capture-mode.md +29 -30
  768. package/docs/api/sensor-color-sampling.md +31 -33
  769. package/docs/api/sensor-constant-rate-temporal-integration.md +10 -11
  770. package/docs/api/sensor-dark-current.md +30 -14
  771. package/docs/api/sensor-environment-photo-signal.md +13 -6
  772. package/docs/api/sensor-eqe-electron-rate.md +9 -8
  773. package/docs/api/sensor-eqe-local-exposure.md +18 -17
  774. package/docs/api/sensor-eqe-temporal-exposure.md +12 -11
  775. package/docs/api/sensor-exposure-window.md +24 -24
  776. package/docs/api/sensor-fixed-pattern-noise.md +104 -0
  777. package/docs/api/sensor-iso-gain.md +63 -0
  778. package/docs/api/sensor-iso-signal-chain.md +13 -22
  779. package/docs/api/sensor-local-exposure-binding.md +12 -13
  780. package/docs/api/sensor-optical-stack.md +37 -40
  781. package/docs/api/sensor-photon-energy.md +10 -11
  782. package/docs/api/sensor-physical-charge-capacity.md +8 -13
  783. package/docs/api/sensor-pixel-pitch.md +1 -1
  784. package/docs/api/sensor-radiometry-readiness.md +9 -9
  785. package/docs/api/sensor-raw-readout.md +118 -52
  786. package/docs/api/sensor-raw-reconstruction.md +97 -20
  787. package/docs/api/sensor-readout-exposure-linkage.md +120 -24
  788. package/docs/api/sensor-readout-timing.md +33 -35
  789. package/docs/api/sensor-response-application-compatibility.md +9 -11
  790. package/docs/api/sensor-response-operating-range.md +14 -18
  791. package/docs/api/sensor-responsivity-photocurrent.md +9 -12
  792. package/docs/api/sensor-sampling-aperture.md +22 -25
  793. package/docs/api/sensor-scene-eqe-temporal-exposure.md +9 -8
  794. package/docs/api/sensor-sensor-geometry.md +23 -24
  795. package/docs/api/sensor-signal-noise.md +10 -10
  796. package/docs/api/sensor-spatial-sample-reduction.md +27 -30
  797. package/docs/api/sensor-spatial-sampling-quadrature.md +37 -39
  798. package/docs/api/sensor-spatio-spectral-reduction.md +27 -29
  799. package/docs/api/sensor-spectral-coverage.md +5 -5
  800. package/docs/api/sensor-spectral-quadrature.md +33 -35
  801. package/docs/api/sensor-spectral-response.md +28 -16
  802. package/docs/api/sensor-temporal-photo-signal.md +6 -6
  803. package/docs/api/simulation-poc-simulation.md +17 -16
  804. package/docs/api/stabilization-camera-shake.md +29 -10
  805. package/docs/api/stabilization-system.md +25 -21
  806. package/docs/examples/production-request.json +3821 -2157
  807. package/docs/examples/simulate-capture.mjs +14 -0
  808. package/llms.txt +35 -0
  809. package/package.json +32 -12
  810. package/AGENTS.md +0 -674
  811. package/CONTRIBUTING.md +0 -76
  812. package/DCO +0 -33
  813. package/dist/camera/equivalent-focal-length.d.ts.map +0 -1
  814. package/dist/camera/field-of-view.d.ts.map +0 -1
  815. package/dist/camera/projected-object-size.d.ts.map +0 -1
  816. package/dist/capture/capture-export-metadata.d.ts.map +0 -1
  817. package/dist/capture/environment-raw-producer.d.ts.map +0 -1
  818. package/dist/capture/export-jpeg.d.ts.map +0 -1
  819. package/dist/capture/export-tiff.d.ts.map +0 -1
  820. package/dist/capture/focus-control.d.ts.map +0 -1
  821. package/dist/capture/linear-encoding.d.ts.map +0 -1
  822. package/dist/capture/native-environment-raw.d.ts.map +0 -1
  823. package/dist/capture/native-raw-development.d.ts.map +0 -1
  824. package/dist/capture/native-raw.d.ts.map +0 -1
  825. package/dist/capture/photographic-export.d.ts.map +0 -1
  826. package/dist/capture/raw-frame-limits.d.ts.map +0 -1
  827. package/dist/capture/raw-frame-reconstruction.d.ts.map +0 -1
  828. package/dist/capture/release-sequence.d.ts.map +0 -1
  829. package/dist/capture/sensor-raw-frame.d.ts.map +0 -1
  830. package/dist/capture/sensor-raw-producer.d.ts.map +0 -1
  831. package/dist/capture/simulated-capture.d.ts.map +0 -1
  832. package/dist/color/capture-color.d.ts.map +0 -1
  833. package/dist/color/sensor-color-development.d.ts.map +0 -1
  834. package/dist/color/srgb-icc.d.ts.map +0 -1
  835. package/dist/color/srgb-transfer.d.ts.map +0 -1
  836. package/dist/color/white-balance.d.ts.map +0 -1
  837. package/dist/composition/image-formation-plan.d.ts.map +0 -1
  838. package/dist/composition/plan-consumer.d.ts.map +0 -1
  839. package/dist/core/calculation-result.d.ts.map +0 -1
  840. package/dist/core/canonical-json.d.ts.map +0 -1
  841. package/dist/core/configuration-error.d.ts.map +0 -1
  842. package/dist/core/evidence-provenance.d.ts.map +0 -1
  843. package/dist/core/image-formation.d.ts.map +0 -1
  844. package/dist/core/owned-data.d.ts.map +0 -1
  845. package/dist/core/record-validation.d.ts.map +0 -1
  846. package/dist/core/scientific-assurance.d.ts.map +0 -1
  847. package/dist/core/spectral-composition.d.ts.map +0 -1
  848. package/dist/core/spectral.d.ts.map +0 -1
  849. package/dist/core/validation.d.ts.map +0 -1
  850. package/dist/core/version.d.ts.map +0 -1
  851. package/dist/equipment/exposure-capabilities.d.ts.map +0 -1
  852. package/dist/equipment/generic-tier-assets.d.ts.map +0 -1
  853. package/dist/equipment/generic-tier-presets.d.ts.map +0 -1
  854. package/dist/equipment/iso-capabilities.d.ts.map +0 -1
  855. package/dist/equipment/metering-capabilities.d.ts.map +0 -1
  856. package/dist/equipment/release-capabilities.d.ts.map +0 -1
  857. package/dist/exposure/exposure-duration-control.d.ts.map +0 -1
  858. package/dist/exposure/exposure-mode-resolver.d.ts.map +0 -1
  859. package/dist/exposure/exposure.d.ts.map +0 -1
  860. package/dist/exposure/flash.d.ts.map +0 -1
  861. package/dist/exposure/metering-scene-radiance.d.ts.map +0 -1
  862. package/dist/exposure/metering-target.d.ts.map +0 -1
  863. package/dist/exposure/metering-temporal.d.ts.map +0 -1
  864. package/dist/exposure/metering.d.ts.map +0 -1
  865. package/dist/exposure/program-line.d.ts.map +0 -1
  866. package/dist/index.d.ts.map +0 -1
  867. package/dist/motion/camera-rotation.d.ts.map +0 -1
  868. package/dist/motion/capture-rotation-inverse-mapping.d.ts.map +0 -1
  869. package/dist/motion/capture-rotation-temporal-quadrature.d.ts.map +0 -1
  870. package/dist/motion/capture-rotation-trajectory.d.ts.map +0 -1
  871. package/dist/motion/capture-translation-parallax-temporal-quadrature.d.ts.map +0 -1
  872. package/dist/motion/extended-object-projection.d.ts.map +0 -1
  873. package/dist/motion/projected-motion.d.ts.map +0 -1
  874. package/dist/optics/aperture.d.ts.map +0 -1
  875. package/dist/optics/circle-of-confusion.d.ts.map +0 -1
  876. package/dist/optics/complex-pupil-psf.d.ts.map +0 -1
  877. package/dist/optics/depth-of-field.d.ts.map +0 -1
  878. package/dist/optics/diffraction.d.ts.map +0 -1
  879. package/dist/optics/focus-breathing.d.ts.map +0 -1
  880. package/dist/optics/focus-state.d.ts.map +0 -1
  881. package/dist/optics/front-of-lens-filter.d.ts.map +0 -1
  882. package/dist/optics/illumination-vignetting.d.ts.map +0 -1
  883. package/dist/optics/lateral-chromatic-aberration.d.ts.map +0 -1
  884. package/dist/optics/lens-psf-profile.d.ts.map +0 -1
  885. package/dist/optics/polygon-diffraction.d.ts.map +0 -1
  886. package/dist/optics/profile-contract.d.ts.map +0 -1
  887. package/dist/optics/psf-foundation.d.ts.map +0 -1
  888. package/dist/optics/radial-distortion.d.ts.map +0 -1
  889. package/dist/optics/scene-to-sensor-irradiance.d.ts.map +0 -1
  890. package/dist/optics/scene-to-sensor-quadrature.d.ts.map +0 -1
  891. package/dist/optics/sensor-aperture-rays.d.ts.map +0 -1
  892. package/dist/optics/sensor-environment-query.d.ts.map +0 -1
  893. package/dist/optics/sensor-psf-quadrature.d.ts.map +0 -1
  894. package/dist/optics/sensor-psf-support.d.ts.map +0 -1
  895. package/dist/optics/stray-light.d.ts.map +0 -1
  896. package/dist/optics/thin-lens.d.ts.map +0 -1
  897. package/dist/output/capture-corrected-sdr.d.ts.map +0 -1
  898. package/dist/output/capture-geometry.d.ts.map +0 -1
  899. package/dist/output/capture-sdr.d.ts.map +0 -1
  900. package/dist/output/crop.d.ts.map +0 -1
  901. package/dist/output/geometric-transforms.d.ts.map +0 -1
  902. package/dist/output/lens-corrections.d.ts.map +0 -1
  903. package/dist/output/native-capture-sdr.d.ts.map +0 -1
  904. package/dist/output/print-contrast-reference.d.ts.map +0 -1
  905. package/dist/output/print-detail.d.ts.map +0 -1
  906. package/dist/output/print-jpeg.d.ts.map +0 -1
  907. package/dist/output/print-plan.d.ts.map +0 -1
  908. package/dist/output/print-region-statistics.d.ts.map +0 -1
  909. package/dist/output/sdr-rendering.d.ts.map +0 -1
  910. package/dist/output/subject-framing-crop.d.ts.map +0 -1
  911. package/dist/schema/camera.d.ts.map +0 -1
  912. package/dist/schema/camera.js +0 -3
  913. package/dist/schema/camera.js.map +0 -1
  914. package/dist/schema/illumination-spectral.d.ts.map +0 -1
  915. package/dist/schema/illumination-temporal.d.ts.map +0 -1
  916. package/dist/schema/illumination.d.ts.map +0 -1
  917. package/dist/schema/scene-radiance-temporal.d.ts.map +0 -1
  918. package/dist/schema/scene-radiance.d.ts.map +0 -1
  919. package/dist/schema/scene.d.ts.map +0 -1
  920. package/dist/schema/scene.js +0 -3
  921. package/dist/schema/scene.js.map +0 -1
  922. package/dist/schema/validation.d.ts.map +0 -1
  923. package/dist/sensor/accumulated-charge.d.ts.map +0 -1
  924. package/dist/sensor/architecture.d.ts.map +0 -1
  925. package/dist/sensor/camera-saturation-capacity.d.ts.map +0 -1
  926. package/dist/sensor/capture-color-sampling-binding.d.ts.map +0 -1
  927. package/dist/sensor/capture-mode-timing.d.ts.map +0 -1
  928. package/dist/sensor/capture-mode.d.ts.map +0 -1
  929. package/dist/sensor/color-sampling.d.ts.map +0 -1
  930. package/dist/sensor/constant-rate-temporal-integration.d.ts.map +0 -1
  931. package/dist/sensor/dark-current.d.ts.map +0 -1
  932. package/dist/sensor/environment-photo-signal.d.ts.map +0 -1
  933. package/dist/sensor/eqe-electron-rate.d.ts.map +0 -1
  934. package/dist/sensor/eqe-local-exposure.d.ts.map +0 -1
  935. package/dist/sensor/eqe-temporal-exposure.d.ts.map +0 -1
  936. package/dist/sensor/exposure-window.d.ts.map +0 -1
  937. package/dist/sensor/iso-signal-chain.d.ts.map +0 -1
  938. package/dist/sensor/local-exposure-binding.d.ts.map +0 -1
  939. package/dist/sensor/optical-stack.d.ts.map +0 -1
  940. package/dist/sensor/photon-energy.d.ts.map +0 -1
  941. package/dist/sensor/physical-charge-capacity.d.ts.map +0 -1
  942. package/dist/sensor/pixel-pitch.d.ts.map +0 -1
  943. package/dist/sensor/radiometry-readiness.d.ts.map +0 -1
  944. package/dist/sensor/raw-readout.d.ts.map +0 -1
  945. package/dist/sensor/raw-reconstruction.d.ts.map +0 -1
  946. package/dist/sensor/readout-exposure-linkage.d.ts.map +0 -1
  947. package/dist/sensor/readout-timing.d.ts.map +0 -1
  948. package/dist/sensor/response-application-compatibility.d.ts.map +0 -1
  949. package/dist/sensor/response-operating-range.d.ts.map +0 -1
  950. package/dist/sensor/responsivity-photocurrent.d.ts.map +0 -1
  951. package/dist/sensor/sampling-aperture.d.ts.map +0 -1
  952. package/dist/sensor/scene-eqe-temporal-exposure.d.ts.map +0 -1
  953. package/dist/sensor/sensor-geometry.d.ts.map +0 -1
  954. package/dist/sensor/signal-noise.d.ts.map +0 -1
  955. package/dist/sensor/spatial-sample-reduction.d.ts.map +0 -1
  956. package/dist/sensor/spatial-sampling-quadrature.d.ts.map +0 -1
  957. package/dist/sensor/spatio-spectral-reduction.d.ts.map +0 -1
  958. package/dist/sensor/spectral-coverage.d.ts.map +0 -1
  959. package/dist/sensor/spectral-quadrature.d.ts.map +0 -1
  960. package/dist/sensor/spectral-response.d.ts.map +0 -1
  961. package/dist/sensor/temporal-eqe-sum.d.ts.map +0 -1
  962. package/dist/sensor/temporal-photo-signal.d.ts.map +0 -1
  963. package/dist/sensor/temporal-sample-binding.d.ts.map +0 -1
  964. package/dist/simulation/poc-simulation.d.ts.map +0 -1
  965. package/dist/stabilization/camera-shake.d.ts.map +0 -1
  966. package/dist/stabilization/system.d.ts.map +0 -1
  967. package/docs/API_STYLE.md +0 -235
  968. package/docs/ARCHITECTURE.md +0 -356
  969. package/docs/AUTOMATION_COORDINATION.md +0 -190
  970. package/docs/CAPTURE_COLOR.md +0 -25
  971. package/docs/CAPTURE_CORRECTED_SDR.md +0 -25
  972. package/docs/CAPTURE_EXPORT_METADATA.md +0 -27
  973. package/docs/CAPTURE_SDR.md +0 -22
  974. package/docs/DENSE_PUPIL_QUALIFICATION.md +0 -121
  975. package/docs/DEVELOPERS.md +0 -68
  976. package/docs/DIGITAL_OPTICS_FOUNDATION.md +0 -69
  977. package/docs/ENVIRONMENT_RAW_CAPTURE.md +0 -208
  978. package/docs/EXPORT_ACCEPTANCE.md +0 -133
  979. package/docs/GENERIC_TIER_PRESETS.md +0 -269
  980. package/docs/GEOMETRIC_PREPARATION_REUSE.md +0 -141
  981. package/docs/IMAGE_FORMATION.md +0 -247
  982. package/docs/LINEAR_CAPTURE_ENCODING.md +0 -25
  983. package/docs/MOTION_AND_SIGNAL.md +0 -250
  984. package/docs/NATIVE_CAPTURE_SDR.md +0 -70
  985. package/docs/NATIVE_CAPTURE_SDR_REVIEW.md +0 -37
  986. package/docs/NATIVE_CAPTURE_SDR_VALIDATION.md +0 -34
  987. package/docs/NATIVE_RAW_AND_PRINT.md +0 -54
  988. package/docs/NUMERICAL_CORRECTNESS.md +0 -191
  989. package/docs/PERFORMANCE_BASELINE.md +0 -56
  990. package/docs/PHOTOGRAPHIC_EXPORT.md +0 -206
  991. package/docs/PHYSICS_FOUNDATION.md +0 -426
  992. package/docs/POC_API.md +0 -278
  993. package/docs/POC_PROJECTION_PLATFORM_REFERENCE.md +0 -12
  994. package/docs/POC_PROJECTION_REUSE.md +0 -124
  995. package/docs/PRINT_195_196_REVIEW.md +0 -93
  996. package/docs/PRINT_196_ENGINE_ACCEPTANCE.md +0 -45
  997. package/docs/PRINT_CONTRAST_REFERENCE.md +0 -43
  998. package/docs/PRINT_DETAIL_ASSESSMENT.md +0 -81
  999. package/docs/PRINT_DETAIL_JPEG_EVIDENCE.md +0 -36
  1000. package/docs/PRINT_DETAIL_SDR_EVIDENCE.md +0 -29
  1001. package/docs/PRINT_DETAIL_VALIDATION.md +0 -36
  1002. package/docs/PRINT_PLANNING.md +0 -75
  1003. package/docs/PRINT_REGION_STATISTICS.md +0 -38
  1004. package/docs/PROCESSED_OUTPUT.md +0 -157
  1005. package/docs/PRODUCTION_COMPOSITION.md +0 -373
  1006. package/docs/PRODUCTION_ENVIRONMENT_CAPTURE.md +0 -131
  1007. package/docs/PROVENANCE.md +0 -115
  1008. package/docs/PSF_FOUNDATION.md +0 -165
  1009. package/docs/RAW_FRAME_ENVELOPE.md +0 -56
  1010. package/docs/RAW_FRAME_RECONSTRUCTION.md +0 -30
  1011. package/docs/RELEASE_1_0.md +0 -66
  1012. package/docs/RELEASE_1_0_1.md +0 -61
  1013. package/docs/RELEASE_1_1_0.md +0 -25
  1014. package/docs/RELEASE_1_2_0.md +0 -51
  1015. package/docs/RELEASE_1_3_0.md +0 -15
  1016. package/docs/RELEASE_1_4_0.md +0 -23
  1017. package/docs/RELEASE_1_5_0.md +0 -105
  1018. package/docs/RELEASE_AUDIT.md +0 -39
  1019. package/docs/SCENE_RADIANCE_AND_ILLUMINATION.md +0 -298
  1020. package/docs/SCENE_SENSOR_QUADRATURE.md +0 -98
  1021. package/docs/SCENE_SENSOR_TEMPORAL_EXPOSURE.md +0 -86
  1022. package/docs/SCIENTIFIC_ASSURANCE.md +0 -280
  1023. package/docs/SCIENTIFIC_CONFORMANCE.md +0 -83
  1024. package/docs/SDR_RENDERING.md +0 -33
  1025. package/docs/SENSOR_APERTURE_RAYS.md +0 -23
  1026. package/docs/SENSOR_ENVIRONMENT_QUERY.md +0 -86
  1027. package/docs/SENSOR_EQE_LOCAL_EXPOSURE.md +0 -104
  1028. package/docs/SENSOR_EQE_TEMPORAL_EXPOSURE.md +0 -74
  1029. package/docs/SENSOR_PSF_QUADRATURE.md +0 -90
  1030. package/docs/SENSOR_RAW_FRAME.md +0 -20
  1031. package/docs/SENSOR_RAW_PRODUCER.md +0 -77
  1032. package/docs/SEPARABLE_EMISSION_DRAFT.md +0 -103
  1033. package/docs/SIGNAL_CONFORMANCE.md +0 -23
  1034. package/docs/SIMULATED_CAPTURE.md +0 -65
  1035. package/docs/STABILIZATION.md +0 -234
  1036. package/docs/TEMPORAL_PHOTO_RAW.md +0 -83
  1037. package/docs/TIER_ACCEPTANCE.md +0 -129
  1038. package/docs/TIER_PRESET_ACCEPTANCE.md +0 -59
  1039. package/docs/V1_COMPOSITION_MAP.md +0 -123
  1040. package/docs/V1_HELPER_REUSE_REVIEW.md +0 -60
  1041. package/docs/V1_PERFORMANCE_DISPOSITION.md +0 -88
  1042. package/docs/benchmarks/2026-10-01-main-baseline.json +0 -694
  1043. package/docs/benchmarks/2026-10-02-geometric-preparation-reuse.json +0 -2190
  1044. package/docs/benchmarks/2026-10-02-poc-projection-reuse.json +0 -2423
  1045. package/docs/benchmarks/2026-10-02-v1-environment-gc-profile.json +0 -210
  1046. package/docs/benchmarks/2026-10-02-v1-environment-ordinary.json +0 -187
  1047. package/docs/benchmarks/2026-10-02-v1-geometry-gc-profile.json +0 -756
  1048. package/docs/benchmarks/2026-10-02-v1-geometry-ordinary-1.json +0 -711
  1049. package/docs/benchmarks/2026-10-02-v1-geometry-ordinary-2.json +0 -711
  1050. package/docs/benchmarks/2026-10-02-v1-poc-gc-profile.json +0 -885
  1051. package/docs/benchmarks/2026-10-02-v1-poc-ordinary-1.json +0 -868
  1052. package/docs/benchmarks/2026-10-02-v1-poc-ordinary-2.json +0 -868
  1053. package/docs/licenses/castleCSF-MIT.txt +0 -21
  1054. package/docs/validation/dense-pupil-2026-10-06/pilot.jsonl +0 -6
  1055. package/docs/validation/documentation-fragments.json +0 -212
  1056. package/docs/validation/native-capture-sdr-darwin-arm64-node26-review.jsonl +0 -4
  1057. package/docs/validation/native-capture-sdr-darwin-arm64.jsonl +0 -3
  1058. package/docs/validation/native-raw-print-2026-10-04/fractional-asymmetric-crop.jpg +0 -0
  1059. package/docs/validation/native-raw-print-2026-10-04/gray-ramp.jpg +0 -0
  1060. package/docs/validation/native-raw-print-2026-10-04/linear-area-checker.jpg +0 -0
  1061. package/docs/validation/native-raw-print-2026-10-04/manifest.json +0 -218
  1062. package/docs/validation/native-raw-print-2026-10-04/megapixel-native.jpg +0 -0
  1063. package/docs/validation/native-raw-print-2026-10-04/native-color.jpg +0 -0
  1064. package/docs/validation/native-raw-print-2026-10-04/photivra-native-full-megapixel-validation.json +0 -9
  1065. package/docs/validation/native-raw-print-2026-10-04/photivra-native-full-megapixel.json +0 -29
  1066. package/docs/validation/native-raw-print-2026-10-04/photivra-native-full-megapixel.json.codes.u16le.gz +0 -0
  1067. package/docs/validation/native-raw-print-2026-10-04/photivra-native-full-megapixel.json.jpg +0 -0
  1068. package/docs/validation/native-raw-print-2026-10-04/photivra-native-full-megapixel.json.rgb.gz +0 -0
  1069. package/docs/validation/native-raw-print-2026-10-04/srgb.icc +0 -0
  1070. package/docs/validation/native-raw-print-2026-10-04/validation.json +0 -41
  1071. package/docs/validation/print-196-candidate-capabilities.json +0 -28
  1072. package/docs/validation/print-196-candidate-evidence.json +0 -356
  1073. package/docs/validation/print-196-engine-acceptance.json +0 -81
  1074. package/docs/validation/print-detail-capabilities.json +0 -103
  1075. package/docs/validation/print-detail-jpeg-evidence.json +0 -116
  1076. package/docs/validation/print-detail-sdr-evidence.json +0 -81
  1077. package/docs/validation/release-audit-inventory.json +0 -1306
  1078. package/docs/validation/tier-acceptance-report-v1.json +0 -2538
@@ -0,0 +1,1026 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ /**
3
+ * `simulateCapture()` is the easy starting point. It turns a small input into a complete native
4
+ * environment RAW request: scene radiance → optics → spectral EQE (external quantum efficiency) →
5
+ * charge → readout and ADC. That request then runs through the existing native RAW development task
6
+ * and, optionally, the existing JPEG encoder.
7
+ *
8
+ * It adds no physics of its own. Every number comes from existing public contracts: generic tier
9
+ * presets, exposure metering and mode resolution, colorimetry, native environment RAW, native RAW
10
+ * development and print JPEG.
11
+ *
12
+ * Everything the facade decides for the caller is listed in `result.assumptions`: defaults, derived
13
+ * values and fixed modelling choices. Nothing is hidden. The complete underlying request is returned
14
+ * in `result.request`; get the per-site inputs with `request.siteInput(index)`.
15
+ *
16
+ * The 2.0 options are opt-in and only combine existing public contracts. If none is given, the
17
+ * request, RAW, samples and JPEG are byte-identical to the 1.0 facade:
18
+ * - output formats: `png` (`serializePngImage`; sRGB, or Display P3 / Rec. 2020 through the colorimetry
19
+ * matrices and transfer functions) and `dng`
20
+ * (`serializeNativeRawDng` of the packed native RAW; no linear master is declared);
21
+ * - demosaic: `createBuiltInBayerDemosaicProfiles` (bilinear) and mirror borders;
22
+ * - optics: the environment `lensFieldMapping` (projection, radial / Brown–Conrady distortion), radial
23
+ * illumination vignetting as field throughput and a caller sampled lens PSF;
24
+ * - motion: constant camera angular velocity and the electronic rolling-shutter exposure schedule.
25
+ * @see docs/QUICK_START.md
26
+ */
27
+ import { InvalidConfigurationError } from "../core/configuration-error.js";
28
+ import { requireInputObject } from "../core/record-validation.js";
29
+ import { ENGINE_API_VERSION } from "../core/version.js";
30
+ import { GENERIC_TIER_ASSETS } from "../equipment/generic-tier-assets.js";
31
+ import { parseCallerCameraPreset } from "./camera-preset.js";
32
+ import { createHostYield } from "./host-yield.js";
33
+ import { resolveGenericEquipmentExposureCapabilities } from "../equipment/exposure-capabilities.js";
34
+ import { resolveGenericIsoSignalChain } from "../sensor/iso-signal-chain.js";
35
+ import { meterRelativeExposure } from "../exposure/metering.js";
36
+ import { createExposureMeterTargetFromMeteringResult, setExposureCompensationOnMeterTarget } from "../exposure/metering-target.js";
37
+ import { resolveManualExposureMode, resolveAperturePriorityExposureMode, resolveShutterPriorityExposureMode, resolveProgramAutoExposureMode } from "../exposure/exposure-mode-resolver.js";
38
+ import { parseExposureProgramLineProfile } from "../exposure/program-line.js";
39
+ import { resolveCaptureGeometry } from "../output/capture-geometry.js";
40
+ import { estimateCie1931ColorMatchingFunctions } from "../color/color-matching-functions.js";
41
+ import { createColorTemperatureWhiteBalanceIntent, resolveColorTemperatureWhiteBalance, resolveCustomWhiteBalance, resolveManualWhiteBalance, estimateAutoWhiteBalance } from "../color/white-balance.js";
42
+ import { createCaptureWhiteBalanceIntent } from "../capture/simulated-capture.js";
43
+ import { SENSOR_RAW_PRODUCER_NOISE_MODEL } from "../capture/sensor-raw-producer.js";
44
+ import { calculateNativeRawPlan } from "../capture/native-raw.js";
45
+ import { createNativeEnvironmentRawTask } from "../capture/native-environment-raw.js";
46
+ import { createNativeRawDevelopmentTask } from "../capture/native-raw-development.js";
47
+ import { parseSdrRenderingProfile } from "../output/sdr-rendering.js";
48
+ import { createPrintJpegTask } from "../output/print-jpeg.js";
49
+ import { checkNativeRawIntegrity, prepareNativeRawDevelopment } from "../capture/native-raw-development.js";
50
+ import { createRawPixelDeveloper } from "../capture/raw-development-kernel.js";
51
+ import { BUILT_IN_BAYER_DEMOSAIC_MODEL_VERSION as BUILT_IN_DEMOSAIC_VERSION, createBuiltInBayerDemosaicProfiles } from "../sensor/raw-reconstruction.js";
52
+ import { transformOrientedRasterPointToNative } from "../output/capture-geometry.js";
53
+ import { resolveCaptureColorModel } from "../color/capture-color.js";
54
+ import { evaluateBt709Oetf, evaluateSrgbEncoding, resolveRgbColorSpace } from "../color/rgb-color-spaces.js";
55
+ import { serializePngImage } from "../output/png-encoder.js";
56
+ import { parseLensProjectionModel } from "../camera/lens-projection.js";
57
+ import { prepareEnvironmentLensFieldMapping } from "../optics/sensor-environment-query.js";
58
+ import { createRollingShutterCaptureExposureSchedule, resolveElectronicRollingShutterExposureSchedule } from "../sensor/readout-exposure-linkage.js";
59
+ import { calculateCaptureExposureWindows } from "../sensor/exposure-window.js";
60
+ import { multiplyMatrices } from "../color/colorimetry-math.js";
61
+ import { prepareRgbGamutMapping } from "../color/gamut-mapping.js";
62
+ import { serializeNativeRawDng } from "../capture/native-raw-dng.js";
63
+ import { resolveSensorReadoutRegime } from "../sensor/raw-readout.js";
64
+ import { resolveChartScene, CHART_SCENE_PRESETS } from "./chart-scenes.js";
65
+ import { resolveCaptureIlluminant, calculateIlluminantIrradianceScale, buildPlanarSceneRadianceProvider, requireCallerScene, FACADE_SPECTRAL_RANGE_NANOMETERS, PHOTOPIC_MAXIMUM_LUMINOUS_EFFICACY_LM_PER_W } from "./scene-radiance.js";
66
+ /** Version of how the facade builds requests. It changes whenever the same input would build a different request. */
67
+ export const SIMULATE_CAPTURE_FACADE_VERSION = "1.0.0";
68
+ const FACADE_EVIDENCE = Object.freeze([Object.freeze({ sourceOrigin: "photivra",
69
+ sourceReference: "photivra:simulate-capture-facade", reuseStatus: "photivra-owned" })]);
70
+ const DEFAULTS = Object.freeze({ camera: "professional", fNumber: 8, shutterSeconds: 1 / 125, iso: 100, compensationStops: 0,
71
+ subjectDistanceM: 5, width: 64, height: 64, format: "srgb-8", toneCurve: "identity", highlightMode: "clip",
72
+ whiteBalance: "scene-illuminant", jpegQuantizationStep: 2, pixelPitchMicrometers: 6, spectralStepNanometers: 20,
73
+ temporalSampleCount: 1, noiseSeed: 1, illuminantKelvin: 6500 });
74
+ /** Standard output sensitivity S = 10 / H (CIPA DC-004-2020 §3.2): the reference exposure in lx·s is 10 / S. */
75
+ const SOS_REFERENCE_EXPOSURE_NUMERATOR_LUX_SECONDS = 10;
76
+ /** Camera equation H = 0.65·L·t/N² (CIPA DC-004-2020 §5.5 (4); 0.65 = τ·π/4 with τ = 0.828, Explanation 4). */
77
+ const CAMERA_EQUATION_CONSTANT = 0.65;
78
+ /** Neutral reference reflectance: DC-004 ties the SOS reference level to an 18 % grey (Explanation 1 and 2.2). */
79
+ const GREY_CARD_REFLECTANCE = 0.18;
80
+ /** Scene luminance (cd/m²) whose focal-plane exposure 0.65·L·t/N² equals the SOS reference exposure 10/S. */
81
+ function sosReferenceLuminance(fNumber, shutterSeconds, iso) {
82
+ return SOS_REFERENCE_EXPOSURE_NUMERATOR_LUX_SECONDS * fNumber ** 2 / (CAMERA_EQUATION_CONSTANT * shutterSeconds * iso);
83
+ }
84
+ /**
85
+ * Default illuminance, lux: an 18 % Lambertian grey card (L = 0.18·E/π) then gets exactly the CIPA DC-004 standard
86
+ * output sensitivity reference exposure H = 10/S at f/8, 1/125 s, ISO 100:
87
+ * E = π·10·N²/(0.65·t·S·0.18) = π·80 000/11.7 ≈ 21 480.98 lux.
88
+ */
89
+ export const SIMULATE_CAPTURE_DEFAULT_ILLUMINANCE_LUX = Math.PI * sosReferenceLuminance(DEFAULTS.fNumber, DEFAULTS.shutterSeconds, DEFAULTS.iso) /
90
+ GREY_CARD_REFLECTANCE;
91
+ /** Demosaic border, native sites per side. */
92
+ const MARGIN = 1;
93
+ /** Default temporal midpoints when the camera rotates (2.0 motion option). */
94
+ const ROTATING_TEMPORAL_SAMPLE_COUNT = 8;
95
+ const IDS = Object.freeze({ frame: "facade-frame", mode: "native-still", cfa: "photivra-generic-bayer", binding: "facade-native-cfa",
96
+ sceneState: "facade-scene-state", scene: "facade-scene", provider: "facade-provider", lights: "facade-lights", materials: "facade-materials",
97
+ capture: "facade-capture", spectral: "facade-colorimetric-eqe", sampling: "facade-sampling", stack: "facade-no-aa",
98
+ stationarity: "facade-stationarity", operatingState: "facade-operating-state", dark: "facade-dark", completeness: "facade-completeness",
99
+ chargeSampling: "facade-charge-sampling", capacity: "facade-capacity" });
100
+ const CHANNELS = ["red", "green", "blue"];
101
+ /**
102
+ * Synthetic colorimetric sensor: channel EQE(λ) = a·cmf(λ)·(555 nm/λ), with cmf = x̄, ȳ, z̄. The λ factor turns
103
+ * the energy-weighted CMF (colour-matching function) into a photon-counting response. So channel counts are
104
+ * proportional to CIE XYZ (the Luther–Ives condition holds, up to quadrature error), and the camera→XYZ
105
+ * matrix is diagonal.
106
+ */
107
+ const EQE_SCALE = Object.freeze({ red: 0.5, green: 0.5, blue: 0.25 });
108
+ const CMF_KEY = Object.freeze({ red: "x", green: "y", blue: "z" });
109
+ const CAMERA_TO_XYZ = Object.freeze([[EQE_SCALE.green / EQE_SCALE.red, 0, 0], [0, 1, 0], [0, 0, EQE_SCALE.green / EQE_SCALE.blue]].map((r) => Object.freeze(r)));
110
+ const XYZ_TO_CAMERA = [[EQE_SCALE.red / EQE_SCALE.green, 0, 0], [0, 1, 0], [0, 0, EQE_SCALE.blue / EQE_SCALE.green]];
111
+ /** Synthetic dark current per site at the operating temperature, e⁻/s. */
112
+ const DARK_CURRENT_ELECTRONS_PER_SECOND = 1;
113
+ const OPERATING_TEMPERATURE_C = 20;
114
+ function fail(message) { throw new InvalidConfigurationError(message); }
115
+ function positive(value, name) {
116
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0)
117
+ fail(name + " must be a positive finite number.");
118
+ return value;
119
+ }
120
+ function integerIn(value, name, minimum, maximum) {
121
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < minimum || value > maximum)
122
+ fail(name + " must be an integer from " + minimum + " to " + maximum + ".");
123
+ return value;
124
+ }
125
+ function allowKeys(value, name, keys) {
126
+ if (value === undefined)
127
+ return {};
128
+ requireInputObject(value, name);
129
+ for (const key of Object.keys(value))
130
+ if (!keys.includes(key))
131
+ fail("Unknown " + name + " field: " + key + ".");
132
+ return value;
133
+ }
134
+ function sampled(values) {
135
+ return { wavelengthUnit: "nm", wavelengthBasis: "vacuum", interpolation: "piecewise-linear", outsideRangeBehavior: "fail-closed", evidence: FACADE_EVIDENCE,
136
+ samples: values.map((s) => ({ wavelengthNanometers: s.nm, value: s.value })) };
137
+ }
138
+ async function sha256Hex(text) {
139
+ const subtle = globalThis.crypto?.subtle;
140
+ if (!subtle)
141
+ fail("simulateCapture needs crypto.subtle (a secure browser context or Node 22+) to digest the scene description.");
142
+ const bytes = new Uint8Array(await subtle.digest("SHA-256", new TextEncoder().encode(text)));
143
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
144
+ }
145
+ /**
146
+ * The form in which a caller spectrum enters the scene digest. Tabulated data is hashed exactly as its
147
+ * (wavelength, value) pairs. A function spectrum has no data to hash, so it is sampled at the facade's
148
+ * 1 nm integer nodes over 380-780 nm (the grid the facade also uses to normalize illuminance).
149
+ */
150
+ function spectrumDigestForm(raw, prepared) {
151
+ const record = raw;
152
+ if (record.kind === "tabulated" && Array.isArray(record.samples)) {
153
+ return { kind: "tabulated", samples: record.samples.map((sample) => [sample.wavelengthNanometers, sample.value]) };
154
+ }
155
+ const values = [];
156
+ for (let nm = FACADE_SPECTRAL_RANGE_NANOMETERS.minimum; nm <= FACADE_SPECTRAL_RANGE_NANOMETERS.maximum; nm++)
157
+ values.push(prepared.evaluate(nm));
158
+ return { kind: "function-sampled-1nm", rangeNanometers: [FACADE_SPECTRAL_RANGE_NANOMETERS.minimum, FACADE_SPECTRAL_RANGE_NANOMETERS.maximum], values };
159
+ }
160
+ function luminanceOf(radiance) {
161
+ let sum = 0;
162
+ for (let nm = FACADE_SPECTRAL_RANGE_NANOMETERS.minimum; nm <= FACADE_SPECTRAL_RANGE_NANOMETERS.maximum; nm += 5) {
163
+ const w = nm === FACADE_SPECTRAL_RANGE_NANOMETERS.minimum || nm === FACADE_SPECTRAL_RANGE_NANOMETERS.maximum ? 2.5 : 5;
164
+ sum += w * radiance(nm) * estimateCie1931ColorMatchingFunctions(nm).y;
165
+ }
166
+ return PHOTOPIC_MAXIMUM_LUMINOUS_EFFICACY_LM_PER_W * sum;
167
+ }
168
+ function illuminantXyz(illuminant) {
169
+ let x = 0, y = 0, z = 0;
170
+ for (let nm = FACADE_SPECTRAL_RANGE_NANOMETERS.minimum; nm <= FACADE_SPECTRAL_RANGE_NANOMETERS.maximum; nm++) {
171
+ const s = illuminant.shape.evaluate(nm), c = estimateCie1931ColorMatchingFunctions(nm);
172
+ x += s * c.x;
173
+ y += s * c.y;
174
+ z += s * c.z;
175
+ }
176
+ return [x / y, 1, z / y];
177
+ }
178
+ /** Bilinear RGGB demosaic as explicit linear neighbourhood kernels (weights sum to one per output channel). */
179
+ function bilinearPhaseProfiles() {
180
+ const cross = [[1, 0], [-1, 0], [0, 1], [0, -1]], diagonal = [[1, 1], [1, -1], [-1, 1], [-1, -1]], horizontal = [[1, 0], [-1, 0]], vertical = [[0, 1], [0, -1]];
181
+ const average = (offsets, channel) => offsets.map(([x, y]) => ({ offsetX: x, offsetY: y, sourceChannelId: channel, weight: 1 / offsets.length }));
182
+ const self = (channel) => [{ offsetX: 0, offsetY: 0, sourceChannelId: channel, weight: 1 }];
183
+ const kernels = {
184
+ "0,0": { red: self("red"), green: average(cross, "green"), blue: average(diagonal, "blue") },
185
+ "1,0": { red: average(horizontal, "red"), green: self("green"), blue: average(vertical, "blue") },
186
+ "0,1": { red: average(vertical, "red"), green: self("green"), blue: average(horizontal, "blue") },
187
+ "1,1": { red: average(diagonal, "red"), green: average(cross, "green"), blue: self("blue") }
188
+ };
189
+ return [[0, 0], [1, 0], [0, 1], [1, 1]].map(([px, py], index) => ({ phaseX: px, phaseY: py, profile: { schemaVersion: "0.1.0",
190
+ profileId: "facade-bilinear-phase-" + index, profileVersion: "1", captureModeId: IDS.mode, colorSamplingProfileId: IDS.cfa,
191
+ scientificStatus: "approximation", method: "explicit-linear-native-neighborhood",
192
+ normalization: "weights-sum-to-one-per-output-channel", negativeBlackSubtractedValuesAllowed: true,
193
+ kernels: CHANNELS.map((c) => ({ outputChannelId: c, contributions: kernels[px + "," + py][c] })), evidence: FACADE_EVIDENCE,
194
+ limitations: ["Bilinear interpolation of the RGGB mosaic; no edge-directed demosaic, false-colour suppression or calibration."] } }));
195
+ }
196
+ function defaultProgramLine() {
197
+ // Generic educational program line written for the engine, relative to the f/8, 1/125 s reference. For less light
198
+ // it first opens the aperture toward f/4, then lengthens the shutter. For more light it first closes toward
199
+ // f/16, then shortens the shutter.
200
+ return parseExposureProgramLineProfile({ schemaVersion: "0.1.0", profileId: "facade-generic-program-line", profileVersion: "1.0.0",
201
+ scientificStatus: "approximation", policyKind: "generic-program-line", interpolation: "log2-aperture-shutter", evidence: FACADE_EVIDENCE,
202
+ limitations: ["Generic educational program line authored for the capture facade; not any manufacturer's program."],
203
+ nodes: [
204
+ { nodeId: "minus-6", opticalExposureStopsFromReference: -6, aperture: 16, shutterSeconds: 1 / 2000 },
205
+ { nodeId: "minus-2", opticalExposureStopsFromReference: -2, aperture: 16, shutterSeconds: 1 / 125 },
206
+ { nodeId: "zero", opticalExposureStopsFromReference: 0, aperture: 8, shutterSeconds: 1 / 125 },
207
+ { nodeId: "plus-2", opticalExposureStopsFromReference: 2, aperture: 4, shutterSeconds: 1 / 125 },
208
+ { nodeId: "plus-9", opticalExposureStopsFromReference: 9, aperture: 4, shutterSeconds: 128 / 125 }
209
+ ] });
210
+ }
211
+ /** PNG colour signalling: the sRGB chunk for sRGB, ITU-T H.273 code points through cICP otherwise. */
212
+ function pngColourEncoding(encoding) {
213
+ if (encoding === "srgb")
214
+ return { kind: "srgb", renderingIntent: "perceptual" };
215
+ if (encoding === "display-p3")
216
+ return { kind: "cicp", primaries: "display-p3", transfer: "srgb", fullRange: true };
217
+ return { kind: "cicp", primaries: "bt2020", transfer: "bt709", fullRange: true };
218
+ }
219
+ /**
220
+ * Develops the packed RAW to unclipped linear sRGB/D65, using the exact operator inside
221
+ * `createNativeRawDevelopmentTask`. Validation, demosaic kernels, border handling, per-site white-level
222
+ * clipping, white balance, highlight mode and the camera→XYZ→linear-sRGB matrices are all the same. It
223
+ * stops before tone mapping, gamut clipping and quantization. It reads the RAW without taking ownership.
224
+ */
225
+ async function developLinearSrgb(input, afterRow) {
226
+ const v = prepareNativeRawDevelopment(input), plan = v.plan, raw = input.raw, layout = plan.frame.colorSamplingProfile.layout;
227
+ if (layout.kind !== "periodic-mosaic")
228
+ fail("Development requires a periodic CFA.");
229
+ const native = plan.exposure.geometry.nativeRaster, g = plan.exposure.resolvedGeometry, active = g.activeCapture.nativeRect;
230
+ checkNativeRawIntegrity(raw, native.pixelWidth, layout.repeatWidthSites, layout.repeatHeightSites, 0, native.pixelHeight);
231
+ const developer = createRawPixelDeveloper({ nativeWidth: native.pixelWidth, repeatWidth: layout.repeatWidthSites, repeatHeight: layout.repeatHeightSites,
232
+ codes: raw.codes, blackLevels: raw.blackLevels, saturationFlags: raw.saturationFlags, whiteByPhase: raw.effectiveSaturationCodes }, v.phases, v.development, v.mirror);
233
+ const width = v.raster.pixelWidth, height = v.raster.pixelHeight, rgb = new Float64Array(width * height * 3), row = new Float64Array(width * 3);
234
+ const at = (x, y) => {
235
+ const q = transformOrientedRasterPointToNative({ point: { x: v.crop.x + x + 0.5, y: v.crop.y + y + 0.5 }, nativeRaster: g.activeCapture.raster, orientation: plan.exposure.geometry.orientation });
236
+ return { x: Math.floor(q.x) + active.x, y: Math.floor(q.y) + active.y };
237
+ };
238
+ for (let y = 0; y < height; y++) {
239
+ const first = at(0, y), second = width > 1 ? at(1, y) : first;
240
+ developer.developRun(first.x, first.y, second.x - first.x, second.y - first.y, width, row);
241
+ rgb.set(row, y * width * 3);
242
+ await afterRow(y + 1, height);
243
+ }
244
+ const c = developer.counts;
245
+ return { rgb, highlight: { mode: v.highlightMode, saturatedPixelCount: c.saturatedPixelCount, whiteBalanceClippedSampleCount: c.whiteBalanceClippedSampleCount,
246
+ reconstructedPixelCount: c.reconstructedPixelCount } };
247
+ }
248
+ /** Linear sRGB/D65 → CIE XYZ (inverse of the development's XYZ → sRGB matrix) → linear target RGB/D65. */
249
+ function linearSrgbToTarget(target) {
250
+ return multiplyMatrices(resolveRgbColorSpace(target).value.xyzToRgb, resolveCaptureColorModel().cameraRgbToXyz);
251
+ }
252
+ /** Knee of the `perceptual` gamut mapping: chroma below 90 % of the gamut-boundary chroma (at constant L*, h) is untouched. */
253
+ const PERCEPTUAL_GAMUT_KNEE_FRACTION = 0.9;
254
+ /**
255
+ * SDR encoding from linear sRGB/D65:
256
+ * 1. Convert to the target primaries with a matrix.
257
+ * 2. Either clip each component to [0, 1], or, with `mapping`, compress chroma while keeping CIELAB hue
258
+ * and lightness (`prepareRgbGamutMapping`).
259
+ * 3. Apply the target transfer function and round to the nearest code (ties round up).
260
+ */
261
+ function encodeSdrFromLinear(rgb, encoding, bitDepth, mapping) {
262
+ const m = linearSrgbToTarget(encoding), toXyz = resolveCaptureColorModel().cameraRgbToXyz, maximum = 2 ** bitDepth - 1;
263
+ const transfer = encoding === "rec2020" ? evaluateBt709Oetf : evaluateSrgbEncoding;
264
+ const samples = bitDepth === 8 ? new Uint8Array(rgb.length) : new Uint16Array(rgb.length);
265
+ let low = 0, high = 0, adjusted = 0, lightnessClipped = 0;
266
+ for (let i = 0; i < rgb.length; i += 3) {
267
+ const r = rgb[i], g = rgb[i + 1], b = rgb[i + 2];
268
+ if (mapping) {
269
+ const xyz = [0, 1, 2].map((k) => toXyz[k][0] * r + toXyz[k][1] * g + toXyz[k][2] * b);
270
+ const mapped = mapping.map(xyz);
271
+ if (mapped.adjusted)
272
+ adjusted++;
273
+ if (mapped.lightnessClipped)
274
+ lightnessClipped++;
275
+ for (let k = 0; k < 3; k++)
276
+ samples[i + k] = Math.floor(transfer(mapped.linearRgb[k]) * maximum + 0.5);
277
+ continue;
278
+ }
279
+ for (let k = 0; k < 3; k++) {
280
+ const row = m[k];
281
+ let value = row[0] * r + row[1] * g + row[2] * b;
282
+ if (value < 0) {
283
+ value = 0;
284
+ low++;
285
+ }
286
+ else if (value > 1) {
287
+ value = 1;
288
+ high++;
289
+ }
290
+ samples[i + k] = Math.floor(transfer(value) * maximum + 0.5);
291
+ }
292
+ }
293
+ return { samples, adjustedPixelCount: adjusted, lightnessClippedPixelCount: lightnessClipped, diagnostics: { renderingNegativeSampleCount: low,
294
+ renderingAboveReferenceSampleCount: high, toneChangedSampleCount: 0, gamutClippedLowSampleCount: low, gamutClippedHighSampleCount: high, captureSaturation: "consumed-in-raw-development" } };
295
+ }
296
+ /**
297
+ * Simulates one still capture of a planar scene and returns the developed image, the packed RAW, the full
298
+ * underlying request/plan, provenance and every assumption made. See docs/QUICK_START.md.
299
+ */
300
+ export async function simulateCapture(options) {
301
+ const started = Date.now();
302
+ requireInputObject(options, "simulateCapture options");
303
+ const top = allowKeys(options, "simulateCapture options", ["camera", "lens", "exposure", "scene", "subjectDistanceM", "illuminant",
304
+ "illuminanceLux", "output", "sensor", "motion", "signal", "onProgress"]);
305
+ const signal = top.signal, onProgress = top.onProgress;
306
+ if (signal !== undefined && (typeof signal !== "object" || signal === null || typeof signal.aborted !== "boolean"))
307
+ fail("signal must be an AbortSignal.");
308
+ if (onProgress !== undefined && typeof onProgress !== "function")
309
+ fail("onProgress must be a function.");
310
+ const assumptions = [];
311
+ const assume = (id, kind, value, explanation) => { assumptions.push({ id, kind, value, explanation }); };
312
+ function pick(record, key, path, fallback, explanation) {
313
+ if (record[key] !== undefined)
314
+ return record[key];
315
+ assume(path, "default", fallback, explanation);
316
+ return fallback;
317
+ }
318
+ const checkAbort = () => { if (signal?.aborted)
319
+ fail("simulateCapture aborted."); };
320
+ checkAbort();
321
+ // Camera
322
+ const cameraOption = pick(top, "camera", "camera", DEFAULTS.camera, "Generic Photivra tier preset 2.0.0 (synthetic educational values, not a real camera).");
323
+ let preset;
324
+ if (typeof cameraOption === "string")
325
+ preset = GENERIC_TIER_ASSETS.find((p) => p.tier === cameraOption) ?? fail("Unknown camera tier.");
326
+ else
327
+ preset = parseCallerCameraPreset(cameraOption);
328
+ const body = preset.body, lens = preset.lens;
329
+ // Lens
330
+ const lensOptions = allowKeys(top.lens, "lens", ["focalLengthMm", "fNumber", "focusDistanceM", "pupilSampling", "projection", "distortion", "vignetting", "psf"]);
331
+ const focalDefault = lens.exposure.focalLengthMmRange.value.minimum;
332
+ const focalLengthMm = positive(pick(lensOptions, "focalLengthMm", "lens.focalLengthMm", focalDefault, "The preset lens's focal length (the generic primes are 50 mm)."), "lens.focalLengthMm");
333
+ const capabilities = resolveGenericEquipmentExposureCapabilities({ bodyProfile: body.exposure, lensProfile: lens.exposure, selectedFocalLengthMm: focalLengthMm });
334
+ const subjectDistanceM = positive(pick(top, "subjectDistanceM", "subjectDistanceM", DEFAULTS.subjectDistanceM, "Scene plane 5 m in front of the camera (the generic tiers' reference focus distance)."), "subjectDistanceM");
335
+ if (subjectDistanceM * 1000 <= focalLengthMm * 2)
336
+ fail("subjectDistanceM must exceed twice the focal length.");
337
+ const focusDistanceM = positive(pick(lensOptions, "focusDistanceM", "lens.focusDistanceM", subjectDistanceM, "Focused on the scene plane."), "lens.focusDistanceM");
338
+ if (focusDistanceM * 1000 <= focalLengthMm)
339
+ fail("lens.focusDistanceM must exceed the focal length.");
340
+ let pupil;
341
+ if (lensOptions.pupilSampling === undefined)
342
+ assume("lens.pupilSampling", "default", null, "No pupil sampling: each scene query follows the central (chief) ray, so defocus and depth of field are not rendered. Enable lens.pupilSampling for an ideal circular pupil.");
343
+ else {
344
+ const p = allowKeys(lensOptions.pupilSampling, "lens.pupilSampling", ["radialSamples", "angularSamples"]);
345
+ // The environment pupil contract admits 4..64 azimuthal midpoints and at most 256 rays.
346
+ pupil = { radialSamples: integerIn(p.radialSamples, "lens.pupilSampling.radialSamples", 1, 16), angularSamples: integerIn(p.angularSamples, "lens.pupilSampling.angularSamples", 4, 64) };
347
+ if (pupil.radialSamples * pupil.angularSamples > 256)
348
+ fail("lens.pupilSampling needs at most 256 rays (radialSamples × angularSamples).");
349
+ }
350
+ // 2.0 optics hooks: only what the environment site contract accepts.
351
+ const projection = lensOptions.projection === undefined ? undefined : parseLensProjectionModel(lensOptions.projection);
352
+ let distortion;
353
+ if (lensOptions.distortion !== undefined) {
354
+ const d = allowKeys(lensOptions.distortion, "lens.distortion", ["kind", "profile"]);
355
+ if (d.kind !== "radial" && d.kind !== "brown-conrady")
356
+ fail("lens.distortion.kind must be radial or brown-conrady.");
357
+ distortion = d;
358
+ }
359
+ const lensFieldMapping = projection === undefined && distortion === undefined ? undefined :
360
+ { projectionModel: projection ?? "rectilinear", distortion: distortion ?? { kind: "none" }, radiometricAreaCorrection: "inverse-distortion-jacobian" };
361
+ if (lensFieldMapping) {
362
+ if (pupil)
363
+ fail("lens.projection and lens.distortion cannot be combined with lens.pupilSampling: ideal pupil rays use the rectilinear, distortion-free projection.");
364
+ try {
365
+ prepareEnvironmentLensFieldMapping(lensFieldMapping);
366
+ }
367
+ catch (error) {
368
+ fail("Invalid lens projection or distortion: " + error.message);
369
+ }
370
+ }
371
+ let vignetting;
372
+ if (lensOptions.vignetting !== undefined) {
373
+ requireInputObject(lensOptions.vignetting, "lens.vignetting");
374
+ vignetting = lensOptions.vignetting;
375
+ }
376
+ let lensPsf;
377
+ if (lensOptions.psf !== undefined) {
378
+ const p = allowKeys(lensOptions.psf, "lens.psf", ["kind", "configuration"]);
379
+ if (p.kind !== "sampled-local")
380
+ fail("lens.psf.kind must be sampled-local.");
381
+ requireInputObject(p.configuration, "lens.psf.configuration");
382
+ if (pupil)
383
+ fail("lens.psf cannot be combined with lens.pupilSampling: geometric defocus would be counted twice.");
384
+ lensPsf = p;
385
+ }
386
+ // Motion (2.0): constant camera rotation and the shutter type.
387
+ const motionOptions = allowKeys(top.motion, "motion", ["angularVelocityRadPerSec", "shutter", "rollingShutterSkewSeconds"]);
388
+ const angular = { pitch: 0, yaw: 0, roll: 0 };
389
+ if (motionOptions.angularVelocityRadPerSec !== undefined) {
390
+ const a = allowKeys(motionOptions.angularVelocityRadPerSec, "motion.angularVelocityRadPerSec", ["pitch", "yaw", "roll"]);
391
+ for (const axis of ["pitch", "yaw", "roll"])
392
+ if (a[axis] !== undefined) {
393
+ const value = a[axis];
394
+ if (typeof value !== "number" || !Number.isFinite(value) || Math.abs(value) > 100)
395
+ fail("motion.angularVelocityRadPerSec." + axis + " must be a finite number within ±100 rad/s.");
396
+ angular[axis] = value;
397
+ }
398
+ }
399
+ const rotating = angular.pitch !== 0 || angular.yaw !== 0 || angular.roll !== 0;
400
+ const shutterKind = motionOptions.shutter === undefined ? "global" : motionOptions.shutter;
401
+ if (shutterKind !== "global" && shutterKind !== "rolling")
402
+ fail("motion.shutter must be global or rolling.");
403
+ if (motionOptions.rollingShutterSkewSeconds !== undefined && shutterKind !== "rolling")
404
+ fail("motion.rollingShutterSkewSeconds needs motion.shutter \"rolling\".");
405
+ const rollingSkewOption = motionOptions.rollingShutterSkewSeconds === undefined ? undefined : positive(motionOptions.rollingShutterSkewSeconds, "motion.rollingShutterSkewSeconds");
406
+ // Output and sensor
407
+ const outputOptions = allowKeys(top.output, "output", ["width", "height", "format", "toneCurve", "toneCurveParameters", "highlightMode", "whiteBalance", "jpegQuantizationStep",
408
+ "colorEncoding", "bitDepth", "gamutMapping", "demosaic", "borderHandling", "dng"]);
409
+ const width = integerIn(pick(outputOptions, "width", "output.width", DEFAULTS.width, "64 × 64 output pixels."), "output.width", 1, 4094);
410
+ const height = integerIn(pick(outputOptions, "height", "output.height", DEFAULTS.height, "64 × 64 output pixels."), "output.height", 1, 4094);
411
+ const format = pick(outputOptions, "format", "output.format", DEFAULTS.format, "8-bit sRGB samples.");
412
+ if (!["srgb-8", "srgb-16", "jpeg", "png", "dng"].includes(format))
413
+ fail("output.format must be srgb-8, srgb-16, jpeg, png or dng.");
414
+ let dngCapturedAtUtc;
415
+ if (format === "dng") {
416
+ const d = allowKeys(outputOptions.dng, "output.dng", ["capturedAtUtc"]);
417
+ dngCapturedAtUtc = d.capturedAtUtc;
418
+ if (dngCapturedAtUtc === undefined)
419
+ assume("output.dng.capturedAtUtc", "default", null, "No capture time: the DNG carries no DateTimeOriginal (the engine never invents a wall clock).");
420
+ }
421
+ else if (outputOptions.dng !== undefined)
422
+ fail("output.dng applies only to output.format \"dng\".");
423
+ let colorEncoding, pngBitDepth;
424
+ if (format === "png") {
425
+ colorEncoding = pick(outputOptions, "colorEncoding", "output.colorEncoding", "srgb", "sRGB (W3C CSS Color 4 srgb) samples signalled with the PNG sRGB chunk (perceptual intent) and its prescribed gAMA/cHRM values.");
426
+ if (!["srgb", "display-p3", "rec2020"].includes(colorEncoding))
427
+ fail("output.colorEncoding must be srgb, display-p3 or rec2020.");
428
+ pngBitDepth = pick(outputOptions, "bitDepth", "output.bitDepth", 8, "8 bits per sample.");
429
+ if (pngBitDepth !== 8 && pngBitDepth !== 16)
430
+ fail("output.bitDepth must be 8 or 16.");
431
+ }
432
+ else if (outputOptions.colorEncoding !== undefined || outputOptions.bitDepth !== undefined)
433
+ fail("output.colorEncoding and output.bitDepth apply only to output.format \"png\".");
434
+ let perceptual = false;
435
+ if (format === "png") {
436
+ const gamutMapping = pick(outputOptions, "gamutMapping", "output.gamutMapping", "clip", "Out-of-gamut components are clipped to [0, 1] in the target space.");
437
+ if (gamutMapping !== "clip" && gamutMapping !== "perceptual")
438
+ fail("output.gamutMapping must be clip or perceptual.");
439
+ perceptual = gamutMapping === "perceptual";
440
+ }
441
+ else if (outputOptions.gamutMapping !== undefined)
442
+ fail("output.gamutMapping applies only to output.format \"png\".");
443
+ const wideGamut = colorEncoding !== undefined && colorEncoding !== "srgb";
444
+ // The linear development path serves wide-gamut encodings and perceptual gamut mapping.
445
+ const linearPath = wideGamut || perceptual;
446
+ const toneCurve = pick(outputOptions, "toneCurve", "output.toneCurve", DEFAULTS.toneCurve, "Identity tone curve: scene-linear values are clipped at display white, then sRGB-encoded.");
447
+ if (linearPath && toneCurve !== "identity")
448
+ fail("output.toneCurve other than identity is available only for sRGB output; wide-gamut encodings are converted from the linear developed colour.");
449
+ const highlightMode = pick(outputOptions, "highlightMode", "output.highlightMode", DEFAULTS.highlightMode, "Saturated RAW highlights are clipped to neutral.");
450
+ const whiteBalanceOption = pick(outputOptions, "whiteBalance", "output.whiteBalance", DEFAULTS.whiteBalance, "White balance measured from a perfect white diffuser under the scene illuminant (a custom/grey-card white balance).");
451
+ const jpegStep = format === "jpeg" ? integerIn(pick(outputOptions, "jpegQuantizationStep", "output.jpegQuantizationStep", DEFAULTS.jpegQuantizationStep, "Constant JPEG quantization step 2 (not a manufacturer quality scale)."), "output.jpegQuantizationStep", 1, 255) : undefined;
452
+ if (format !== "jpeg" && outputOptions.jpegQuantizationStep !== undefined)
453
+ fail("output.jpegQuantizationStep applies only to output.format \"jpeg\".");
454
+ const sensorOptions = allowKeys(top.sensor, "sensor", ["pixelPitchMicrometers", "spectralStepNanometers", "temporalSampleCount", "noiseSeed"]);
455
+ const pitch = positive(pick(sensorOptions, "pixelPitchMicrometers", "sensor.pixelPitchMicrometers", DEFAULTS.pixelPitchMicrometers, "6 µm pixels (about a 24 MP full-frame sensor). The simulated sensor is width × pitch wide, so small images are a central crop of a large sensor, not a downscaled full frame."), "sensor.pixelPitchMicrometers");
456
+ if (pitch < 0.5 || pitch > 100)
457
+ fail("sensor.pixelPitchMicrometers must be 0.5-100.");
458
+ const spectralStep = positive(pick(sensorOptions, "spectralStepNanometers", "sensor.spectralStepNanometers", DEFAULTS.spectralStepNanometers, "Spectral midpoint quadrature over 380-780 nm with subintervals of at most 20 nm."), "sensor.spectralStepNanometers");
459
+ if (spectralStep < 1 || spectralStep > 100)
460
+ fail("sensor.spectralStepNanometers must be 1-100.");
461
+ const temporalSampleCount = integerIn(rotating ? pick(sensorOptions, "temporalSampleCount", "sensor.temporalSampleCount", ROTATING_TEMPORAL_SAMPLE_COUNT, "Eight temporal midpoints across each site's exposure window sample the camera rotation (motion blur); raise it for long exposures or fast rotation.") :
462
+ pick(sensorOptions, "temporalSampleCount", "sensor.temporalSampleCount", DEFAULTS.temporalSampleCount, "One temporal sample: the static scene and still camera make more samples unnecessary."), "sensor.temporalSampleCount", 1, 256);
463
+ const noiseSeed = integerIn(pick(sensorOptions, "noiseSeed", "sensor.noiseSeed", DEFAULTS.noiseSeed, "Fixed noise seed 1 (deterministic output)."), "sensor.noiseSeed", 0, 0xffffffff);
464
+ const colorSamplingProfile = { schemaVersion: "0.1.0", profileId: IDS.cfa, evidence: FACADE_EVIDENCE, coordinateSystem: "native-sensor-color-sampling-site-index",
465
+ layout: { kind: "periodic-mosaic", repeatWidthSites: 2, repeatHeightSites: 2, siteChannelIds: ["red", "green", "green", "blue"], anchor: "native-sensor-top-left-site" } };
466
+ // Demosaic (2.0): built-in Bayer kernels and border handling. Omitted, the 1.0 bilinear kernels and a
467
+ // one-site rendered border are used unchanged.
468
+ const demosaicOption = outputOptions.demosaic, borderOption = outputOptions.borderHandling;
469
+ if (demosaicOption !== undefined && demosaicOption !== "bilinear")
470
+ fail("output.demosaic must be bilinear.");
471
+ if (borderOption !== undefined && borderOption !== "margin" && borderOption !== "mirror")
472
+ fail("output.borderHandling must be margin or mirror.");
473
+ const builtInDemosaic = demosaicOption === undefined ? undefined : createBuiltInBayerDemosaicProfiles({ kernel: "bilinear",
474
+ captureModeId: IDS.mode, colorSamplingProfile, channelRoles: { red: "red", green: "green", blue: "blue" } }).value;
475
+ const mirrorBorder = borderOption === "mirror";
476
+ const margin = mirrorBorder ? 0 : builtInDemosaic ? builtInDemosaic.kernelReachSites : MARGIN;
477
+ if (mirrorBorder && (width < 2 || height < 2))
478
+ fail("output.borderHandling \"mirror\" needs an output of at least 2 × 2 pixels.");
479
+ // Geometry: the demosaic border (if any) on each side; output crop is the active area.
480
+ const nativeWidth = width + 2 * margin, nativeHeight = height + 2 * margin;
481
+ const imagingArea = { widthMm: nativeWidth * pitch / 1000, heightMm: nativeHeight * pitch / 1000 };
482
+ const geometry = { imagingArea, nativeRaster: { pixelWidth: nativeWidth, pixelHeight: nativeHeight }, orientation: "landscape",
483
+ activeCaptureRect: { x: margin, y: margin, width, height }, outputRaster: { pixelWidth: width, pixelHeight: height } };
484
+ const resolvedGeometry = resolveCaptureGeometry(geometry).value;
485
+ const imageDistanceMm = focalLengthMm * focusDistanceM * 1000 / (focusDistanceM * 1000 - focalLengthMm);
486
+ const metresPerFrameUnit = (width * pitch / 2000) * subjectDistanceM / imageDistanceMm;
487
+ const aspect = height / width;
488
+ assume("scene.framing", "derived", { framedWidthM: 2 * metresPerFrameUnit, framedHeightM: 2 * metresPerFrameUnit * aspect }, "The scene plane is perpendicular to the optical axis and the chart is sized to fill the active frame exactly.");
489
+ if (demosaicOption === undefined && borderOption === undefined) {
490
+ assume("output.demosaic", "model", "bilinear-rggb", "Bilinear RGGB demosaic through explicit linear kernels; the native raster has a one-site border on each side so every output pixel has full support.");
491
+ }
492
+ else {
493
+ if (demosaicOption === undefined)
494
+ assume("output.demosaic", "default", "bilinear-rggb", "The 1.0 facade's bilinear RGGB kernels (explicit linear neighbourhood weights).");
495
+ else
496
+ assume("output.demosaic", "model", { kernel: builtInDemosaic.kernel, modelVersion: BUILT_IN_DEMOSAIC_VERSION, kernelReachSites: builtInDemosaic.kernelReachSites }, "Built-in bilinear Bayer kernels (createBuiltInBayerDemosaicProfiles): nearest same-channel averages.");
497
+ if (borderOption === undefined)
498
+ assume("output.borderHandling", "default", "margin", "The native raster adds the kernel reach (" + margin + " site(s)) on each side, so every output pixel is reconstructed from rendered sites.");
499
+ else
500
+ assume("output.borderHandling", "model", borderOption, mirrorBorder ?
501
+ "No rendered border: the native raster equals the output and reconstruction reads reflect-101 mirror images of the missing sites (CFA consistency validated), so edge pixels use mirrored rather than rendered neighbours." :
502
+ "The native raster adds the kernel reach (" + margin + " site(s)) on each side, so every output pixel is reconstructed from rendered sites.");
503
+ }
504
+ // Radiance queries per native site: spectral subintervals × temporal midpoints × pupil rays × PSF taps.
505
+ const psfTaps = lensPsf ? Math.max(1, ...(lensPsf.configuration.psf.profile.nodes ?? [])
506
+ .map((node) => Number(node.kernel?.widthSamples) * Number(node.kernel?.heightSamples)).filter((taps) => Number.isSafeInteger(taps) && taps > 0)) : 1;
507
+ const queriesPerSite = temporalSampleCount * (Math.ceil((FACADE_SPECTRAL_RANGE_NANOMETERS.maximum - FACADE_SPECTRAL_RANGE_NANOMETERS.minimum) / spectralStep) + 1) *
508
+ (pupil ? pupil.radialSamples * pupil.angularSamples : 1) * psfTaps;
509
+ // Scene and illuminant
510
+ // The scene is the one required input: the facade never guesses what is being photographed.
511
+ if (top.scene === undefined)
512
+ fail("simulateCapture needs a scene, for example { preset: \"uniform\" } (an 18 % grey card) or { preset: \"slanted-edge\" }.");
513
+ const sceneOption = top.scene;
514
+ requireInputObject(sceneOption, "scene");
515
+ const illuminantOption = pick(top, "illuminant", "illuminant", { kind: "planckian", temperatureKelvin: DEFAULTS.illuminantKelvin }, "Planckian (blackbody) 6500 K as a stand-in for daylight. CIE D65 is not bundled; pass its S0/S1/S2 basis as a daylight illuminant to use it.");
516
+ const illuminant = resolveCaptureIlluminant(illuminantOption);
517
+ const illuminanceLux = positive(pick(top, "illuminanceLux", "illuminanceLux", SIMULATE_CAPTURE_DEFAULT_ILLUMINANCE_LUX, "π·10·N²/(0.65·t·S·0.18) lux at f/8, 1/125 s, ISO 100 (CIPA DC-004 standard output sensitivity H = 10/S and camera equation H = 0.65·L·t/N²): an 18 % grey card then gets the reference exposure for the default settings. It does not follow the caller's exposure settings."), "illuminanceLux");
518
+ const irradianceScale = calculateIlluminantIrradianceScale(illuminant.shape, illuminanceLux);
519
+ let chart, callerScene;
520
+ const sceneRecord = sceneOption;
521
+ if ("kind" in sceneRecord) {
522
+ callerScene = requireCallerScene(sceneOption);
523
+ }
524
+ else {
525
+ chart = resolveChartScene({ scene: sceneOption, aspect });
526
+ for (const key of chart.defaultedParameters)
527
+ assume("scene." + key, "default", chart.parameters[key], "Chart default for " + chart.preset + ".");
528
+ }
529
+ const scenePreset = chart ? chart.preset : callerScene.kind;
530
+ // Caller spectra enter the digest as data (see spectrumDigestForm). Without caller spectra the description is unchanged,
531
+ // so default captures keep their digests.
532
+ const illuminantRecord = typeof illuminantOption === "object" ? illuminantOption : undefined;
533
+ const illuminantSpectrum = illuminantRecord?.kind === "spectrum" ? { spectrum: spectrumDigestForm(illuminant.spectrum, illuminant.shape) } :
534
+ illuminantRecord?.kind === "daylight" ? { basisDatasetId: illuminantRecord.basis?.datasetId, spectrum: spectrumDigestForm(illuminant.spectrum, illuminant.shape) } : undefined;
535
+ const sceneSpectra = chart ? chart.materials.flatMap((m, index) => {
536
+ if (m.kind !== "spectrum")
537
+ return [];
538
+ const raw = chart.preset === "uniform" ? sceneRecord.reflectance : sceneRecord.patches[index].reflectance;
539
+ return [{ material: index, spectrum: spectrumDigestForm(raw, m.spectrum) }];
540
+ }) : [];
541
+ const sceneDescription = { facadeVersion: SIMULATE_CAPTURE_FACADE_VERSION, scene: chart ? { preset: chart.preset, parameters: chart.parameters } :
542
+ { kind: callerScene.kind, sceneId: callerScene.sceneId }, illuminant: illuminant.id, illuminanceLux, subjectDistanceM, framedWidthM: 2 * metresPerFrameUnit, aspect,
543
+ ...(illuminantSpectrum === undefined ? {} : { illuminantSpectrum }), ...(sceneSpectra.length === 0 ? {} : { sceneSpectra }) };
544
+ if (callerScene)
545
+ assume("scene.digest", "model", "caller-function-not-hashed", "The source digest covers the caller's sceneId and the framing, not the body of the caller's evaluate function.");
546
+ const sourceSha256 = await sha256Hex(JSON.stringify(sceneDescription, (_key, value) => (typeof value === "object" && value !== null && "evaluate" in value ? "caller-spectrum" : value)));
547
+ checkAbort();
548
+ const provider = buildPlanarSceneRadianceProvider({ scene: chart ?? callerScene, sceneId: IDS.scene, providerProfileId: IDS.provider,
549
+ illuminant, irradianceScale, geometry: { distanceM: subjectDistanceM, metresPerFrameUnit }, evidence: FACADE_EVIDENCE,
550
+ limitations: ["Planar Lambertian scene under uniform illumination; no shadows, inter-reflection, gloss or fluorescence."] });
551
+ assume("model.scene-radiometry", "model", "lambertian-plane", "Radiance = reflectance × irradiance / π on one plane; illuminance from K_m = 683 lm/W and the engine's analytic CIE 1931 ȳ fit.");
552
+ // Metering (CIPA DC-004 standard output sensitivity and camera equation) and exposure-mode resolution
553
+ const exposureOptions = allowKeys(top.exposure, "exposure", ["mode", "iso", "shutterSeconds", "compensationStops"]);
554
+ const mode = pick(exposureOptions, "mode", "exposure.mode", "manual", "Manual exposure.");
555
+ if (!["manual", "aperture-priority", "shutter-priority", "program"].includes(mode))
556
+ fail("Unknown exposure mode.");
557
+ const iso = positive(pick(exposureOptions, "iso", "exposure.iso", DEFAULTS.iso, "ISO 100, the tiers' base ISO."), "exposure.iso");
558
+ const compensation = exposureOptions.compensationStops === undefined ? DEFAULTS.compensationStops : exposureOptions.compensationStops;
559
+ if (typeof compensation !== "number" || !Number.isFinite(compensation) || Math.abs(compensation) > 10)
560
+ fail("exposure.compensationStops must be within ±10.");
561
+ if (exposureOptions.compensationStops === undefined)
562
+ assume("exposure.compensationStops", "default", 0, "No exposure compensation.");
563
+ const reference = { aperture: DEFAULTS.fNumber, shutterSeconds: DEFAULTS.shutterSeconds, iso: DEFAULTS.iso };
564
+ const referenceLuminance = sosReferenceLuminance(reference.aperture, reference.shutterSeconds, reference.iso);
565
+ assume("exposure.referenceExposure", "model", { ...reference, meteredLuminanceCdPerSquareMeter: referenceLuminance }, "Exposure anchor: f/8, 1/125 s, ISO 100 gives the CIPA DC-004 standard output sensitivity reference exposure H = 10/S = 0.1 lx·s for an average scene luminance of 10·N²/(0.65·t·S) ≈ 1230.77 cd/m² (camera equation H = 0.65·L·t/N²).");
566
+ const meterProfile = body.metering[0].profile, zones = 16, zoneSamples = [];
567
+ let luminanceSum = 0;
568
+ for (let j = 0; j < zones; j++)
569
+ for (let i = 0; i < zones; i++) {
570
+ const u = -1 + (i + 0.5) * 2 / zones, v = aspect - (j + 0.5) * 2 * aspect / zones;
571
+ const luminance = luminanceOf((nm) => provider.radianceAt(u, v, nm));
572
+ luminanceSum += luminance;
573
+ zoneSamples.push({ sampleId: "zone-" + i + "-" + j, positionOrientedCaptureUv: { u: (i + 0.5) / zones, v: (j + 0.5) / zones },
574
+ relativeLinearSignal: meterProfile.target.targetRelativeSignal * luminance / referenceLuminance, areaWeight: 1 });
575
+ }
576
+ const meteredLuminance = luminanceSum / (zones * zones);
577
+ assume("exposure.metering", "model", { policy: meterProfile.policy.kind, zones: zones * zones }, "The body's " + meterProfile.policy.kind + " meter reads scene luminance on a 16 × 16 zone grid, before the lens (no lens transmission, vignetting or flare).");
578
+ const meterResult = meterRelativeExposure({ profile: meterProfile, sampleSet: { measurementId: "facade-meter", sceneStateId: IDS.sceneState,
579
+ inputDomain: "relative-pre-exposure-linear-signal", captureRegion: "oriented-active-capture", captureGeometry: resolvedGeometry,
580
+ processingState: { exposureSettingsApplied: false, whiteBalanceApplied: false, toneMappingApplied: false, displayGammaApplied: false, sharpeningApplied: false },
581
+ samples: zoneSamples } });
582
+ let target = createExposureMeterTargetFromMeteringResult({ targetId: "facade-target", meterResult: meterResult.value });
583
+ if (compensation !== 0)
584
+ target = setExposureCompensationOnMeterTarget({ targetId: "facade-target-ec", baseTarget: target, exposureCompensationStops: compensation });
585
+ let resolution;
586
+ let fNumber, shutterSeconds;
587
+ if (mode === "manual" || mode === "aperture-priority") {
588
+ fNumber = positive(pick(lensOptions, "fNumber", "lens.fNumber", DEFAULTS.fNumber, "f/8."), "lens.fNumber");
589
+ }
590
+ else {
591
+ if (lensOptions.fNumber !== undefined)
592
+ fail("lens.fNumber is resolved automatically in " + mode + " mode; omit it.");
593
+ fNumber = NaN;
594
+ }
595
+ if (mode === "manual" || mode === "shutter-priority") {
596
+ shutterSeconds = positive(pick(exposureOptions, "shutterSeconds", "exposure.shutterSeconds", DEFAULTS.shutterSeconds, "1/125 s."), "exposure.shutterSeconds");
597
+ }
598
+ else {
599
+ if (exposureOptions.shutterSeconds !== undefined)
600
+ fail("exposure.shutterSeconds is resolved automatically in " + mode + " mode; omit it.");
601
+ shutterSeconds = NaN;
602
+ }
603
+ if (mode === "manual") {
604
+ resolution = resolveManualExposureMode({ target, capabilities, referenceExposure: reference, manualAperture: fNumber, manualShutterSeconds: shutterSeconds,
605
+ isoControl: { kind: "manual", iso } });
606
+ }
607
+ else if (mode === "aperture-priority") {
608
+ resolution = resolveAperturePriorityExposureMode({ target, capabilities, referenceExposure: reference, manualAperture: fNumber, manualIso: iso,
609
+ shutterQuantizationPolicy: "nearest-log2-shorter-on-tie" });
610
+ }
611
+ else if (mode === "shutter-priority") {
612
+ resolution = resolveShutterPriorityExposureMode({ target, capabilities, referenceExposure: reference, manualShutterSeconds: shutterSeconds,
613
+ isoControl: { kind: "manual", iso, apertureQuantizationPolicy: "nearest-log2-narrower-on-tie" } });
614
+ }
615
+ else {
616
+ assume("exposure.programLine", "model", "facade-generic-program-line", "Engine-authored generic program line (f/16 1/2000 s … f/8 1/125 s … f/4 1.024 s); not a manufacturer program.");
617
+ resolution = resolveProgramAutoExposureMode({ target, capabilities, referenceExposure: reference, programLine: defaultProgramLine(), isoControl: { kind: "manual", iso } });
618
+ }
619
+ if (resolution.status !== "resolved" || !resolution.resolvedSettings)
620
+ fail("Exposure could not be resolved (" + resolution.status + "); the scene may have no signal.");
621
+ fNumber = resolution.resolvedSettings.aperture ?? fNumber;
622
+ shutterSeconds = resolution.resolvedSettings.shutterSeconds ?? shutterSeconds;
623
+ const resolvedIso = resolution.resolvedSettings.iso ?? iso;
624
+ // Sensor readout for the ISO setting
625
+ const regimeByChannel = {};
626
+ for (const channel of CHANNELS) {
627
+ const asset = body.readout.find((a) => a.profile.channelId === channel) ?? fail("Camera readout lacks channel " + channel + ".");
628
+ const chain = resolveGenericIsoSignalChain({ profile: body.signalChain, isoCapability: body.iso, requestedIsoSetting: { kind: "standard", exposureIndex: resolvedIso },
629
+ captureModeId: IDS.mode, readoutProfile: asset.profile });
630
+ regimeByChannel[channel] = { readoutProfile: asset.profile, regimeId: chain.readout.regime.regimeId };
631
+ }
632
+ const baseRegime = body.readout[0].profile.regimes[0];
633
+ const capacityElectrons = Math.max(...body.readout[0].profile.regimes.map((r) => r.preAdcSaturationElectronEquivalent.value), baseRegime.preAdcSaturationElectronEquivalent.value);
634
+ assume("sensor.capacityElectrons", "derived", capacityElectrons, "Physical charge capacity set to the body's largest declared pre-ADC saturation (its base-ISO saturation signal), so storage never clips before the declared readout white.");
635
+ assume("sensor.darkCurrent", "model", { electronsPerSecond: DARK_CURRENT_ELECTRONS_PER_SECOND, temperatureC: OPERATING_TEMPERATURE_C }, "Synthetic uniform dark current of 1 e⁻/s per site at 20 °C (the tier presets declare none); dark shot noise follows from it.");
636
+ assume("sensor.spectralResponse", "model", { kind: "colorimetric-cmf-eqe", peakScale: EQE_SCALE }, "Idealized colorimetric sensor: channel EQE = a·{x̄, ȳ, z̄}(λ)·555/λ (the engine's analytic CIE 1931 fit), so RAW channels are proportional to CIE XYZ (Luther–Ives) and the camera matrix is diagonal. Not a real camera's response; CMF air wavelengths are used as vacuum wavelengths (difference about 0.03 %).");
637
+ assume("sensor.fillFactor", "model", 1, "Square sensitive aperture equal to the pixel pitch; no microlens or anti-aliasing filter.");
638
+ // Shutter (2.0): the electronic rolling-shutter schedule derived from the body's rolling readout declaration.
639
+ let rollingSchedule;
640
+ if (shutterKind === "rolling") {
641
+ const declared = body.rollingReadoutTiming?.profile;
642
+ if (!declared || declared.readoutMode !== "rolling")
643
+ fail("motion.shutter \"rolling\" needs a camera body with a rolling readout timing declaration.");
644
+ const readout = rollingSkewOption === undefined ? declared : { ...declared, spatialSamplingSkewSeconds: { value: rollingSkewOption, unit: "s", evidence: FACADE_EVIDENCE } };
645
+ rollingSchedule = resolveElectronicRollingShutterExposureSchedule({ readout, linkageEvidence: FACADE_EVIDENCE,
646
+ nominalExposureDurationSeconds: { value: shutterSeconds, unit: "s", evidence: FACADE_EVIDENCE } });
647
+ assume("sensor.shutter", "model", { kind: "electronic-rolling", modelId: rollingSchedule.modelId, modelVersion: rollingSchedule.modelVersion,
648
+ lineScanSkewSeconds: rollingSchedule.lineScanSkewSeconds, scanDirection: readout.scanDirectionNative.value }, "Electronic rolling shutter (line reset/read linkage): a site at normalized scan position s integrates over [s·τ, s·τ + T]. " +
649
+ (rollingSkewOption === undefined ? "τ is the body's declared full-frame data-readout skew, applied unchanged across the simulated frame (it is not scaled to the cropped sensor), so a small frame shows the skew of a full-sensor readout." :
650
+ "τ is the caller-declared skew across the simulated frame."));
651
+ }
652
+ else
653
+ assume("sensor.shutter", "model", "global-electronic", "Global electronic shutter: every site integrates the same exposure window.");
654
+ if (rotating)
655
+ assume("motion.angularVelocityRadPerSec", "model", { ...angular }, "Constant camera angular velocity applied to every scene ray at each temporal midpoint of each site's exposure window (engine rotation convention, docs/SENSOR_ENVIRONMENT_QUERY.md); motion blur is the midpoint quadrature over sensor.temporalSampleCount samples.");
656
+ if (lensPsf)
657
+ assume("lens.psf", "model", { kind: "sampled-local" }, "Caller-supplied sampled lens PSF redistributes irradiance through the environment PSF quadrature; its evidence and limits are the caller's.");
658
+ else
659
+ assume("lens.psf", "model", "not-applied", "No lens PSF or diffraction blur is applied (point optics); the generic tiers' optical data are declared only at f/4.");
660
+ assume("lens.transmission", "model", lens.matchedReference.transmission, "The tier lens's spectrally flat transmission is used at every f-number and focus in the lens capability; the tier declares it only at its f/4, 5 m reference state.");
661
+ if (!vignetting && !lensFieldMapping)
662
+ assume("lens.fieldThroughput", "model", "unity", "No vignetting, distortion, lateral colour or stray light.");
663
+ else {
664
+ if (vignetting)
665
+ assume("lens.vignetting", "model", { kind: "radial-illumination-vignetting", profile: vignetting }, "Caller-declared radial relative illumination 1 + r2·ρ² + r4·ρ⁴ + r6·ρ⁶, applied once as environment field throughput at the physical sensor point before any PSF.");
666
+ else
667
+ assume("lens.fieldThroughput", "model", "unity", "No vignetting, lateral colour or stray light.");
668
+ if (distortion)
669
+ assume("lens.distortion", "model", distortion, "Caller-declared " + distortion.kind + " distortion: each sensor point is inverse-distorted before the scene-ray query and irradiance is multiplied exactly once by the area factor 1/det(J).");
670
+ }
671
+ if (projection === undefined)
672
+ assume("lens.projection", "default", "rectilinear", "Ideal rectilinear (pinhole) projection.");
673
+ else
674
+ assume("lens.projection", "model", projection, "Ideal " + projection + " projection through the environment lens field mapping. At finite focus its scale is the thin-lens image distance (an approximation); the chart is still sized with the rectilinear thin-lens scale, so its edges move with the projection.");
675
+ // White balance
676
+ let whiteBalanceState = null, needsAuto = false;
677
+ if (whiteBalanceOption === "none")
678
+ whiteBalanceState = null;
679
+ else if (whiteBalanceOption === "auto")
680
+ needsAuto = true;
681
+ else if (whiteBalanceOption === "scene-illuminant") {
682
+ const xyz = illuminantXyz(illuminant), camera = XYZ_TO_CAMERA.map((row) => row[0] * xyz[0] + row[1] * xyz[1] + row[2] * xyz[2]);
683
+ whiteBalanceState = resolveCustomWhiteBalance({ stateId: "facade-wb-scene-illuminant", sampleSet: { imageStateId: "facade-white-diffuser",
684
+ inputDomain: "relative-pre-wb-camera-linear-rgb", samples: [{ sampleId: "white", red: camera[0], green: camera[1], blue: camera[2], weight: 1, clipped: false }] } });
685
+ }
686
+ else if (typeof whiteBalanceOption === "object" && whiteBalanceOption !== null && "kelvin" in whiteBalanceOption) {
687
+ const wb = allowKeys(whiteBalanceOption, "output.whiteBalance", ["kelvin", "tint"]);
688
+ const intent = createColorTemperatureWhiteBalanceIntent({ intentId: "facade-wb-cct", colorTemperatureKelvin: positive(wb.kelvin, "output.whiteBalance.kelvin"),
689
+ tint: wb.tint === undefined ? 0 : wb.tint });
690
+ whiteBalanceState = resolveColorTemperatureWhiteBalance({ stateId: "facade-wb-cct", intent, whiteLocus: "planckian", tintInterpretation: "duv-cie-1960-uv",
691
+ camera: { profileId: IDS.spectral, profileVersion: SIMULATE_CAPTURE_FACADE_VERSION, xyzToCameraRgb: XYZ_TO_CAMERA.map((r) => [...r]) } });
692
+ }
693
+ else if (typeof whiteBalanceOption === "object" && whiteBalanceOption !== null && "channelGains" in whiteBalanceOption) {
694
+ allowKeys(whiteBalanceOption, "output.whiteBalance", ["channelGains"]);
695
+ requireInputObject(whiteBalanceOption.channelGains, "output.whiteBalance.channelGains");
696
+ allowKeys(whiteBalanceOption.channelGains, "output.whiteBalance.channelGains", ["red", "green", "blue"]);
697
+ whiteBalanceState = resolveManualWhiteBalance({ stateId: "facade-wb-manual", channelGains: whiteBalanceOption.channelGains });
698
+ }
699
+ else
700
+ fail("Unknown output.whiteBalance.");
701
+ const intentOf = (state) => (state ? createCaptureWhiteBalanceIntent({ state }) : null);
702
+ const rendering = { schemaVersion: "0.1.0", profileId: "facade-sdr", profileVersion: "1", renderingExposureEv: 0, toneCurve,
703
+ ...(outputOptions.toneCurveParameters === undefined ? {} : { toneCurveParameters: outputOptions.toneCurveParameters }),
704
+ gamutHandling: "clip-components", outputDynamicRange: "sdr", transferFunction: "srgb", bitDepth: format === "srgb-16" || (format === "png" && !wideGamut && pngBitDepth === 16) ? 16 : 8, rounding: "nearest-ties-up", dither: "none" };
705
+ if (!linearPath)
706
+ assume("output.rendering", "model", { renderingExposureEv: 0, gamutHandling: "clip-components" }, "Developed linear sRGB/D65 with the RAW white level mapped to display white, no exposure push, out-of-gamut components clipped.");
707
+ else
708
+ assume("output.rendering", "model", { renderingExposureEv: 0, gamutHandling: "clip-components-in-target-gamut", sdrRendererUsed: false }, "The sRGB SDR renderer is not used: the linear development (RAW white level = relative 1.0, no exposure push) is converted to the target encoding; request.development.rendering is validated but only its exposure and tone curve (identity) apply.");
709
+ // Rendering choices are validated before any radiance work, so a bad tone curve fails fast.
710
+ parseSdrRenderingProfile(rendering);
711
+ if (highlightMode !== "clip" && highlightMode !== "reconstruct")
712
+ fail("output.highlightMode must be clip or reconstruct.");
713
+ // Frame profiles
714
+ const evidence = FACADE_EVIDENCE;
715
+ const nativeRaster = { pixelWidth: nativeWidth, pixelHeight: nativeHeight };
716
+ const captureModeProfile = { schemaVersion: "0.1.0", modes: [{ modeId: IDS.mode, evidence, acquisition: { kind: "single-frame" },
717
+ perFrameSampling: { kind: "native-effective-raster" }, reconstructionStages: [], processedImageRaster: { value: nativeRaster, evidence },
718
+ dependencies: ["color-sampling-model"] }] };
719
+ const bindingProfile = { schemaVersion: "0.1.0", bindingId: IDS.binding, colorSamplingProfileId: IDS.cfa, nativeRaster, evidence,
720
+ relationship: { kind: "regular-native-effective-sample-blocks", sitesPerNativeSampleX: 1, sitesPerNativeSampleY: 1, anchor: "shared-native-top-left" } };
721
+ const exposureWindow = rollingSchedule ? createRollingShutterCaptureExposureSchedule(rollingSchedule) :
722
+ { shutterMechanism: "electronic", nominalExposureDurationSeconds: { value: shutterSeconds, unit: "s", evidence },
723
+ opening: { kind: "simultaneous" }, closing: { kind: "simultaneous" } };
724
+ const buildRawInput = (intent) => ({
725
+ exposure: { captureId: IDS.capture, sceneStateId: IDS.sceneState, sceneTimeSeconds: 0, geometry,
726
+ exposure: { focalLengthMm, aperture: fNumber, shutterSeconds, iso: resolvedIso }, focus: { kind: "finite", distanceM: focusDistanceM },
727
+ noise: { seedUint32: noiseSeed, realizationId: "facade-noise", model: { ...SENSOR_RAW_PRODUCER_NOISE_MODEL } },
728
+ source: { kind: "scene-radiance-source", artifactId: "facade-scene-" + scenePreset, sha256: sourceSha256, dynamicRangeHistory: "no-loss-declared" },
729
+ whiteBalanceIntent: intent, adoptedWhiteXyz: null,
730
+ models: [{ profile: { id: "photivra-simulate-capture", version: SIMULATE_CAPTURE_FACADE_VERSION }, scientificStatus: "approximation", publicEvidenceIds: ["photivra:simulate-capture-facade"] }] },
731
+ frameId: IDS.frame, modeId: IDS.mode, captureModeProfile, colorSamplingProfile, bindingProfile, exposureWindow,
732
+ maximumOutputBytes: nativeWidth * nativeHeight * 7, tileWidth
733
+ });
734
+ const sceneBinding = { sceneStateId: IDS.sceneState, providerSceneId: IDS.scene, evidence };
735
+ // Shared per-site environment pieces (identical objects for every site keep the native task's shared-state fast path).
736
+ const range = { ...FACADE_SPECTRAL_RANGE_NANOMETERS };
737
+ // EQE breakpoints coincide with the quadrature subintervals, so the quadrature is not split further.
738
+ const eqeSpacing = (range.maximum - range.minimum) / Math.ceil((range.maximum - range.minimum) / spectralStep);
739
+ const eqeSamples = (channel) => {
740
+ const out = [];
741
+ for (let nm = range.minimum; nm <= range.maximum + 1e-9; nm += eqeSpacing)
742
+ out.push({ nm, value: EQE_SCALE[channel] * estimateCie1931ColorMatchingFunctions(nm)[CMF_KEY[channel]] * 555 / nm });
743
+ return out;
744
+ };
745
+ const operatingConditions = { temperatureC: 25, incidenceAngleDegreesFromNormal: 0, polarization: "unpolarized" };
746
+ // One single-channel response profile per CFA channel keeps each site's response data small.
747
+ const spectralResponseProfiles = Object.fromEntries(CHANNELS.map((only) => [only, { schemaVersion: "0.1.0", profileId: IDS.spectral, colorSamplingProfileId: IDS.cfa, evidence,
748
+ channels: [only].map((channelId) => ({ channelId, kind: "effective-external-quantum-efficiency", responseScope: "sensor-package-incident-effective-channel-response",
749
+ scientificStatus: "approximation", uncertainty: { kind: "not-quantified", limitation: "Idealized colorimetric response; not a measured sensor." },
750
+ evidence, referenceConditions: operatingConditions, conditionDependence: "not-modeled", externalQuantumEfficiency: sampled(eqeSamples(channelId)) })) }]));
751
+ const apertureArea = pitch * pitch;
752
+ const samplingApertureProfile = { schemaVersion: "0.1.0", profileId: IDS.sampling, colorSamplingProfileId: IDS.cfa, colorSamplingBindingId: IDS.binding, evidence,
753
+ siteCenterLattice: { kind: "regular-rectangular-site-center-lattice", coordinateSystem: "native-sensor-physical", pitchXMicrometers: pitch, pitchYMicrometers: pitch,
754
+ firstSiteCenterFromImagingAreaTopLeftMicrometers: { x: pitch / 2, y: pitch / 2 }, evidence },
755
+ geometricSensitiveAperture: { kind: "uniform-axis-aligned-rectangle", widthMicrometers: pitch, heightMicrometers: pitch, centerOffsetFromSiteCenterMicrometers: { x: 0, y: 0 }, evidence } };
756
+ const opticalStackProfile = { schemaVersion: "0.1.0", profileId: IDS.stack, evidence, effectiveAntiAliasingSpatialResponse: { kind: "absent", evidence } };
757
+ const perChannel = Object.fromEntries(CHANNELS.map((channelId) => [channelId, {
758
+ applicationProfile: { schemaVersion: "0.1.0", profileId: "facade-application-" + channelId, spectralResponseProfileId: IDS.spectral, colorSamplingProfileId: IDS.cfa, channelId,
759
+ samplingApertureProfileId: IDS.sampling, opticalStackProfileId: IDS.stack, evidence, incidentAreaBasis: { kind: "geometric-sensitive-aperture", areaSquareMicrometers: apertureArea },
760
+ spatialResponseModel: { kind: "uniform-over-geometric-sensitive-aperture", scientificStatus: "approximation", evidence, limitation: "Uniform response over the square aperture." },
761
+ referenceConditionPolicy: { kind: "exact-match-required" } },
762
+ operatingRangeProfile: { schemaVersion: "0.1.0", profileId: "facade-linearity-" + channelId, responseApplicationProfileId: "facade-application-" + channelId,
763
+ spectralResponseProfileId: IDS.spectral, colorSamplingProfileId: IDS.cfa, channelId, scientificStatus: "approximation",
764
+ uncertainty: { kind: "not-quantified", limitation: "Declared linear photo response for the idealized sensor." }, evidence,
765
+ inputRange: { kind: "wavelength-integrated-geometric-aperture-radiant-power", unit: "W", minimumInclusive: 0, maximumInclusive: 1 },
766
+ wavelengthApplicability: { wavelengthBasis: "vacuum", minimumNanometers: range.minimum, maximumNanometers: range.maximum, containment: "requested-range-must-be-contained" },
767
+ linearityCriterion: { maximumAbsoluteRelativeDeviation: 0.01 },
768
+ spatialLinearityModel: { kind: "linear-superposition-over-geometric-aperture", scientificStatus: "approximation", evidence, limitation: "Declared linear superposition." },
769
+ spectralInputModel: { kind: "per-spectral-bin", maximumBinWidthNanometers: Math.max(spectralStep, 20), scientificStatus: "approximation", evidence, limitation: "Declared per-bin linearity." },
770
+ referenceConditions: operatingConditions, referenceConditionPolicy: { kind: "exact-match-required" } }
771
+ }]));
772
+ const sourcePlane = { value: "sensor-package-incident", evidence };
773
+ const localExposure = { bindingProfile, exposureWindowInput: { ...exposureWindow, nativeRaster } };
774
+ const sceneBindings = {
775
+ illuminationProfile: { schemaVersion: "0.1.0", profileId: IDS.lights, sceneId: IDS.scene, evidence, sources: [{ sourceId: "illuminant", family: "environment",
776
+ enabled: true, geometry: { kind: "environment" }, magnitude: { kind: "relative-linear-scale", scale: 1, scientificStatus: "approximation",
777
+ limitation: "Absolute level is carried by the provider radiance (" + illuminanceLux + " lux)." },
778
+ spectrum: { kind: "unresolved", limitation: "Spectral radiance is supplied numerically by the provider (" + illuminant.id + ")." },
779
+ temporalBehavior: { kind: "time-invariant" }, evidence }] },
780
+ materialResponseProfile: { schemaVersion: "0.1.0", profileId: IDS.materials, sceneId: IDS.scene, evidence, materials: [{ materialResponseId: "facade-surface", evidence,
781
+ representation: { kind: "spectral-wavelength-preserving-data", dataArtifact: { id: "facade-scene-" + scenePreset, checksumSha256: sourceSha256 },
782
+ wavelengthBasis: "vacuum", wavelengthRangeNanometers: range, scatteringModel: "provider-defined-wavelength-preserving", scientificStatus: "approximation",
783
+ uncertainty: { kind: "not-quantified", limitation: "Analytic Lambertian reflectance." }, wavelengthChangingBehaviorModeled: false, emissionModeled: false } }],
784
+ fluorescenceModeled: false, volumetricMaterialTransportModeled: false, polarizationModeled: false },
785
+ providerProfile: { schemaVersion: "0.1.0", profileId: IDS.provider, sceneId: IDS.scene, illuminationProfileId: IDS.lights, materialResponseProfileId: IDS.materials,
786
+ outputQuantity: "outgoing-spectral-radiance", outputUnit: "W/m^2/sr/nm", scientificStatus: "approximation",
787
+ uncertainty: { kind: "not-quantified", limitation: "Analytic planar scene." },
788
+ fidelity: { spectral: "wavelength-resolved", material: "spectral-data", visibility: "resolved", directTransport: "approximation", indirectTransport: "not-modeled" },
789
+ wavelengthChangingTransportModeled: false, volumetricTransportModeled: false, polarizationModeled: false, evidence, limitations: ["Engine-authored planar chart or caller scene function."] }
790
+ };
791
+ const lensId = lens.exposure.profileId;
792
+ const opticsProfile = { schemaVersion: "0.1.0", profileId: lensId + ":facade-transmission", profileVersion: SIMULATE_CAPTURE_FACADE_VERSION, lensProfileId: lensId,
793
+ scientificStatus: "approximation", applicability: { focalLengthMm: { minimum: capabilities.selectedFocalLengthMm, maximum: capabilities.selectedFocalLengthMm },
794
+ nominalFNumber: { minimum: capabilities.aperture.widestAvailableFNumber, maximum: capabilities.aperture.narrowestAvailableFNumber }, focus: { kind: "any" } },
795
+ transmission: { kind: "spectral-transmission", scientificStatus: "approximation", wavelengthBasis: "vacuum",
796
+ samples: { value: [{ wavelengthNanometers: range.minimum, linearTransmissionFactor: lens.matchedReference.transmission },
797
+ { wavelengthNanometers: range.maximum, linearTransmissionFactor: lens.matchedReference.transmission }], evidence: preset.evidence },
798
+ uncertainty: { kind: "not-quantified", limitation: "Synthetic spectrally flat transmission from the tier preset." } },
799
+ distortionAreaMappingOwnership: "not-applied-by-bridge", psfRedistributionOwnership: "downstream-normalized-energy-redistribution",
800
+ sensorOpticalStackIncluded: false, strayLightIncluded: false, polarizationModeled: false, wavelengthChangingBehaviorModeled: false, volumetricScatteringModeled: false,
801
+ evidence: preset.evidence, limitations: [...lens.limitations, "Flat transmission applied across the lens capability by the capture facade."] };
802
+ const optics = { profile: opticsProfile, focalLengthMm, nominalFNumber: fNumber,
803
+ focus: { kind: "ideal-symmetric-thin-lens", objectDistanceM: focusDistanceM, pupilMagnificationAssumption: "unity" } };
804
+ const motion = { angularVelocityRadPerSec: angular, timeReference: "first-opening-boundary-phase", environmentDirectionConvention: "outgoing-radiance-toward-camera" };
805
+ const fieldThroughput = vignetting ? { kind: "radial-illumination-vignetting", profile: vignetting, evidence,
806
+ limitation: "Caller-declared radial relative illumination applied by the capture facade; not a measured lens." } :
807
+ { kind: "unity", evidence, limitation: "No vignetting or field throughput loss applied by the facade." };
808
+ const psf = lensPsf ?? { kind: "not-applied", evidence, limitation: "No lens PSF (point optics)." };
809
+ const pupilRecord = pupil ? { kind: "ideal-uniform-circular-pupil", radialSampleCount: pupil.radialSamples, angularSampleCount: pupil.angularSamples, evidence,
810
+ limitation: "Ideal uniform circular pupil; finite ray sampling, no convergence claim." } : undefined;
811
+ const spectralSamplingByChannel = Object.fromEntries(CHANNELS.map((channelId) => [channelId, { channelId, wavelengthBasis: "vacuum", wavelengthRangeNanometers: range,
812
+ maximumSubintervalWidthNanometers: spectralStep }]));
813
+ const chargeSamplingProfile = { schemaVersion: "0.1.0", profileId: IDS.chargeSampling, completenessProfileId: IDS.completeness, scientificStatus: "approximation",
814
+ photoShotNoiseModel: "poisson", darkShotNoiseModel: "poisson", additionalComponentPolicies: [], evidence, limitations: ["Poisson photo and dark shot noise."] };
815
+ const channelAt = (x, y) => ["red", "green", "green", "blue"][(y % 2) * 2 + (x % 2)];
816
+ function siteInput(index) {
817
+ if (!Number.isSafeInteger(index) || index < 0 || index >= nativeWidth * nativeHeight)
818
+ fail("Native site index out of range.");
819
+ const x = index % nativeWidth, y = Math.floor(index / nativeWidth), channel = channelAt(x, y), site = { x, y }, c = perChannel[channel], r = regimeByChannel[channel];
820
+ const environment = { temporalIntegrationId: IDS.frame + ":native:" + index,
821
+ sensor: { spatialSampling: { imagingArea, samplingApertureProfile, opticalStackProfile, site, spatialSampleCountX: 1, spatialSampleCountY: 1 },
822
+ spectralSampling: spectralSamplingByChannel[channel], colorSamplingProfile, spectralResponseProfile: spectralResponseProfiles[channel],
823
+ responseApplication: { applicationProfile: c.applicationProfile, sourcePlane, operatingConditions }, operatingRangeProfile: c.operatingRangeProfile, localExposure },
824
+ sceneBindings, optics, motion, temporalSampleCount, fieldThroughput, psf, ...(pupilRecord ? { pupil: pupilRecord } : {}), ...(lensFieldMapping ? { lensFieldMapping } : {}) };
825
+ // The charge completeness profile must cover the site's own exposure window (shifted by s·τ under a rolling shutter).
826
+ const window = rollingSchedule ? calculateCaptureExposureWindows({ ...exposureWindow, nativeRaster, samplePointsNative: [{ x: x + 0.5, y: y + 0.5 }] }).value.samples[0] :
827
+ { startOffsetSecondsFromOpeningReference: 0, endOffsetSecondsFromOpeningReference: shutterSeconds };
828
+ return { environment, operatingTemperatureC: OPERATING_TEMPERATURE_C,
829
+ darkCurrentProfile: { schemaVersion: "0.1.0", profileId: IDS.dark, colorSamplingProfileId: IDS.cfa, channelId: channel, scientificStatus: "approximation", evidence,
830
+ uncertainty: { kind: "not-quantified", limitation: "Synthetic dark current for the capture facade." }, chargeMeaning: "pre-compensation-thermally-generated-electrons",
831
+ siteApplicability: { kind: "exact-site", site }, temperatureModel: { kind: "fixed-reference-temperature", referenceTemperatureC: OPERATING_TEMPERATURE_C,
832
+ darkCurrentElectronsPerSecond: DARK_CURRENT_ELECTRONS_PER_SECOND }, darkCurrentCompensationIncluded: false, spatialDarkCurrentNonuniformityModeled: false },
833
+ charge: { completenessProfile: { schemaVersion: "0.1.0", profileId: IDS.completeness, colorSamplingProfileId: IDS.cfa, channelId: channel, site, bindingId: IDS.binding,
834
+ timeReference: "first-opening-boundary-phase", startOffsetSecondsFromOpeningReference: window.startOffsetSecondsFromOpeningReference,
835
+ endOffsetSecondsFromOpeningReference: window.endOffsetSecondsFromOpeningReference,
836
+ darkCurrentProfileId: IDS.dark, includedAdditionalComponentIds: [], coverageMeaning: "all-material-stored-electron-contributors-accounted-for",
837
+ scientificStatus: "approximation", evidence, limitation: "Photo and dark charge only." } },
838
+ readout: { samplingProfile: chargeSamplingProfile, capacityProfile: { schemaVersion: "0.1.0", profileId: IDS.capacity, colorSamplingProfileId: IDS.cfa, channelId: channel,
839
+ capacityMeaning: "physical-charge-storage-capacity-electrons", capacityElectrons, scientificStatus: "approximation",
840
+ uncertainty: { kind: "not-quantified", limitation: "Derived from the tier's declared saturation signal." }, evidence, siteApplicability: { kind: "exact-site", site },
841
+ operatingState: { stateId: IDS.operatingState, evidence }, temperatureApplicability: { kind: "exact-reference-temperature", temperatureC: OPERATING_TEMPERATURE_C } },
842
+ operatingStateId: IDS.operatingState, readoutProfile: r.readoutProfile, regimeId: r.regimeId } };
843
+ }
844
+ const readTile = async (request) => ({ ...request, sites: Array.from({ length: request.width }, (_, j) => siteInput(request.y * nativeWidth + request.x + j)) });
845
+ const yieldControl = createHostYield().yieldControl;
846
+ const perSite = queriesPerSite;
847
+ const evaluations = nativeWidth * nativeHeight * perSite;
848
+ // The native task admits at most 100 000 radiance queries per tile.
849
+ const tileWidth = Math.max(1, Math.min(256, Math.floor(100_000 / perSite)));
850
+ const environmentRaw = { raw: buildRawInput(intentOf(whiteBalanceState)), sceneBinding,
851
+ maximumProviderEvaluations: Math.min(2_000_000_000, Math.max(nativeWidth * nativeHeight, evaluations)) };
852
+ async function runTask(task) {
853
+ const onAbort = () => task.cancel();
854
+ signal?.addEventListener("abort", onAbort, { once: true });
855
+ try {
856
+ checkAbort();
857
+ await task.run();
858
+ checkAbort();
859
+ }
860
+ catch (error) {
861
+ task.dispose();
862
+ if (signal?.aborted)
863
+ fail("simulateCapture aborted.");
864
+ throw error;
865
+ }
866
+ finally {
867
+ signal?.removeEventListener("abort", onAbort);
868
+ }
869
+ }
870
+ const rawStarted = Date.now();
871
+ const plan = calculateNativeRawPlan(environmentRaw.raw);
872
+ const progress = (stage, total) => {
873
+ let completed = 0;
874
+ return () => { completed++; onProgress?.({ stage, completedTileCount: completed, totalTileCount: total }); };
875
+ };
876
+ const rawTick = progress("raw", plan.tileCount);
877
+ const rawTask = createNativeEnvironmentRawTask(environmentRaw, {
878
+ readTile: async (request) => { const tile = await readTile(request); rawTick(); return tile; },
879
+ evaluateRadiance: provider.evaluateRadiance, ...(pupil ? { evaluateApertureRadiance: provider.evaluateApertureRadiance } : {}), yieldControl
880
+ });
881
+ await runTask(rawTask);
882
+ let rawOutput = rawTask.takeOutput();
883
+ const providerEvaluationCount = rawOutput.providerEvaluationCount;
884
+ const rawMs = Date.now() - rawStarted, wbStarted = Date.now();
885
+ let finalRawInput = environmentRaw;
886
+ if (needsAuto) {
887
+ // Grey-world AWB over pre-WB RAW: black-subtracted, white-normalized channel means of each 2×2 CFA block on a coarse grid.
888
+ const raw = rawOutput.raw, step = Math.max(2, 2 * Math.floor(Math.sqrt(width * height / 4096) / 1)), samples = [];
889
+ for (let y = margin - (margin % 2); y + 1 < nativeHeight; y += step)
890
+ for (let x = margin - (margin % 2); x + 1 < nativeWidth; x += step) {
891
+ const value = (dx, dy) => { const i = (y + dy) * nativeWidth + x + dx; return (raw.codes[i] - raw.blackLevels[i]) / (raw.effectiveSaturationCodes[dy * 2 + dx] - raw.blackLevels[i]); };
892
+ let clipped = false;
893
+ for (const [dx, dy] of [[0, 0], [1, 0], [0, 1], [1, 1]])
894
+ if (raw.saturationFlags[(y + dy) * nativeWidth + x + dx] !== 0)
895
+ clipped = true;
896
+ samples.push({ sampleId: "block-" + x + "-" + y, red: Math.max(1e-9, value(0, 0)), green: Math.max(1e-9, (value(1, 0) + value(0, 1)) / 2), blue: Math.max(1e-9, value(1, 1)), weight: 1, clipped });
897
+ }
898
+ const policy = body.whiteBalance.awbPolicies[0];
899
+ assume("output.whiteBalance.auto", "model", { policyId: policy.policyId, samples: samples.length }, "Grey-world AWB of the body's " + policy.policyId + " policy over 2×2 CFA block means of the pre-WB RAW; the RAW is then rendered again with that intent (the same radiance queries are repeated).");
900
+ whiteBalanceState = estimateAutoWhiteBalance({ stateId: "facade-wb-auto", profile: body.whiteBalance, policyId: policy.policyId,
901
+ sampleSet: { imageStateId: "facade-pre-wb-raw", inputDomain: "relative-pre-wb-camera-linear-rgb", samples } });
902
+ finalRawInput = { raw: buildRawInput(intentOf(whiteBalanceState)), sceneBinding, maximumProviderEvaluations: environmentRaw.maximumProviderEvaluations };
903
+ const wbTick = progress("white-balance", plan.tileCount);
904
+ // A second lean RAW pass with the measured intent. It gives the same RAW as re-running the sensor stage on a
905
+ // retained optical stage, and is faster: retaining the optical stage takes the first pass off the lean executor.
906
+ const wbTask = createNativeEnvironmentRawTask(finalRawInput, {
907
+ readTile: async (request) => { const tile = await readTile(request); wbTick(); return tile; },
908
+ evaluateRadiance: provider.evaluateRadiance, ...(pupil ? { evaluateApertureRadiance: provider.evaluateApertureRadiance } : {}), yieldControl
909
+ });
910
+ await runTask(wbTask);
911
+ rawOutput = wbTask.takeOutput();
912
+ }
913
+ const wbMs = Date.now() - wbStarted, devStarted = Date.now();
914
+ const colorProfile = { schemaVersion: "0.1.0", profileId: IDS.spectral, profileVersion: SIMULATE_CAPTURE_FACADE_VERSION, colorSamplingProfileId: IDS.cfa,
915
+ channelIds: ["red", "green", "blue"], scientificStatus: "approximation", referenceIlluminant: "D65", normalizedCameraChannelsToXyz: CAMERA_TO_XYZ,
916
+ evidence, limitations: ["Diagonal camera→XYZ matrix of the idealized colorimetric (Luther–Ives) sensor; exact up to spectral quadrature."] };
917
+ const phaseProfiles = builtInDemosaic ? builtInDemosaic.phaseProfiles : bilinearPhaseProfiles();
918
+ const developmentWhiteBalance = whiteBalanceState ? "apply-resolved-sensor-gains" : "not-required";
919
+ // DNG (2.0): the packed native RAW itself, before development takes ownership of it.
920
+ let dng;
921
+ if (format === "dng") {
922
+ const noise = Object.fromEntries(CHANNELS.map((channel) => {
923
+ const resolved = resolveSensorReadoutRegime({ profile: regimeByChannel[channel].readoutProfile, regimeId: regimeByChannel[channel].regimeId });
924
+ return [channel, { conversionGainElectronsPerCode: resolved.regime.systemConversionGainElectronsPerCode.value / (resolved.regime.adc.digitalGain ?? 1),
925
+ readNoiseRmsElectrons: resolved.combinedReadNoiseRmsElectrons }];
926
+ }));
927
+ dng = await serializeNativeRawDng({ raw: rawOutput.raw, colorProfile, noise, ...(dngCapturedAtUtc === undefined ? {} : { capturedAtUtc: dngCapturedAtUtc }) });
928
+ checkAbort();
929
+ assume("output.dng", "model", { writer: "serializeNativeRawDng", schemaVersion: dng.schemaVersion, whiteLevel: dng.whiteLevel, blackLevels: dng.blackLevels,
930
+ noiseProfile: dng.noiseProfile, unrepresentedSaturationFlagCount: dng.unrepresentedSaturationFlagCount }, "Lossless uncompressed 16-bit CFA DNG of exactly the packed native RAW codes: per-phase BlackLevel, WhiteLevel = smallest per-phase effective white, ActiveArea and DefaultCrop from the capture geometry, ColorMatrix1/ForwardMatrix1 from the facade's diagonal camera→XYZ profile (D65), AsShotNeutral from the white-balance intent, BaselineExposure 0, and NoiseProfile from each channel's readout regime (conversion gain ÷ digital gain, combined read noise; Poisson + Gaussian + quantization only). No linear master or developed image is declared; saturation flags below WhiteLevel are not representable and are counted.");
931
+ }
932
+ // Development
933
+ const development = { phaseProfiles, colorProfile,
934
+ whiteBalance: developmentWhiteBalance, rendering, highlightMode, ...(mirrorBorder ? { borderHandling: "mirror" } : {}),
935
+ maximumRetainedPayloadBytes: plan.outputBytes + width * height * 3 * (rendering.bitDepth / 8) };
936
+ let developed;
937
+ let gamutMappingDiagnostics;
938
+ if (!linearPath) {
939
+ const devTask = createNativeRawDevelopmentTask({ ...development, raw: rawOutput.raw }, { yieldControl,
940
+ onProgress: (p) => onProgress?.({ stage: "development", completedTileCount: p.completedTileCount, totalTileCount: p.totalTileCount }) });
941
+ await runTask(devTask);
942
+ developed = devTask.takeOutput();
943
+ }
944
+ else {
945
+ // Wide gamut: the native development operator up to linear sRGB/D65 (unclipped), then XYZ → target primaries.
946
+ let lastYield = Date.now();
947
+ const linear = await developLinearSrgb({ ...development, raw: rawOutput.raw }, async (completed, total) => {
948
+ onProgress?.({ stage: "development", completedTileCount: completed, totalTileCount: total });
949
+ if (Date.now() - lastYield >= 8) {
950
+ await yieldControl();
951
+ checkAbort();
952
+ lastYield = Date.now();
953
+ }
954
+ });
955
+ {
956
+ const mapping = perceptual ? prepareRgbGamutMapping({ colorSpace: colorEncoding, method: "cielab-hue-preserving-chroma-compression", kneeFraction: PERCEPTUAL_GAMUT_KNEE_FRACTION }) : undefined;
957
+ const encoded = encodeSdrFromLinear(linear.rgb, colorEncoding, pngBitDepth, mapping);
958
+ developed = { raw: rawOutput.raw, integerSamples: encoded.samples, highlight: linear.highlight, diagnostics: encoded.diagnostics };
959
+ if (mapping) {
960
+ gamutMappingDiagnostics = { method: "cielab-hue-preserving-chroma-compression", kneeFraction: PERCEPTUAL_GAMUT_KNEE_FRACTION,
961
+ adjustedPixelCount: encoded.adjustedPixelCount, lightnessClippedPixelCount: encoded.lightnessClippedPixelCount };
962
+ assume("output.gamutMapping", "model", { method: "cielab-hue-preserving-chroma-compression", kneeFraction: PERCEPTUAL_GAMUT_KNEE_FRACTION, colorSpace: colorEncoding }, "prepareRgbGamutMapping: CIELAB L* and hue held, chroma above 90 % of the boundary chroma C_max(L*, h) compressed with a tanh knee so it never reaches C_max (Morovič 2008, §10). In-gamut colours below the knee are unchanged; saturated in-gamut colours above it are desaturated slightly. L* ≥ 100 (above the RAW white) maps to white. CIELAB hue is not perceptually constant for saturated blues, so they can shift toward purple.");
963
+ }
964
+ assume("output.colorEncoding.conversion", "model", { from: "linear-srgb-d65 (unclipped)", via: "CIE XYZ", to: colorEncoding, gamutHandling: perceptual ? "cielab-hue-preserving-chroma-compression" : "clip-components",
965
+ transfer: colorEncoding === "rec2020" ? "bt2020-oetf (= BT.709 OETF)" : "srgb (W3C CSS Color 4)" }, "Developed with the native development operator (demosaic, per-site white-level clip, WB, highlight mode, diagonal camera→XYZ, XYZ→linear sRGB) but before any sRGB clipping, so colours outside sRGB are kept; converted to " +
966
+ colorEncoding + " through XYZ (matrices from primaries and D65, as in W3C CSS Color 4), " + (perceptual ? "gamut-mapped perceptually (output.gamutMapping)" : "components clipped to [0, 1] in the target gamut") +
967
+ ", then encoded and rounded to nearest. RAW white maps to display white, as in the sRGB path.");
968
+ }
969
+ }
970
+ const devMs = Date.now() - devStarted, jpegStarted = Date.now();
971
+ let jpeg;
972
+ if (format === "jpeg") {
973
+ const samples = developed.integerSamples;
974
+ const jpegSha = await sha256Hex(Array.from(samples.subarray(0, Math.min(samples.length, 4096))).join(",") + ":" + samples.length + ":" + sourceSha256);
975
+ const source = { captureId: IDS.capture, imageStateId: "facade-developed", artifactId: "facade-developed-srgb8", sha256: jpegSha, width, height, encoding: "encoded-srgb-8-rgb" };
976
+ // The encoder reads every pixel once, in source tiles of at most 256 × 32 aligned to the crop origin.
977
+ const reads = Math.ceil(height / 32) * Math.ceil(width / 256), jpegTick = progress("jpeg", reads);
978
+ const jpegTask = createPrintJpegTask({ source, crop: { x: 0, y: 0, width, height }, outputWidth: width, outputHeight: height, quantizationStep: jpegStep,
979
+ maximumEncodedBytes: Math.max(65536, width * height * 6), maximumProviderReads: reads }, {
980
+ readTile: async (request) => {
981
+ jpegTick();
982
+ const out = new Uint8Array(request.width * request.height * 3);
983
+ for (let row = 0; row < request.height; row++)
984
+ out.set(samples.subarray(((request.y + row) * width + request.x) * 3, ((request.y + row) * width + request.x + request.width) * 3), row * request.width * 3);
985
+ return { ...request, samples: out };
986
+ }, yieldControl
987
+ });
988
+ await runTask(jpegTask);
989
+ jpeg = jpegTask.takeOutput().bytes;
990
+ assume("output.jpeg", "model", { encoder: "photivra-print-jpeg", iccProfile: "photivra-srgb" }, "Baseline JPEG from the existing print JPEG encoder with an embedded sRGB ICC profile; the source digest covers a sample prefix, length and scene digest.");
991
+ }
992
+ const jpegMs = Date.now() - jpegStarted;
993
+ let png;
994
+ if (format === "png") {
995
+ png = serializePngImage({ width, height, bitDepth: pngBitDepth, samples: developed.integerSamples, colourEncoding: pngColourEncoding(colorEncoding) });
996
+ assume("output.png", "model", { encoder: "serializePngImage", bitDepth: pngBitDepth, colourEncoding: pngColourEncoding(colorEncoding) }, "Deterministic PNG (8/16-bit RGB, in-house zlib) with the colour encoding signalled in the file: the sRGB chunk for sRGB, otherwise a cICP chunk with ITU-T H.273 primaries/transfer code points. No mastering-display (mDCV) or content-light-level (cLLI) metadata is written.");
997
+ }
998
+ return Object.freeze({
999
+ facadeVersion: SIMULATE_CAPTURE_FACADE_VERSION, engineApiVersion: ENGINE_API_VERSION,
1000
+ image: { width, height, format, samples: developed.integerSamples, ...(jpeg ? { jpeg } : {}), ...(png ? { png, colorEncoding: colorEncoding } : {}), ...(dng ? { dng: dng.bytes } : {}) },
1001
+ raw: developed.raw, plan,
1002
+ request: { environmentRaw: finalRawInput, siteInput, development, whiteBalanceState },
1003
+ exposure: { mode, settings: { fNumber, shutterSeconds, iso: resolvedIso }, ev100: Math.log2(fNumber ** 2 / shutterSeconds),
1004
+ meteredSceneLuminanceCdPerSquareMeter: meteredLuminance, resolution },
1005
+ scene: { sceneId: chart ? "chart:" + chart.preset : callerScene.sceneId, preset: scenePreset, parameters: chart ? chart.parameters : {},
1006
+ illuminant: { id: illuminant.id, description: illuminant.description, illuminanceLux }, distanceM: subjectDistanceM,
1007
+ framedWidthM: 2 * metresPerFrameUnit, framedHeightM: 2 * metresPerFrameUnit * aspect, sourceSha256 },
1008
+ diagnostics: { providerEvaluationCount, highlight: developed.highlight, rendering: developed.diagnostics, ...(gamutMappingDiagnostics ? { gamutMapping: gamutMappingDiagnostics } : {}),
1009
+ timingsMs: { raw: rawMs, whiteBalance: wbMs, development: devMs, jpeg: jpegMs, total: Date.now() - started } },
1010
+ provenance: { scientificStatus: "approximation", cameraPresetId: preset.presetId, cameraPresetVersion: preset.presetVersion,
1011
+ evidence: [...FACADE_EVIDENCE, ...preset.evidence],
1012
+ composedContracts: ["resolveGenericEquipmentExposureCapabilities", "meterRelativeExposure", "createExposureMeterTargetFromMeteringResult",
1013
+ "resolve" + (mode === "manual" ? "Manual" : mode === "aperture-priority" ? "AperturePriority" : mode === "shutter-priority" ? "ShutterPriority" : "ProgramAuto") + "ExposureMode",
1014
+ ...(rollingSchedule ? ["resolveElectronicRollingShutterExposureSchedule", "createRollingShutterCaptureExposureSchedule"] : []),
1015
+ "resolveGenericIsoSignalChain", "createNativeEnvironmentRawTask", ...(needsAuto ? ["estimateAutoWhiteBalance"] : []),
1016
+ ...(builtInDemosaic ? ["createBuiltInBayerDemosaicProfiles"] : []),
1017
+ ...(linearPath ? ["native RAW development operator (linear, pre-clip)", "resolveRgbColorSpace", ...(perceptual ? ["prepareRgbGamutMapping"] : [])] : ["createNativeRawDevelopmentTask"]),
1018
+ ...(jpeg ? ["createPrintJpegTask"] : []), ...(png ? ["serializePngImage"] : []), ...(dng ? ["resolveSensorReadoutRegime", "serializeNativeRawDng"] : [])],
1019
+ limitations: ["Educational approximation: synthetic tier sensor values, idealized colorimetric sensor, planar Lambertian scene and point optics; not a calibrated camera emulation.",
1020
+ "Not offered by the facade: atmospheric haze and polarizers (the scene-radiance provider contract declares no volumetric transport or polarization), flash, video, HDR brackets and processing operators; compose those APIs directly."] },
1021
+ assumptions: Object.freeze(assumptions.map((a) => Object.freeze(a)))
1022
+ });
1023
+ }
1024
+ /** Chart preset ids accepted by `simulateCapture({ scene: { preset } })`. */
1025
+ export const SIMULATE_CAPTURE_CHART_PRESETS = CHART_SCENE_PRESETS;
1026
+ //# sourceMappingURL=simulate-capture.js.map