@photivra/engine 2.15.0 → 2.16.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 (67) hide show
  1. package/CHANGELOG.md +47 -1
  2. package/CITATION.cff +1 -1
  3. package/README.md +6 -6
  4. package/SECURITY.md +1 -1
  5. package/THIRD_PARTY.md +14 -0
  6. package/dist/capture/native-environment-lean.js +1 -1
  7. package/dist/capture/native-environment-lean.js.map +1 -1
  8. package/dist/capture/native-raw-development.d.ts +15 -0
  9. package/dist/capture/native-raw-development.js +12 -6
  10. package/dist/capture/native-raw-development.js.map +1 -1
  11. package/dist/capture/raw-development-kernel.d.ts +10 -3
  12. package/dist/capture/raw-development-kernel.js +25 -6
  13. package/dist/capture/raw-development-kernel.js.map +1 -1
  14. package/dist/core/internal-radiance-evaluator.d.ts +5 -2
  15. package/dist/core/internal-radiance-evaluator.js.map +1 -1
  16. package/dist/core/version.d.ts +1 -1
  17. package/dist/core/version.js +1 -1
  18. package/dist/core/version.js.map +1 -1
  19. package/dist/facade/defocus-image-space-provider.d.ts +109 -0
  20. package/dist/facade/defocus-image-space-provider.js +359 -0
  21. package/dist/facade/defocus-image-space-provider.js.map +1 -0
  22. package/dist/facade/diffraction-provider.d.ts +16 -0
  23. package/dist/facade/diffraction-provider.js +3 -3
  24. package/dist/facade/diffraction-provider.js.map +1 -1
  25. package/dist/facade/display-view.d.ts +21 -0
  26. package/dist/facade/display-view.js +66 -8
  27. package/dist/facade/display-view.js.map +1 -1
  28. package/dist/facade/simulate-capture.d.ts +111 -2
  29. package/dist/facade/simulate-capture.js +220 -44
  30. package/dist/facade/simulate-capture.js.map +1 -1
  31. package/dist/facade/simulate-display-view.js +7 -3
  32. package/dist/facade/simulate-display-view.js.map +1 -1
  33. package/dist/gpu/display-view-quality-gpu.d.ts +2 -0
  34. package/dist/gpu/display-view-quality-gpu.js +57 -13
  35. package/dist/gpu/display-view-quality-gpu.js.map +1 -1
  36. package/dist/gpu/display-view-speed-gpu.d.ts +2 -0
  37. package/dist/gpu/display-view-speed-gpu.js +45 -16
  38. package/dist/gpu/display-view-speed-gpu.js.map +1 -1
  39. package/dist/gpu/image-kernel-accelerator.js +3 -0
  40. package/dist/gpu/image-kernel-accelerator.js.map +1 -1
  41. package/dist/gpu/wgsl-display-view-quality-kernel.d.ts +4 -0
  42. package/dist/gpu/wgsl-display-view-quality-kernel.js +53 -2
  43. package/dist/gpu/wgsl-display-view-quality-kernel.js.map +1 -1
  44. package/dist/gpu/wgsl-display-view-speed-kernel.d.ts +15 -0
  45. package/dist/gpu/wgsl-display-view-speed-kernel.js +42 -1
  46. package/dist/gpu/wgsl-display-view-speed-kernel.js.map +1 -1
  47. package/dist/index.d.ts +2 -2
  48. package/dist/index.js +1 -1
  49. package/dist/index.js.map +1 -1
  50. package/dist/optics/defocus-disc-kernel.d.ts +3 -2
  51. package/dist/optics/defocus-disc-kernel.js +3 -2
  52. package/dist/optics/defocus-disc-kernel.js.map +1 -1
  53. package/dist/optics/sensor-environment-query.d.ts +31 -0
  54. package/dist/optics/sensor-environment-query.js +45 -13
  55. package/dist/optics/sensor-environment-query.js.map +1 -1
  56. package/dist/processing/development-detail.d.ts +132 -0
  57. package/dist/processing/development-detail.js +325 -0
  58. package/dist/processing/development-detail.js.map +1 -0
  59. package/docs/API_REFERENCE.md +6 -1
  60. package/docs/QUICK_START.md +190 -12
  61. package/docs/USAGE.md +14 -3
  62. package/docs/api/capture-native-raw-development.md +16 -0
  63. package/docs/api/core-version.md +1 -1
  64. package/docs/api/exports.json +27 -2
  65. package/docs/api/facade-simulate-capture.md +128 -3
  66. package/docs/api/optics-defocus-disc-kernel.md +3 -2
  67. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,6 +1,52 @@
1
1
  # Changelog
2
2
 
3
- ## 2.15.0 — unreleased (prepared November 7, 2026)
3
+ ## 2.16.0 — unreleased (prepared November 7, 2026)
4
+
5
+ A minor release of five issues: the display view's highlight count (#533), a development exposure push (#556), sharpening and diffraction correction (#552), `lens.defocus` with projection, distortion, vignetting and camera rotation (#548), and docs (#400). Captures, RAW codes, files and assumptions are the same as 2.15.0 for every request that 2.15.0 renders and that uses none of the new options; the display view's result gains one diagnostic.
6
+ - **New:** `DisplayViewResult.diagnostics.highlight` (#533); `output.exposureCompensationStops` (#556); `output.sharpening` and `output.diffractionCorrection` (#552); out-of-focus `lens.defocus` with `lens.projection`, `lens.distortion`, `lens.vignetting` or camera rotation under a global shutter, in image space (#548).
7
+ - **Docs:** 2.15.0 recorded as published; display-view cost rows re-measured (#400).
8
+ - **Root exports:** 5 new, 1,805 → 1,810 (runtime exports 613 → 614): the type `DisplayViewHighlightDiagnostics` (#533); `NATIVE_RAW_DEVELOPMENT_EXPOSURE_RANGE_STOPS` (#556); the types `SimulateCaptureSharpening`, `SimulateCaptureDiffractionCorrection` and `SimulateCaptureDetailDiagnostics` (#552). #548 adds none.
9
+ - **Why minor:** new root exports and new opt-in options; requests that 2.15.0 refused (#548) now render. Nothing is removed or changed incompatibly, and no default changes.
10
+ - **GPU:** #533 changes `src/gpu` (the display view's Speed and Quality kernels); the hardware gates passed on an Apple M5 Pro (docs/WEBGPU_DEVICES.md, docs/RELEASE_2_16_0.md).
11
+
12
+ - **New: the display view reports its highlight count (#533, for Photivra/apps#158, #223 and #240).** `simulateDisplayView` and `prepareDisplayView(...).render` return `diagnostics.highlight: { saturatedPixelCount, statistic: "expected-value" }`: the real capture's saturated output pixels (at least one clipped contributing RAW site) in the rendered region. `simulateCapture` counts these on its one noise sample as `diagnostics.highlight.saturatedPixelCount`. Until now an app could only show the count of a small quick capture.
13
+ - **Definition:** the expected value over the noise, Σ_j w_j·(1 − Π_k (1 − p_k)). j runs over the region's real pixels, with w_j the part of each inside the region. p_k is the probability that a site pixel j's demosaic reads is at or above the white level, under the view's own site model (normal, with exact expected electrons and shot, dark, read and ADC noise). It is estimated like the expected value (exact area mean, or stratified), so it is a real number. It equals the capture's count exactly where every site is more than 8.5 σ from white. With AHD it counts the 25–29 sites the view's candidates read, not the capture's full 7 × 7 square: an approximation that can be lower at clipped corners and specks (docs/DISPLAY_VIEW.md, "Highlight count").
14
+ - **Additive over parts:** a display pixel's share is the same in every region, so the counts of regions that tile a view sum to the whole view's.
15
+ - **No change to the view:** samples, expected values, noise, every other diagnostic and the assumptions are unchanged. The hash locks of `test/display-view.test.ts`, `display-view-pixel-memo.test.ts` and `display-view-speed-material.test.ts` leave the new field out and keep their recorded hashes.
16
+ - **GPU (`src/gpu`):** the Speed and Quality kernels mark each site certainly unclipped, certainly clipped (beyond twice the reference's 8.5 σ after their error bounds) or undecided. They return a bit per real pixel that is certainly saturated, and flag a display pixel with an undecided real pixel. The host forms the count with the reference's arithmetic and takes an undecided pixel's count from a one-pixel reference render; that pixel's samples stay the GPU's. The count is therefore the reference's bit for bit and `diagnosticsExact` stays true. The run-time self-checks now also compare the count. The flag output grows from one to three words per display pixel (Speed band: 48 → 56 bytes per display pixel).
17
+ - **Cost:** on the app request (Apple M5 Pro, Node 24.19.0, load average about 6), part medians were 38–41 ms against 2.15.0's 39 ms (Speed) and 74–79 ms against 74–78 ms (Quality). The extrapolated whole view was 0–4 % slower.
18
+ - **Tests:** `test/display-view-highlight.test.ts`: the capture's count reproduced exactly (bilinear and AHD, three exact views); the mean of 8 noise seeds near white; additivity per display pixel and over regions; a prepared view; the GPU float32 emulation (Speed and Quality, decided and undecided pixels) against the reference bit for bit.
19
+ - **API:** one new root export, the type `DisplayViewHighlightDiagnostics`; `DisplayViewResult.diagnostics` gains `highlight`. Nothing is removed or changed.
20
+ - **Docs (#400):**
21
+ - 2.15.0 recorded as published (`README.md`, `SECURITY.md`, `docs/DEVELOPERS.md`, its record, this changelog).
22
+ - App-request display-view rows re-measured on 2.15.0 (docs/DISPLAY_VIEW.md, "A device-pixel view rendered in parts"; docs/QUICK_START.md, "Cost").
23
+ - A stale "no GPU display-view renderer exists yet" removed from docs/USAGE.md.
24
+ - **New: development exposure push, `output.exposureCompensationStops` (#556).** Underexpose, then brighten the same RAW in development, like a raw editor's exposure slider, so dynamic range, read noise and bit depth become visible in recovered shadows (for the app's "Brighten in development" control).
25
+ - **Model:** a linear gain 2^stops, −2 to +5 stops (whole or fractional), on each site's black-subtracted, white-normalized value (code − black)/(white − black), before demosaic, white balance, the highlight mode, colour, the tone curve and encoding. No photons are added: raised shadows show the RAW's own shot, dark and read noise and its ADC quantization, scaled by the gain. A site pushed to or past white clips at white and counts in `diagnostics.highlight.saturatedPixelCount`; `highlightMode: "reconstruct"` rolls it off. The SDR profile's `renderingExposureEv` is a different, post-clip scale and stays 0.
26
+ - **Where:** the developed samples (`image.samples`, also with format `"dng"`), JPEG, PNG (every encoding), bilinear and AHD, `simulateIdealReference` and `simulateDisplayView` (its per-site readout moments are scaled by G and G²) honour it. The RAW codes, `result.raw` and the DNG stay the unmodified RAW; `output.whiteBalance: "auto"` is measured on the unpushed RAW.
27
+ - **Reported** in the assumption `output.exposureCompensationStops` (`{ stops, gain, appliedTo, dng }`) when the option is given. A non-zero push is carried in `request.development` and replayed by `createNativeRawDevelopmentTask`, which gains the same optional `exposureCompensationStops`. The WebGPU accelerator's `developNativeRaw` rejects a non-zero push with `unsupported-on-backend`.
28
+ - **Tests** (`test/simulate-capture-exposure-push.test.ts`): a +3-stop push of a 3-stop underexposure matches the normal exposure's mean within noise (0.4 % here, with about √8 times its noise) and keeps the underexposed RAW codes; after a +5-stop push of a 5-stop underexposure, a sensor with 20 e⁻ read noise is noisier than one with 1 e⁻; a pushed noise-free gradient shows 12-bit quantization plateaus and none at 14 or 16 bits; pushed highlights clip, and reconstruct rolls them off; JPEG, PNG (sRGB and Display P3), AHD, the DNG format's samples, the ideal reference and the display view are brighter by the gain, while the DNG bytes are unchanged; validation in the facade and the development task.
29
+ - **Compatibility:** one new root export, `NATIVE_RAW_DEVELOPMENT_EXPOSURE_RANGE_STOPS` (`{ minimum: -2, maximum: 5 }`); `NativeRawDevelopmentInput` and the facade's `output` gain an optional field. Without the option (or with 0), captures, RAW codes, files, requests and assumptions are byte-identical. Docs: `docs/QUICK_START.md` ("Development exposure push"), `docs/USAGE.md`, `docs/DISPLAY_VIEW.md`, `docs/WEBGPU.md`; API reference regenerated.
30
+ - **New: sharpening and diffraction correction, with honest trade-offs (#552).** Two opt-in development stages of `simulateCapture`, off by default, for the app's per-correction toggles (on for the JPEG, off in the DNG).
31
+ - **`output.diffractionCorrection`** `{ wavelengthNm?, regularization?, maximumGain?, kernelRadiusPixels? }`, which needs `lens.diffraction`: a bounded, regularized deconvolution of the developed linear colour with the diffraction OTF of the ideal circular pupil at the capture's working f-number, H(s) = (2/π)(acos s − s√(1 − s²)), s = f/f_c, f_c = 1/(λ·N_w) (MIT OCW 2.71). The gain min(maximumGain, H(1 + α)/(H² + α)) is Wiener deconvolution with a constant noise-to-signal ratio α, built as a tapered (2R + 1)² filter with unit DC gain; its response is measured and reported. Defaults: 550 nm, α = 0.01, gain bound 4, R = the Airy third dark ring (1–16 pixels). Frequencies beyond the cut-off never reached the sensor and stay lost: the design gain there is 0, and the assumption says `recoversBeyondCutoff: false`.
32
+ - **`output.sharpening`** `{ radiusPixels?, amount?, threshold? }`: an unsharp mask y = x + amount·(x − G_σ∗x) where |x − G_σ∗x| ≥ threshold (as in GIMP and ImageMagick), per channel on the sRGB-encoded values after the tone curve and gamut handling, before quantization. Defaults: σ = 1 pixel, amount 0.5, threshold 0. sRGB output only.
33
+ - **Order** (the `output.developmentOrder` assumption): demosaic → colour and white balance → diffraction correction → rendering exposure and tone curve → gamut and transfer → unsharp mask → quantization. With a development exposure push (#556) the order starts with it, on the RAW.
34
+ - **Reported** in the `output.diffractionCorrection` and `output.sharpening` assumptions (settings, defaults used, cut-off, measured filter response), in `diagnostics.detail` (ringing below black and above white; sharpened, held and clipped samples; the largest halo increase and decrease) and in `diagnostics.options`.
35
+ - **Limits:** neither combines with `output.region`; neither is offered by `simulateDisplayView` or `simulateIdealReference`. Both hold the whole developed image, as AHD does. The DNG is never modified: with both stages a DNG request writes the same DNG bytes.
36
+ - **Trade-off tests** (`test/lens-correction-tradeoffs.test.ts`, `test/development-detail.test.ts`; numbers in docs/DIGITAL_OPTICS_FOUNDATION.md): distortion correction stretches the corners, so a 0.25 cycle/pixel source grating lands at 0.186 cycles per output pixel in the corner (0.250 at the centre) with lower modulation; vignetting correction raises corner noise by the gain (×1.29 over the corner patch) and leaves the SNR unchanged; lateral CA correction registers the edges (to 0.005 px) but red and blue stay 1.8–2.2 px wider than green (longitudinal CA); sharpening overshoots and undershoots a step by 0.367·amount·h at σ = 1.5, as predicted; diffraction correction leaves a grating beyond the cut-off at exactly zero and restores one at 0.55·f_c only from 0.34 to 0.82 of its contrast.
37
+ - **Compatibility:** 3 new root type exports (`SimulateCaptureSharpening`, `SimulateCaptureDiffractionCorrection`, `SimulateCaptureDetailDiagnostics`); `SIMULATE_CAPTURE_COST_OPTIONS` gains `output.diffractionCorrection` and `output.sharpening`; `diagnostics.detail` is new and present only when a stage ran. Requests without the two options give the same captures, RAW codes, files and assumptions. No version bump here.
38
+ - **`lens.defocus` out of focus with `lens.projection`, `lens.distortion`, `lens.vignetting` or camera rotation (#548, for Photivra/apps#252, Photivra/apps#230, #549 and #553).** Until now `simulateCapture` refused these combinations, because the defocus disc ran as an object-space convolution on the plane lattice the sites see, and each of these options moves the sites' scene points off that lattice (or, for vignetting, gives each point its own radiance-to-irradiance factor).
39
+ - **Now supported, in image space:** for each spectral node (and, with rotation, each temporal midpoint, in that instant's camera frame) the sensor-plane irradiance before any PSF is formed on the sensor's site lattice through the inverse distortion, the declared projection, the rotation and the field factor (vignetting × distortion area factor), convolved with the disc, and each site query is answered with the result divided by the queried point's field factor (`src/facade/defocus-image-space-provider.ts`). It is the environment PSF quadrature with the disc as a sampled PSF, summed in another order. With `lens.diffraction`, each node's diffraction kernel convolved with the disc is the kernel.
40
+ - **Decisions per combination:** vignetting: supported, no approximation beyond the disc (field throughput applied where the light enters, before the blur, as for any environment PSF; no commutation error). Projection and distortion: supported; the disc has the same diameter at every field point, centred on each chief ray (exact for the rectilinear distortion-free thin lens), and the `lens.defocus` assumption records `fieldVariation` bounds for the request (`distortionMaximumLocalScaleDeviation`, `projectionMaximumDiameterChangeSites`). Camera rotation with a global shutter: supported, per temporal midpoint; `rotationMaximumDiameterChangeSites` bounds the tilt of the plane under pitch and yaw (0 for roll). Still refused, each with a message that says why: rotation with `motion.shutter: "rolling"` (every row has its own instants), and a vignetting or distortion profile that does not reach the frame's corners plus the kernel radius. `lens.pupilSampling` stays refused with projection and distortion (a traced reference needs the 3D ray provider, #358). `simulateDisplayView` and `prepareDisplayView` refuse projection, distortion, vignetting and rotation in any focus, as before, so they share no #548 refusal and do not change.
41
+ - **Accuracy:** RAW codes equal those of the same request with the disc passed as `lens.psf` (the per-tap environment quadrature), for each combination, all together, and with `lens.diffraction`; expected electrons agree to 1e-12 per site. The identity mapping (`lens.projection: "rectilinear"`) gives the object-space path's RAW. Against a fine-grid oracle (the disc sampled 5 times finer, 3 times with the 4 × 4 box), the lattice-sampling error is that of the rectilinear capture: on a 16 × 12 slanted edge with a 5.95-site disc, max / RMS relative difference in expected electrons 0.110 / 0.026 (point) and 0.032 / 0.006 (4 × 4 box) for the rectilinear capture, vignetting, a fisheye and radial distortion alike, and lower with rotation (0.069 / 0.016 and 0.017 / 0.003). The field-variation bounds on the app's full frame (50 mm f/2.8, focus 1 m, a 5.0-site disc): equisolid fisheye 0.43 sites, radial k1 −0.1 a local scale 0.28 off 1 at the corners, typical shake rotation 8e-4 sites.
42
+ - **Performance** (app defaults: Speed preset, 384 × 256 whole frame at 93.75 µm with the 2 × 2 box, Siemens star, 50 mm f/2.8, subject 2 m, focus 1 m, disc 5.0 sites, R 3; Apple M5 Pro, Node.js 24.19.0, medians of 3 on a shared machine): vignetting 1.0 s in focus, 1.6 s out of focus (R 3) and 1.7 s (R 6); rotation 2.1–3.3 s in focus, 7.5–8.8 s (R 3) and 6.5 s (R 6); fisheye 11 s, 15–19 s; radial distortion 18–21 s, 19–20 s; all four together 94–97 s, 101–124 s. Out of focus without these options stays on the object-space path (0.05–0.07 s). Table in docs/QUICK_START.md ("Optics"). Without rotation and lens mapping, and with rotation, the lean executor reads the image-space values by lattice key (`InternalRadianceAccess.keyOfDirection` now takes the request time; internal).
43
+ - **API:** `request.diffraction.execution` gains `"image-space-convolution"`; the `lens.defocus` assumption gains `execution: "image-space-convolution"`, `imageSpaceOptions` and `fieldVariation`. No new root export. Every request that 2.15.0 renders gives the same RAW codes, files and assumptions (in focus, and out of focus without these options).
44
+ - **Tests:** `test/simulate-capture-defocus-optics.test.ts` (each combination against the environment quadrature, 1e-12 per site, diffraction, identity mapping, in focus byte-identical, bounds, refusals, regions with the Quality filters); `test/simulate-capture-defocus-render.test.ts` updated. Docs: `docs/QUICK_START.md` ("Optics", "Modelling choices").
45
+
46
+ - **Integration of #556 with #552 and #548 (this release).** With `output.sharpening` or `output.diffractionCorrection` the capture develops through `developLinearSrgb` (the native development's own linear operator) instead of `createNativeRawDevelopmentTask`; the exposure push applies on both paths, on the black-subtracted, white-normalized RAW, so pushed sites clip at white before the diffraction correction. `output.developmentOrder` lists `"development exposure push (RAW)"` first when a push is given.
47
+ - **Tests** (`test/development-detail-integration.test.ts`): a +2-stop push with an unsharp mask that changes nothing is byte-identical to the push alone (the two paths); a +2-stop push of a quarter exposure (1/32 s), sharpened and diffraction-corrected, matches the full exposure (1/8 s) column by column within noise, with the same clipped highlight (saturated counts within 5 %) and the unclipped quarter-exposure RAW; image-space `lens.defocus` (vignetting, distortion and diffraction) with each of `output.exposureCompensationStops`, `output.sharpening` and `output.diffractionCorrection`, and all three: the RAW is unchanged, the push doubles linear light per sample at +1 stop, the mask adds halos, the correction raises contrast and reports that defocus is not inverted.
48
+
49
+ ## 2.15.0 — published November 7, 2026
4
50
 
5
51
  A minor release: `lens.diffraction` at every spectral step (a fix for the app's color chart), and the defocus size limit with a pre-check as root exports (#532, #560). Captures, RAW codes, files and assumptions are the same as 2.14.1 for every request that 2.14.1 renders.
6
52
  - **Fixed:** `lens.diffraction` with `sensor.spectralStepNanometers` under 6.25 nm, for example the color chart at the recommended 5 nm.
package/CITATION.cff CHANGED
@@ -9,7 +9,7 @@ abstract: "An open TypeScript engine for the science and mathematics of photogra
9
9
  repository-code: "https://github.com/photivra/photivra"
10
10
  url: "https://photivra.com"
11
11
  license: "Apache-2.0"
12
- version: "2.15.0"
12
+ version: "2.16.0"
13
13
  keywords:
14
14
  - photography
15
15
  - camera optics
package/README.md CHANGED
@@ -32,10 +32,10 @@ The source is licensed under Apache-2.0.
32
32
 
33
33
  ## Quick start
34
34
 
35
- Install the published ESM package below. The [2.15.0 release record](https://github.com/Photivra/photivra/blob/main/docs/RELEASE_2_15_0.md) and [changelog](https://github.com/Photivra/photivra/blob/main/CHANGELOG.md) describe the current release:
35
+ Install the published ESM package below. The [2.16.0 release record](https://github.com/Photivra/photivra/blob/main/docs/RELEASE_2_16_0.md) and [changelog](https://github.com/Photivra/photivra/blob/main/CHANGELOG.md) describe the current release:
36
36
 
37
37
  ```sh
38
- npm install @photivra/engine@2.15.0
38
+ npm install @photivra/engine@2.16.0
39
39
  ```
40
40
 
41
41
  Simulate a photograph of a test chart with a generic camera, then read the developed sRGB pixels:
@@ -75,7 +75,7 @@ console.log(horizontal.provenance);
75
75
 
76
76
  Start with the [Developer Guide](https://github.com/Photivra/photivra/blob/main/docs/DEVELOPERS.md). Then use the [Usage Guide](https://github.com/Photivra/photivra/blob/main/docs/USAGE.md) and the [API reference](https://github.com/Photivra/photivra/blob/main/docs/API_REFERENCE.md), which is generated from the package exports.
77
77
 
78
- Repository `main` may describe newer, unreleased work. For the stable contract, use the docs bundled in the published package, or the exact `v2.15.0` tag.
78
+ Repository `main` may describe newer, unreleased work. For the stable contract, use the docs bundled in the published package, or the exact `v2.16.0` tag.
79
79
 
80
80
  ## Documentation
81
81
 
@@ -363,13 +363,13 @@ The application can then use those results, while the photography calculations t
363
363
 
364
364
  ## Status
365
365
 
366
- - Repository package version: `2.15.0` (release candidate prepared November 7, 2026; publication is separate)
367
- - Engine API contract: `2.15.0`
366
+ - Repository package version: `2.16.0` (release candidate prepared November 7, 2026; publication is separate)
367
+ - Engine API contract: `2.16.0`
368
368
  - Versioned generic tier reference assets: [GENERIC_TIER_PRESETS.md](https://github.com/Photivra/photivra/blob/main/docs/GENERIC_TIER_PRESETS.md).
369
369
  - Composed POC simulation API contract: `1.0.0`
370
370
  - Stability: 1.0 root-package compatibility policy; bounded scientific models and independent POC contract
371
371
 
372
- From 1.0.1, the package version and `ENGINE_API_VERSION` are equal. New captures/plans record 2.15.0; existing archives keep their original creator identities. Schema, model and POC contract versions stay independent.
372
+ From 1.0.1, the package version and `ENGINE_API_VERSION` are equal. New captures/plans record 2.16.0; existing archives keep their original creator identities. Schema, model and POC contract versions stay independent.
373
373
 
374
374
  A breaking change to the root contracts now needs a new package major version. Additive and corrective changes follow [compatibility and migration](https://github.com/Photivra/photivra/blob/main/docs/RELEASE_1_3_0.md). A 1.0 package is not a claim of calibrated physical accuracy.
375
375
 
package/SECURITY.md CHANGED
@@ -4,7 +4,7 @@ Please do not open a public issue that contains vulnerability details.
4
4
 
5
5
  ## Supported versions
6
6
 
7
- The published 1.0.0 release started the 1.x root-package compatibility line, and 2.0.0 started the 2.x line. The current release is **2.14.1**, published on November 3, 2026 (see [CHANGELOG.md](CHANGELOG.md)).
7
+ The published 1.0.0 release started the 1.x root-package compatibility line, and 2.0.0 started the 2.x line. The current release is **2.15.0**, published on November 7, 2026 (see [CHANGELOG.md](CHANGELOG.md)).
8
8
 
9
9
  Security fixes target the current maintained development and release line. Do not assume that older 1.x releases, snapshots or pre-1.0 releases will receive fixes.
10
10
 
package/THIRD_PARTY.md CHANGED
@@ -203,6 +203,20 @@ factual constants only, independently implemented; no code or data copied):
203
203
  - Bradford cone matrix (Lam 1985), already used by the ICC serializer, now also
204
204
  for ForwardMatrix1.
205
205
 
206
+ Development detail references (#552; published equations and documented
207
+ behaviour only, independently implemented in `src/processing/development-detail.ts`;
208
+ no code, table or data copied):
209
+
210
+ - Circular-pupil incoherent OTF and its cut-off 1/(λN): MIT OpenCourseWare 2.71
211
+ *Optics* (Spring 2009), lectures 21–22 (already cited for `lens.diffraction`).
212
+ - Wiener deconvolution with a constant noise-to-signal ratio: the scikit-image
213
+ `skimage.restoration.wiener` documentation (BSD-3-Clause project; documentation
214
+ read, no code used) and Orieux, Giovannelli & Rodet (2010), JOSA A 27(7).
215
+ - Unsharp mask (radius, amount, threshold): the GIMP 2.10 manual, "Unsharp Mask",
216
+ and ImageMagick's `-unsharp` option documentation (documented behaviour only).
217
+ - Patent status: no public-record patent check has been recorded for these two
218
+ long-published methods; it is left for the owner's review with the others.
219
+
206
220
  The DNG patent-license notice appears in `NOTICE` and exporter source. These
207
221
  references do not authorize copying SDK code or scientific datasets. Human
208
222
  provenance/license review remains required before release.
@@ -287,7 +287,7 @@ function geometry(t, k, frame, environment, index, access) {
287
287
  return undefined;
288
288
  // #394: the key the evaluator would read for this request (undefined where it would throw: the full path decides).
289
289
  if (access !== undefined) {
290
- key = access.keyOfDirection(direction);
290
+ key = access.keyOfDirection(direction, time);
291
291
  if (key === undefined)
292
292
  return undefined;
293
293
  }