@photivra/engine 2.8.1 → 2.10.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 (91) hide show
  1. package/CHANGELOG.md +144 -1
  2. package/CITATION.cff +1 -1
  3. package/README.md +8 -6
  4. package/SECURITY.md +1 -1
  5. package/THIRD_PARTY.md +2 -0
  6. package/dist/capture/export-jpeg.d.ts +19 -0
  7. package/dist/capture/export-jpeg.js +190 -108
  8. package/dist/capture/export-jpeg.js.map +1 -1
  9. package/dist/capture/native-environment-lean.js +106 -12
  10. package/dist/capture/native-environment-lean.js.map +1 -1
  11. package/dist/capture/native-environment-tabulated.d.ts +37 -0
  12. package/dist/capture/native-environment-tabulated.js +42 -6
  13. package/dist/capture/native-environment-tabulated.js.map +1 -1
  14. package/dist/core/portable-math.d.ts +13 -0
  15. package/dist/core/portable-math.js +43 -0
  16. package/dist/core/portable-math.js.map +1 -1
  17. package/dist/core/version.d.ts +1 -1
  18. package/dist/core/version.js +1 -1
  19. package/dist/core/version.js.map +1 -1
  20. package/dist/facade/channel-response.d.ts +15 -0
  21. package/dist/facade/channel-response.js +33 -0
  22. package/dist/facade/channel-response.js.map +1 -0
  23. package/dist/facade/diffraction-provider.js +50 -15
  24. package/dist/facade/diffraction-provider.js.map +1 -1
  25. package/dist/facade/display-view.d.ts +75 -0
  26. package/dist/facade/display-view.js +471 -109
  27. package/dist/facade/display-view.js.map +1 -1
  28. package/dist/facade/expected-signal-setup.js +2 -1
  29. package/dist/facade/expected-signal-setup.js.map +1 -1
  30. package/dist/facade/expected-signal.d.ts +36 -4
  31. package/dist/facade/expected-signal.js +331 -43
  32. package/dist/facade/expected-signal.js.map +1 -1
  33. package/dist/facade/scene-radiance.d.ts +4 -1
  34. package/dist/facade/scene-radiance.js +4 -4
  35. package/dist/facade/scene-radiance.js.map +1 -1
  36. package/dist/facade/simulate-capture.d.ts +77 -5
  37. package/dist/facade/simulate-capture.js +232 -40
  38. package/dist/facade/simulate-capture.js.map +1 -1
  39. package/dist/facade/simulate-display-view.d.ts +25 -2
  40. package/dist/facade/simulate-display-view.js +55 -21
  41. package/dist/facade/simulate-display-view.js.map +1 -1
  42. package/dist/gpu/image-kernel-execution.d.ts +5 -0
  43. package/dist/gpu/image-kernel-execution.js +2 -2
  44. package/dist/gpu/image-kernel-execution.js.map +1 -1
  45. package/dist/index.d.ts +5 -4
  46. package/dist/index.js +3 -3
  47. package/dist/index.js.map +1 -1
  48. package/dist/optics/diffraction-psf-profile.d.ts +19 -17
  49. package/dist/optics/diffraction-psf-profile.js +49 -18
  50. package/dist/optics/diffraction-psf-profile.js.map +1 -1
  51. package/dist/optics/irradiance-convolution.d.ts +11 -0
  52. package/dist/optics/irradiance-convolution.js +64 -24
  53. package/dist/optics/irradiance-convolution.js.map +1 -1
  54. package/dist/optics/scene-to-sensor-irradiance.d.ts +13 -0
  55. package/dist/optics/scene-to-sensor-irradiance.js +58 -24
  56. package/dist/optics/scene-to-sensor-irradiance.js.map +1 -1
  57. package/dist/optics/sensor-aperture-rays.js +2 -1
  58. package/dist/optics/sensor-aperture-rays.js.map +1 -1
  59. package/dist/optics/sensor-environment-query.js +4 -3
  60. package/dist/optics/sensor-environment-query.js.map +1 -1
  61. package/dist/optics/site-diffraction-kernel.d.ts +16 -0
  62. package/dist/optics/site-diffraction-kernel.js +60 -8
  63. package/dist/optics/site-diffraction-kernel.js.map +1 -1
  64. package/dist/output/print-jpeg.d.ts +28 -1
  65. package/dist/output/print-jpeg.js +79 -7
  66. package/dist/output/print-jpeg.js.map +1 -1
  67. package/dist/processing/demosaic.js +114 -37
  68. package/dist/processing/demosaic.js.map +1 -1
  69. package/dist/schema/scene-radiance.d.ts +8 -0
  70. package/dist/schema/scene-radiance.js +28 -17
  71. package/dist/schema/scene-radiance.js.map +1 -1
  72. package/dist/sensor/environment-photo-signal.d.ts +21 -0
  73. package/dist/sensor/environment-photo-signal.js +68 -2
  74. package/dist/sensor/environment-photo-signal.js.map +1 -1
  75. package/dist/sensor/stochastic-seed.d.ts +12 -0
  76. package/dist/sensor/stochastic-seed.js +25 -0
  77. package/dist/sensor/stochastic-seed.js.map +1 -1
  78. package/docs/API_REFERENCE.md +23 -1
  79. package/docs/QUICK_START.md +77 -8
  80. package/docs/USAGE.md +30 -0
  81. package/docs/api/capture-export-jpeg.md +10 -0
  82. package/docs/api/core-version.md +1 -1
  83. package/docs/api/exports.json +77 -2
  84. package/docs/api/facade-display-view.md +50 -0
  85. package/docs/api/facade-scene-radiance.md +2 -1
  86. package/docs/api/facade-simulate-capture.md +88 -8
  87. package/docs/api/facade-simulate-display-view.md +23 -1
  88. package/docs/api/gpu-image-kernel-execution.md +5 -0
  89. package/docs/api/optics-diffraction-psf-profile.md +1 -1
  90. package/docs/api/output-print-jpeg.md +41 -0
  91. package/package.json +1 -1
package/docs/USAGE.md CHANGED
@@ -7516,6 +7516,36 @@ const view = await simulateDisplayView({ scene: { preset: "slanted-edge" }, sens
7516
7516
  console.log(view.kind, view.view.scaleX, view.assumptions.find((a) => a.id === "view.definition")?.value);
7517
7517
  ```
7518
7518
 
7519
+ #### Sharing AHD correction states between workers
7520
+
7521
+ With `output.demosaic: "ahd"`, low-signal display pixels read correction states that each realm (a page, a Web Worker)
7522
+ computes and keeps for itself. Two workers can split a part's states and exchange them instead of both computing all
7523
+ of them; the renders have the same bytes either way (see "AHD at low signal" in
7524
+ [DISPLAY_VIEW.md](https://github.com/Photivra/photivra/blob/main/docs/DISPLAY_VIEW.md#ahd-at-low-signal)). Import only
7525
+ states whose key a prepared view reported. The two handles below run in one script for the example; in an app each
7526
+ runs in its own worker, and the arrays travel with `postMessage(set, [set.gridKeys.buffer, set.values.buffer])`.
7527
+
7528
+ ```ts
7529
+ import { serializeDisplayViewCorrectionStates, bindDisplayViewCorrectionStates, prepareDisplayView, type SimulateCaptureOptions } from "@photivra/engine";
7530
+
7531
+ const request: SimulateCaptureOptions = { scene: { preset: "uniform", reflectance: 0.18 }, exposure: { iso: 1600, shutterSeconds: 1 / 2000 },
7532
+ sensor: { pixelPitchMicrometers: 1.4 }, output: { width: 200, height: 150, demosaic: "ahd" } };
7533
+ const part = { width: 20, height: 15, region: { x: 0, y: 0, width: 10, height: 8 } };
7534
+ // Each worker prepares the same request; the key says which states it reads.
7535
+ const workerA = await prepareDisplayView(request), workerB = await prepareDisplayView(request);
7536
+ console.log(workerA.correctionKey() === workerB.correctionKey());
7537
+ // Worker A finds the states the part needs (no image, no state computed) and gives half to each worker.
7538
+ const { gridKeys } = await workerA.collectCorrectionStates(part);
7539
+ const half = Math.ceil(gridKeys.length / 2);
7540
+ const fromA = await workerA.computeCorrectionStates(gridKeys.slice(0, half));
7541
+ const fromB = await workerB.computeCorrectionStates(gridKeys.slice(half));
7542
+ // Each worker imports the other's share (in one realm, as here, the second import adds nothing new).
7543
+ console.log(bindDisplayViewCorrectionStates([fromB]), bindDisplayViewCorrectionStates([fromA]));
7544
+ // Either worker now renders the part without computing a state; a new worker can start from an export.
7545
+ const view = await workerA.render(part);
7546
+ console.log(view.diagnostics.ahdCorrectedPixelCount, serializeDisplayViewCorrectionStates().map((set) => set.gridKeys.length));
7547
+ ```
7548
+
7519
7549
  ### Facade 2.0 options: output, demosaic, optics and motion
7520
7550
 
7521
7551
  Each 2.0 option is opt-in and uses an existing public contract. Without them, the facade's output is
@@ -14,3 +14,13 @@ was exactly a negative half-integer. The repository's test images are unchanged.
14
14
  ```ts
15
15
  EXPORT_JPEG_ENCODER_ID = "photivra-baseline-jpeg-444-v2" as const
16
16
  ```
17
+
18
+ ## JpegStripBits
19
+
20
+ The entropy-coded bits of consecutive blocks of one image, coded apart from the blocks before them (`encodeJpegStrip`):
21
+ the first block's quantized coefficients (3 × 64, natural order), whose DC differences need the block before; the other
22
+ blocks' bits, unstuffed, most-significant first; and the last block's DC values, which the next strip's first block needs.
23
+
24
+ ```ts
25
+ export interface JpegStripBits { first: Float64Array; bits: Uint8Array; bitLength: number; blockCount: number; lastDc: Float64Array }
26
+ ```
@@ -11,5 +11,5 @@ version they were made with. Proof-of-concept (POC), schema and model contracts
11
11
  own versions. Release checks confirm it matches package.json and the packed package.
12
12
 
13
13
  ```ts
14
- ENGINE_API_VERSION = "2.8.1" as const
14
+ ENGINE_API_VERSION = "2.10.0" as const
15
15
  ```
@@ -1,6 +1,6 @@
1
1
  {
2
- "packageVersion": "2.8.1",
3
- "engineApiVersion": "2.8.1",
2
+ "packageVersion": "2.10.0",
3
+ "engineApiVersion": "2.10.0",
4
4
  "exports": [
5
5
  {
6
6
  "name": "AbsoluteUncertaintyEstimate",
@@ -212,6 +212,11 @@
212
212
  "source": "src/processing/denoise.ts",
213
213
  "kind": "type"
214
214
  },
215
+ {
216
+ "name": "bindDisplayViewCorrectionStates",
217
+ "source": "src/facade/display-view.ts",
218
+ "kind": "runtime"
219
+ },
215
220
  {
216
221
  "name": "bindIsoCapabilityToExposureCapabilities",
217
222
  "source": "src/equipment/iso-capabilities.ts",
@@ -2097,6 +2102,11 @@
2097
2102
  "source": "src/capture/native-environment-raw.ts",
2098
2103
  "kind": "runtime"
2099
2104
  },
2105
+ {
2106
+ "name": "composePrintJpegStrips",
2107
+ "source": "src/output/print-jpeg.ts",
2108
+ "kind": "runtime"
2109
+ },
2100
2110
  {
2101
2111
  "name": "composeRelativeErrorBounds",
2102
2112
  "source": "src/gpu/image-kernel-execution.ts",
@@ -2377,6 +2387,11 @@
2377
2387
  "source": "src/color/illuminants.ts",
2378
2388
  "kind": "runtime"
2379
2389
  },
2390
+ {
2391
+ "name": "createPrintJpegStrip",
2392
+ "source": "src/output/print-jpeg.ts",
2393
+ "kind": "runtime"
2394
+ },
2380
2395
  {
2381
2396
  "name": "createPrintJpegTask",
2382
2397
  "source": "src/output/print-jpeg.ts",
@@ -2592,6 +2607,16 @@
2592
2607
  "source": "src/core/spectral-composition.ts",
2593
2608
  "kind": "type"
2594
2609
  },
2610
+ {
2611
+ "name": "DisplayViewCorrectionStateKeys",
2612
+ "source": "src/facade/display-view.ts",
2613
+ "kind": "type"
2614
+ },
2615
+ {
2616
+ "name": "DisplayViewCorrectionStateSet",
2617
+ "source": "src/facade/display-view.ts",
2618
+ "kind": "type"
2619
+ },
2595
2620
  {
2596
2621
  "name": "DisplayViewResult",
2597
2622
  "source": "src/facade/simulate-capture.ts",
@@ -3882,6 +3907,11 @@
3882
3907
  "source": "src/equipment/iso-capabilities.ts",
3883
3908
  "kind": "type"
3884
3909
  },
3910
+ {
3911
+ "name": "JpegStripBits",
3912
+ "source": "src/capture/export-jpeg.ts",
3913
+ "kind": "type"
3914
+ },
3885
3915
  {
3886
3916
  "name": "LateralCaLensCorrection",
3887
3917
  "source": "src/output/lens-corrections.ts",
@@ -5522,6 +5552,16 @@
5522
5552
  "source": "src/output/print-jpeg.ts",
5523
5553
  "kind": "type"
5524
5554
  },
5555
+ {
5556
+ "name": "PrintJpegStrip",
5557
+ "source": "src/output/print-jpeg.ts",
5558
+ "kind": "type"
5559
+ },
5560
+ {
5561
+ "name": "PrintJpegStripInput",
5562
+ "source": "src/output/print-jpeg.ts",
5563
+ "kind": "type"
5564
+ },
5525
5565
  {
5526
5566
  "name": "PrintJpegTask",
5527
5567
  "source": "src/output/print-jpeg.ts",
@@ -7842,6 +7882,11 @@
7842
7882
  "source": "src/sensor/dark-current.ts",
7843
7883
  "kind": "type"
7844
7884
  },
7885
+ {
7886
+ "name": "serializeDisplayViewCorrectionStates",
7887
+ "source": "src/facade/display-view.ts",
7888
+ "kind": "runtime"
7889
+ },
7845
7890
  {
7846
7891
  "name": "serializeNativeRawDng",
7847
7892
  "source": "src/capture/native-raw-dng.ts",
@@ -7867,6 +7912,11 @@
7867
7912
  "source": "src/capture/simulated-capture.ts",
7868
7913
  "kind": "runtime"
7869
7914
  },
7915
+ {
7916
+ "name": "serializeSimulatedCaptureDngFromParts",
7917
+ "source": "src/facade/simulate-capture.ts",
7918
+ "kind": "runtime"
7919
+ },
7870
7920
  {
7871
7921
  "name": "setExposureCompensationOnMeterTarget",
7872
7922
  "source": "src/exposure/metering-target.ts",
@@ -7942,6 +7992,11 @@
7942
7992
  "source": "src/facade/simulate-capture.ts",
7943
7993
  "kind": "runtime"
7944
7994
  },
7995
+ {
7996
+ "name": "SIMULATE_CAPTURE_FAST_OPTIONS",
7997
+ "source": "src/facade/simulate-capture.ts",
7998
+ "kind": "runtime"
7999
+ },
7945
8000
  {
7946
8001
  "name": "SIMULATE_DISPLAY_VIEW_DEFAULT_SAMPLES_PER_AXIS",
7947
8002
  "source": "src/facade/simulate-capture.ts",
@@ -7977,11 +8032,31 @@
7977
8032
  "source": "src/facade/simulate-capture.ts",
7978
8033
  "kind": "type"
7979
8034
  },
8035
+ {
8036
+ "name": "SimulateCaptureDemosaicRecord",
8037
+ "source": "src/facade/simulate-capture.ts",
8038
+ "kind": "type"
8039
+ },
8040
+ {
8041
+ "name": "SimulateCaptureDngPart",
8042
+ "source": "src/facade/simulate-capture.ts",
8043
+ "kind": "type"
8044
+ },
7980
8045
  {
7981
8046
  "name": "SimulateCaptureExposureMode",
7982
8047
  "source": "src/facade/simulate-capture.ts",
7983
8048
  "kind": "type"
7984
8049
  },
8050
+ {
8051
+ "name": "SimulateCaptureFastOption",
8052
+ "source": "src/facade/simulate-capture.ts",
8053
+ "kind": "type"
8054
+ },
8055
+ {
8056
+ "name": "SimulateCaptureFastReport",
8057
+ "source": "src/facade/simulate-capture.ts",
8058
+ "kind": "type"
8059
+ },
7985
8060
  {
7986
8061
  "name": "SimulateCaptureLensDistortion",
7987
8062
  "source": "src/facade/simulate-capture.ts",
@@ -0,0 +1,50 @@
1
+ # facade/display-view.ts public contracts
2
+
3
+ The package and root API versions are recorded once in [Navigation](../API_REFERENCE.md) · [Developer guide](https://github.com/Photivra/photivra/blob/main/docs/DEVELOPERS.md). Generated signatures retain independent schema/model versions. Only the exports listed here are root-package contracts; module-local helpers are not supported deep imports.
4
+
5
+ ## bindDisplayViewCorrectionStates
6
+
7
+ 2.10 (#447): adds states another realm computed (an export, or `computeCorrectionStates` results) to this realm's cache and
8
+ returns the number of states added. For each entry, the grid keys its setup does not hold yet are appended; known ones are
9
+ left as they are (a state's values follow from its key, so they are equal). Every entry is checked before the cache is
10
+ touched: `key` a non-empty string, `gridKeys` an Int32Array of grid keys, `values` a Float64Array of 3 × `gridKeys.length`
11
+ finite numbers; otherwise it fails with an InvalidConfigurationError and the cache is unchanged. The cache's bounds apply as
12
+ in a render: an unknown key creates a setup and can evict the least recently used of the four kept, and a setup that
13
+ reaches 65 536 states is cleared before the next one is added. Import only keys a prepared view reported
14
+ (`correctionKey()`), so states a render here will read.
15
+
16
+ ```ts
17
+ export function bindDisplayViewCorrectionStates(entries: readonly DisplayViewCorrectionStateSet[]): number;
18
+ ```
19
+
20
+ This call is synchronous; any supplied provider must follow its explicit synchronous contract.
21
+
22
+ ## DisplayViewCorrectionStateKeys
23
+
24
+ 2.10 (#447): the grid keys of the states a region needs (`PreparedDisplayView.collectCorrectionStates`), ascending.
25
+
26
+ ```ts
27
+ export interface DisplayViewCorrectionStateKeys { key: string; gridKeys: Int32Array }
28
+ ```
29
+
30
+ ## DisplayViewCorrectionStateSet
31
+
32
+ 2.10 (#447): the AHD low-signal correction states of one setup, as a realm sends them to another. `key` is the setup's
33
+ correction key (`PreparedDisplayView.correctionKey()`), `gridKeys[i]` a state's grid key and `values[3·i …]` its three
34
+ values (28 bytes per state). Both arrays are transferable.
35
+
36
+ ```ts
37
+ export interface DisplayViewCorrectionStateSet { key: string; gridKeys: Int32Array; values: Float64Array }
38
+ ```
39
+
40
+ ## serializeDisplayViewCorrectionStates
41
+
42
+ 2.10 (#447): the AHD low-signal correction states this JavaScript realm holds (a Web Worker, a page), one entry per cached
43
+ setup, least recently used first, each state's grid key and three values copied out. Another realm adds them with
44
+ {@link bindDisplayViewCorrectionStates}; a render there then reads them instead of computing them, with the same bytes.
45
+
46
+ ```ts
47
+ export function serializeDisplayViewCorrectionStates(): DisplayViewCorrectionStateSet[];
48
+ ```
49
+
50
+ This call is synchronous; any supplied provider must follow its explicit synchronous contract.
@@ -175,7 +175,8 @@ export type SceneMaterialPoint = Omit<ScenePlanePoint, "wavelengthNanometers">;
175
175
 
176
176
  ## ScenePlanePoint
177
177
 
178
- A point of the scene plane passed to caller scene functions.
178
+ A point of the scene plane passed to caller scene functions: a new object for every call, which the engine does not read
179
+ after the call (not frozen: freezing cost about 40 % of a chart lookup).
179
180
 
180
181
  ```ts
181
182
  export interface ScenePlanePoint {
@@ -30,7 +30,9 @@ export interface DisplayViewResult {
30
30
  * milliseconds (diagnostic only; never affects the samples).
31
31
  */
32
32
  diagnostics: { sampleCount: number; siteEvaluations: number; sceneLookups: number; detailPixelCount: number; uniformPixelCount: number; ahdCorrectedPixelCount: number;
33
- timingsMs: { view: number; total: number } };
33
+ timingsMs: { view: number; total: number };
34
+ /** #395: exact or fast, as requested and as run (see {@link SimulateCaptureFastReport}). */
35
+ fast: SimulateCaptureFastReport };
34
36
  provenance: { scientificStatus: "approximation"; cameraPresetId: string; cameraPresetVersion: string; evidence: readonly EvidenceProvenance[];
35
37
  composedContracts: readonly string[]; limitations: readonly string[] };
36
38
  /** The capture request's assumptions, then `view.definition`, `view.estimation`, `view.noise`, `view.demosaic` and `view.source`. */
@@ -62,6 +64,23 @@ export interface IdealReferenceResult {
62
64
  }
63
65
  ```
64
66
 
67
+ ## serializeSimulatedCaptureDngFromParts
68
+
69
+ Writes the whole-frame DNG of `options` (`output.format: "dng"`, no `output.region`) from the packed RAW of region renders of
70
+ the same request (#391), so the RAW can render in parallel. A region render's native sites are the same sites as the whole
71
+ render's (each site's seeds come from its index in the whole raster), so the joined RAW is the whole render's RAW, and the
72
+ request is resolved and written exactly as `simulateCapture` writes its DNG: the bytes are the same. The parts must cover
73
+ every native site of the frame; where they overlap (each region carries the demosaic margin) their sites must agree. Region
74
+ renders of the request with any format serve (a region's own `output.format` does not change its RAW). A region cannot take
75
+ `output.whiteBalance: "auto"` or `output.dng`: render the parts with a fixed white balance (the RAW codes do not depend on the
76
+ white-balance intent) and pass the DNG's own options here, where auto white balance is measured over the joined frame.
77
+
78
+ ```ts
79
+ export async function serializeSimulatedCaptureDngFromParts(options: SimulateCaptureOptions, parts: readonly SimulateCaptureDngPart[]): Promise<NativeRawDng>;
80
+ ```
81
+
82
+ This call is asynchronous; byte hashing uses the caller runtime's Web Crypto.
83
+
65
84
  ## SIMULATE_CAPTURE_BOX_APERTURE_DEFAULT_SAMPLES
66
85
 
67
86
  Default midpoint samples per axis of `sensor.pixelAperture: "box"`.
@@ -101,6 +120,16 @@ Version of how the facade builds requests. It changes whenever the same input wo
101
120
  SIMULATE_CAPTURE_FACADE_VERSION = "1.0.0" as const
102
121
  ```
103
122
 
123
+ ## SIMULATE_CAPTURE_FAST_OPTIONS
124
+
125
+ The options that take the exact-or-fast flag (#395), in the path style of {@link SIMULATE_CAPTURE_COST_OPTIONS}: each accepts
126
+ the record form with `fast` and is reported in `diagnostics.fast.applied` when it runs. Every one of them takes the flag,
127
+ whether or not it has a fast method yet; one without runs exact and reports `false`.
128
+
129
+ ```ts
130
+ SIMULATE_CAPTURE_FAST_OPTIONS = Object.freeze(["lens.diffraction", "sensor.antiAliasing", "sensor.pixelAperture.box", "output.demosaic.ahd"] as const)
131
+ ```
132
+
104
133
  ## SIMULATE_DISPLAY_VIEW_DEFAULT_SAMPLES_PER_AXIS
105
134
 
106
135
  Default stratified samples per axis of a display view (`view.samplesPerAxis`): 16 × 16 sampled real pixels per display pixel with detail, 4 × 4 where it is uniform.
@@ -176,6 +205,25 @@ edge-directed method that reduces zipper and false colour on sharp edges.
176
205
  export type SimulateCaptureDemosaic = "bilinear" | "ahd";
177
206
  ```
178
207
 
208
+ ## SimulateCaptureDemosaicRecord
209
+
210
+ `output.demosaic` as a record (#395): the demosaic and its `fast` override (see {@link SimulateCaptureOptions.fast}).
211
+
212
+ ```ts
213
+ export interface SimulateCaptureDemosaicRecord { kind: SimulateCaptureDemosaic; fast?: boolean }
214
+ ```
215
+
216
+ ## SimulateCaptureDngPart
217
+
218
+ One part of a whole-frame DNG (#391): a region render's packed RAW and where its native sites lie (`result.raw`, `result.region`).
219
+
220
+ ```ts
221
+ export interface SimulateCaptureDngPart {
222
+ raw: Pick<NativeRawOutput, "codes" | "blackLevels" | "digitalSaturationCodes" | "saturationFlags" | "effectiveSaturationCodes">;
223
+ region: { nativeRect: { x: number; y: number; width: number; height: number } };
224
+ }
225
+ ```
226
+
179
227
  ## SimulateCaptureExposureMode
180
228
 
181
229
  The exact contract is declared below; use the domain guide for assumptions and staged integration.
@@ -184,6 +232,29 @@ The exact contract is declared below; use the domain guide for assumptions and s
184
232
  export type SimulateCaptureExposureMode = "manual" | "aperture-priority" | "shutter-priority" | "program";
185
233
  ```
186
234
 
235
+ ## SimulateCaptureFastOption
236
+
237
+ The exact contract is declared below; use the domain guide for assumptions and staged integration.
238
+
239
+ ```ts
240
+ export type SimulateCaptureFastOption = typeof SIMULATE_CAPTURE_FAST_OPTIONS[number];
241
+ ```
242
+
243
+ ## SimulateCaptureFastReport
244
+
245
+ `diagnostics.fast` of a capture or display view (#395): what was asked for and what ran.
246
+
247
+ ```ts
248
+ export interface SimulateCaptureFastReport {
249
+ /** True when `fast: true` was given at the top level or in any option's record. */
250
+ requested: boolean;
251
+ /** Each fast-capable option that ran, in {@link SIMULATE_CAPTURE_FAST_OPTIONS} order: true where its fast method ran. */
252
+ applied: Readonly<Partial<Record<SimulateCaptureFastOption, boolean>>>;
253
+ /** `engine-reference` when every option ran exact; `approximation-with-a-priori-bound` when any fast method ran (the WebGPU execution record's values). */
254
+ assurance: "engine-reference" | "approximation-with-a-priori-bound";
255
+ }
256
+ ```
257
+
187
258
  ## SimulateCaptureLensDistortion
188
259
 
189
260
  The exact contract is declared below; use the domain guide for assumptions and staged integration.
@@ -304,8 +375,8 @@ export interface SimulateCaptureOptions {
304
375
  * through `prepareRgbGamutMapping`: constant CIELAB L* and hue, chroma above 90 % of the boundary compressed.
305
376
  */
306
377
  gamutMapping?: "clip" | "perceptual";
307
- /** Default: the 1.0 facade's bilinear RGGB kernels. */
308
- demosaic?: SimulateCaptureDemosaic;
378
+ /** Default: the 1.0 facade's bilinear RGGB kernels. The record form (#395) adds a `fast` override. */
379
+ demosaic?: SimulateCaptureDemosaic | SimulateCaptureDemosaicRecord;
309
380
  /** Default "margin". */
310
381
  borderHandling?: SimulateCaptureBorderHandling;
311
382
  /** DNG only: optional capture-event time (YYYY-MM-DDTHH:mm:ss.sssZ); without it no date tags are written. */
@@ -363,6 +434,13 @@ export interface SimulateCaptureOptions {
363
434
  */
364
435
  pixelAperture?: SimulateCapturePixelAperture;
365
436
  };
437
+ /**
438
+ * Exact or fast (#395). `false` or omitted: every option runs its exact method, the engine reference. `true`: each option
439
+ * listed in {@link SIMULATE_CAPTURE_FAST_OPTIONS} runs its fast method where it has one, a measured, bounded approximation of
440
+ * the exact one; an option without a fast method runs exact. An option's own record `fast` overrides this for that option
441
+ * only. `diagnostics.fast` reports what was requested and what ran. No option has a fast method yet, so every result is exact.
442
+ */
443
+ fast?: boolean;
366
444
  /** Aborting cancels the running task; the promise rejects with a domain error and returns no partial image. */
367
445
  signal?: AbortSignal;
368
446
  /** Called after each committed RAW, development or JPEG tile. Cannot change output. */
@@ -380,10 +458,10 @@ export type SimulateCaptureOutputFormat = "srgb-8" | "srgb-16" | "jpeg" | "png"
380
458
 
381
459
  ## SimulateCapturePixelAperture
382
460
 
383
- Pixel aperture (2.3): centre sampling, or the square photosite integrated with an N × N midpoint rule.
461
+ Pixel aperture (2.3): centre sampling, or the square photosite integrated with an N × N midpoint rule; `fast` (#395) overrides the top-level flag.
384
462
 
385
463
  ```ts
386
- export type SimulateCapturePixelAperture = "point" | "box" | { kind: "box"; samplesPerAxis?: number };
464
+ export type SimulateCapturePixelAperture = "point" | "box" | { kind: "box"; samplesPerAxis?: number; fast?: boolean };
387
465
  ```
388
466
 
389
467
  ## SimulateCaptureProgress
@@ -504,6 +582,8 @@ export interface SimulateCaptureResult {
504
582
  * the user's device.
505
583
  */
506
584
  options: readonly SimulateCaptureCostOption[];
585
+ /** #395: exact or fast, as requested and as run (see {@link SimulateCaptureFastReport}). */
586
+ fast: SimulateCaptureFastReport;
507
587
  };
508
588
  provenance: {
509
589
  scientificStatus: "approximation";
@@ -531,9 +611,9 @@ frequency 1/(2·pitch)).
531
611
 
532
612
  ```ts
533
613
  export type SimulateCaptureSensorAntiAliasing =
534
- | { kind: "four-spot"; separationSites?: number }
535
- | { kind: "two-spot"; axis: "horizontal" | "vertical"; separationSites?: number }
536
- | { kind: "point-splitting"; components: readonly AntiAliasingPointSplitComponent[] };
614
+ | { kind: "four-spot"; separationSites?: number; fast?: boolean }
615
+ | { kind: "two-spot"; axis: "horizontal" | "vertical"; separationSites?: number; fast?: boolean }
616
+ | { kind: "point-splitting"; components: readonly AntiAliasingPointSplitComponent[]; fast?: boolean };
537
617
  ```
538
618
 
539
619
  ## SimulateCaptureWhiteBalance
@@ -11,8 +11,30 @@ setup: use it to render one view in many regions, for example on each worker of
11
11
 
12
12
  ```ts
13
13
  export interface PreparedDisplayView {
14
- /** Renders a view of the prepared request. Renders may run concurrently; they share no state except read-only setup. */
14
+ /**
15
+ * Renders a view of the prepared request. Renders may run concurrently; they share no state except read-only setup and the
16
+ * realm's AHD correction states, which never change a byte (docs/DISPLAY_VIEW.md, "AHD at low signal").
17
+ */
15
18
  render(view: SimulateDisplayViewOptions, renderOptions?: PreparedDisplayViewRenderOptions): Promise<DisplayViewResult>;
19
+ /**
20
+ * 2.10 (#447): the key of this request's AHD low-signal correction states, or null when the request has no AHD
21
+ * (`output.demosaic` other than "ahd"). Every input of the key is fixed when the request is prepared; two prepared views
22
+ * with the same key read the same states.
23
+ */
24
+ correctionKey(): string | null;
25
+ /**
26
+ * 2.10 (#447): the grid keys of every correction state a render of `view` reads (ascending, whether or not this realm holds
27
+ * them), found by a render of the view's region that records the states instead of computing them. No image is returned;
28
+ * it costs about one render whose states are all held. Fails with an InvalidConfigurationError when the request has no AHD.
29
+ */
30
+ collectCorrectionStates(view: SimulateDisplayViewOptions, renderOptions?: PreparedDisplayViewRenderOptions): Promise<DisplayViewCorrectionStateKeys>;
31
+ /**
32
+ * 2.10 (#447): computes the correction states of `gridKeys` (from `collectCorrectionStates`, or a share of them) as a render
33
+ * would, stores them in this realm's cache and returns their values, for {@link bindDisplayViewCorrectionStates} in
34
+ * another realm. States this realm holds are read, not computed. Yields and honours `signal` like `render`. Fails with an
35
+ * InvalidConfigurationError when the request has no AHD or a grid key is out of range.
36
+ */
37
+ computeCorrectionStates(gridKeys: Int32Array, renderOptions?: PreparedDisplayViewRenderOptions): Promise<DisplayViewCorrectionStateSet>;
16
38
  }
17
39
  ```
18
40
 
@@ -51,6 +51,11 @@ export interface ImageKernelExecution {
51
51
  fallbackReason: ImageKernelFallbackReason | null;
52
52
  arithmetic: "float64" | "float32";
53
53
  assurance: "engine-reference" | "approximation-with-a-priori-bound";
54
+ /**
55
+ * The exact-or-fast flag in the facade's words (#395): false for `cpu-reference` (the engine reference), true for
56
+ * `webgpu-f32` (a bounded approximation). `backend` still names where the work ran.
57
+ */
58
+ fast: boolean;
54
59
  /**
55
60
  * True only for `cpu-reference`. Bit-exact identities (SHA-256 artifact bindings, byte-parity fixtures,
56
61
  * cross-engine determinism hashes) are CPU-only and must never be computed from a `webgpu-f32` result.
@@ -72,7 +72,7 @@ export interface DiffractionLimitedPsfProfile {
72
72
  /** Largest truncated energy over the wavelengths. */
73
73
  maximumTruncatedEnergyFraction: number;
74
74
  /**
75
- * How the cells were integrated. 2.5: wavelengths with β = p·f_c ≥ 192 (cells far larger than the pattern) use the real-space
75
+ * How the cells were integrated. 2.5: wavelengths with β = p·f_c ≥ `AIRY_REAL_SPACE_MINIMUM_BETA` (192 in 2.5–2.8.1, then 32) use the real-space
76
76
  * encircled-energy method; `realSpaceWavelengthsNm` lists them, and `method` is `"frequency-domain-and-real-space"` when both ran.
77
77
  * `maximumRadialNodes` counts frequency-domain radial nodes (0 when only the real-space method ran); `maximumAngularNodes` the
78
78
  * angular nodes of either method (for the real-space method, all θ nodes of one wavelength).
@@ -2,6 +2,31 @@
2
2
 
3
3
  The package and root API versions are recorded once in [Navigation](../API_REFERENCE.md) · [Developer guide](https://github.com/Photivra/photivra/blob/main/docs/DEVELOPERS.md). Generated signatures retain independent schema/model versions. Only the exports listed here are root-package contracts; module-local helpers are not supported deep imports.
4
4
 
5
+ ## composePrintJpegStrips
6
+
7
+ Joins the strips of a 1:1 Print JPEG (`createPrintJpegStrip`), covering the image top to bottom in order, into its file:
8
+ the same bytes `createPrintJpegTask` writes for the plan and the same source pixels. No source is read, so the output's
9
+ `providerReads` is 0.
10
+
11
+ ```ts
12
+ export function composePrintJpegStrips(input:PrintJpegInput,strips:readonly PrintJpegStrip[]):PrintJpegOutput;
13
+ ```
14
+
15
+ This call is synchronous; any supplied provider must follow its explicit synchronous contract.
16
+
17
+ ## createPrintJpegStrip
18
+
19
+ Codes rows of a 1:1 Print JPEG (output size equal to the crop) apart from the rows before them, so a large file's strips
20
+ can be written on separate workers. `strip.y` is a multiple of 8 and `strip.height` a multiple of 8 unless the strip ends at
21
+ the image's bottom; `strip.samples` are those crop rows' 8-bit sRGB RGB, crop-wide. `composePrintJpegStrips` joins strips that
22
+ cover the image, in order, into the bytes `createPrintJpegTask` writes for the same plan and source pixels.
23
+
24
+ ```ts
25
+ export function createPrintJpegStrip(input:PrintJpegInput,strip:PrintJpegStripInput):PrintJpegStrip;
26
+ ```
27
+
28
+ This call is synchronous; any supplied provider must follow its explicit synchronous contract.
29
+
5
30
  ## createPrintJpegTask
6
31
 
7
32
  Creates the export task. Filtering weights each source pixel by its exact area overlap, after decoding the declared 8-bit SDR source.
@@ -123,6 +148,22 @@ export interface PrintJpegSource {
123
148
  }
124
149
  ```
125
150
 
151
+ ## PrintJpegStrip
152
+
153
+ A strip of a 1:1 Print JPEG's coded blocks (`createPrintJpegStrip`), for `composePrintJpegStrips`.
154
+
155
+ ```ts
156
+ export interface PrintJpegStrip {readonly y:number;readonly height:number;readonly width:number;readonly quantizationStep:number;readonly bits:JpegStripBits}
157
+ ```
158
+
159
+ ## PrintJpegStripInput
160
+
161
+ Rows of a 1:1 Print JPEG for `createPrintJpegStrip`: crop rows `y` to `y + height` (relative to the crop), crop-wide RGB.
162
+
163
+ ```ts
164
+ export interface PrintJpegStripInput {y:number;height:number;samples:Uint8Array}
165
+ ```
166
+
126
167
  ## PrintJpegTask
127
168
 
128
169
  The exact contract is declared below; use the domain guide for assumptions and staged integration.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@photivra/engine",
3
- "version": "2.8.1",
3
+ "version": "2.10.0",
4
4
  "private": false,
5
5
  "description": "Scientific TypeScript engine for camera optics, exposure, focus, motion, sensor sampling, diffraction, and digital imaging calculations.",
6
6
  "type": "module",