@photivra/engine 2.5.0 → 2.6.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.
- package/CHANGELOG.md +38 -1
- package/CITATION.cff +1 -1
- package/README.md +6 -6
- package/SECURITY.md +1 -1
- package/THIRD_PARTY.md +1 -0
- package/dist/capture/native-environment-lean.d.ts +1 -1
- package/dist/capture/native-environment-lean.js +17 -3
- package/dist/capture/native-environment-lean.js.map +1 -1
- package/dist/capture/native-environment-raw.d.ts +0 -4
- package/dist/capture/native-environment-raw.js +6 -2
- package/dist/capture/native-environment-raw.js.map +1 -1
- package/dist/color/illuminants.d.ts +15 -0
- package/dist/color/illuminants.js +81 -24
- package/dist/color/illuminants.js.map +1 -1
- package/dist/color/srgb-transfer.d.ts +6 -0
- package/dist/color/srgb-transfer.js +9 -0
- package/dist/color/srgb-transfer.js.map +1 -1
- package/dist/color/white-balance.d.ts +6 -0
- package/dist/color/white-balance.js +15 -2
- package/dist/color/white-balance.js.map +1 -1
- package/dist/core/internal-radiance-evaluator.d.ts +6 -0
- package/dist/core/internal-radiance-evaluator.js +33 -0
- package/dist/core/internal-radiance-evaluator.js.map +1 -0
- package/dist/core/portable-math.d.ts +33 -0
- package/dist/core/portable-math.js +342 -0
- package/dist/core/portable-math.js.map +1 -1
- package/dist/core/version.d.ts +1 -1
- package/dist/core/version.js +1 -1
- package/dist/core/version.js.map +1 -1
- package/dist/facade/chart-scenes.d.ts +9 -0
- package/dist/facade/chart-scenes.js +17 -4
- package/dist/facade/chart-scenes.js.map +1 -1
- package/dist/facade/diffraction-provider.d.ts +10 -0
- package/dist/facade/diffraction-provider.js +38 -17
- package/dist/facade/diffraction-provider.js.map +1 -1
- package/dist/facade/display-view.d.ts +208 -0
- package/dist/facade/display-view.js +1461 -0
- package/dist/facade/display-view.js.map +1 -0
- package/dist/facade/scene-radiance.d.ts +7 -0
- package/dist/facade/scene-radiance.js +18 -5
- package/dist/facade/scene-radiance.js.map +1 -1
- package/dist/facade/simulate-capture.d.ts +224 -4
- package/dist/facade/simulate-capture.js +104 -33
- package/dist/facade/simulate-capture.js.map +1 -1
- package/dist/facade/simulate-display-view.d.ts +21 -0
- package/dist/facade/simulate-display-view.js +168 -0
- package/dist/facade/simulate-display-view.js.map +1 -0
- package/dist/facade/subject-framing.d.ts +16 -0
- package/dist/facade/subject-framing.js +8 -2
- package/dist/facade/subject-framing.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/optics/diffraction-psf-profile.d.ts +9 -2
- package/dist/optics/diffraction-psf-profile.js +33 -4
- package/dist/optics/diffraction-psf-profile.js.map +1 -1
- package/dist/optics/irradiance-convolution.js +1 -1
- package/dist/optics/irradiance-convolution.js.map +1 -1
- package/dist/optics/scene-to-sensor-irradiance.d.ts +7 -0
- package/dist/optics/scene-to-sensor-irradiance.js +10 -5
- package/dist/optics/scene-to-sensor-irradiance.js.map +1 -1
- package/dist/output/print-plan.d.ts +8 -1
- package/dist/output/print-plan.js +8 -1
- package/dist/output/print-plan.js.map +1 -1
- package/dist/schema/scene-radiance.d.ts +16 -0
- package/dist/schema/scene-radiance.js +25 -4
- package/dist/schema/scene-radiance.js.map +1 -1
- package/dist/sensor/raw-readout.d.ts +12 -0
- package/dist/sensor/raw-readout.js +131 -9
- package/dist/sensor/raw-readout.js.map +1 -1
- package/dist/sensor/stochastic-seed.d.ts +13 -0
- package/dist/sensor/stochastic-seed.js +42 -8
- package/dist/sensor/stochastic-seed.js.map +1 -1
- package/docs/AGENT_GUIDE.md +1 -0
- package/docs/API_REFERENCE.md +13 -1
- package/docs/QUICK_START.md +155 -4
- package/docs/USAGE.md +15 -0
- package/docs/api/core-version.md +1 -1
- package/docs/api/exports.json +22 -2
- package/docs/api/facade-simulate-capture.md +65 -1
- package/docs/api/facade-simulate-display-view.md +18 -0
- package/docs/api/facade-subject-framing.md +16 -0
- package/docs/api/output-print-plan.md +8 -1
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,43 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 2.
|
|
3
|
+
## 2.6.0 — unreleased (prepared October 19–20, 2026)
|
|
4
|
+
|
|
5
|
+
A minor release that adds the display view and the close-up light factor and makes Quality renders faster. `simulateDisplayView` gives the image the real full-resolution capture would give after resizing to a display size, without rendering it at full resolution; region renders with diffraction convolve only the region, identical diffraction kernels are reused and built faster, the engine's own chart scenes skip re-parsing their fixed records, and the framing results report how much light the lens loses up close. Applications that only capture do not bundle the display view. Every capture and ideal-reference request that 2.5.0 accepted gives byte-identical results (the engine version recorded in captures aside), except that region renders with diffraction state the narrower convolution in their `output.region` assumption; their samples, RAW codes and files are unchanged. `calculateChartFraming` and `calculateSubjectDistanceForFraming` results gain two fields. Root exports grow from 1,717 to 1,721 symbols; none is removed. The display view with the Quality options has one known limit, listed below.
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **Display view: the real full-resolution capture resized for display (#368).** `simulateDisplayView(options, view)` takes a `simulateCapture` request (the real sensor's pixel count and pitch) and a display size, and returns the image that capture would give after area averaging in linear light to `view.width` × `view.height`, without rendering it at full resolution. Optics (diffraction, the anti-aliasing filter, the pixel aperture) act at the real pitch, the colour mosaic and demosaic are the real pixels', the noise is that of the average of the real pixels (demosaic correlation included), and exposure, white balance and tone are the capture's. The expected value is a seeded stratified Monte Carlo average over real pixels with exact real-site expectations; the clips and tone curve act on each pixel's own noise distribution (normal, with a first-order Edgeworth skewness term); the noise is one correlated normal draw with the variance of the area mean; AHD is represented by its two linear candidates plus a control-variate correction where its choice reacts to noise. Validated against real full-resolution region renders resized in linear light (three sensors, five charts, f/2.8–f/16, ISO 100–6400, 96–800 pixels wide, Speed and Quality; `docs/DISPLAY_VIEW.md`). Cost follows the detailed display pixels: with the engine defaults 0.6–2.4× the 2.5 whole-frame view of the same request (0.87–1.3× for a Siemens star at 384 pixels wide on 24, 12 and 100 MP sensors). With the Quality options it is 0.76–1.6× on a 6 µm sensor, 1.0–3.7× at 3.76 µm and 2.2–20× at 1.4 µm, from real-pitch diffraction and the AHD correction (one run each, 4-vCPU Xeon; `docs/DISPLAY_VIEW.md`, "Cost"). New `SimulateDisplayViewOptions`, `DisplayViewResult` and `SIMULATE_DISPLAY_VIEW_DEFAULT_SAMPLES_PER_AXIS`; repository scripts `validate-display-view.mjs` and `benchmark-display-view.mjs`. The result is labelled `kind: "display-view"` and is not a capture. Root exports grow from 1,717 to 1,721 symbols; none is removed. Every existing request, result, hash lock and fingerprint is unchanged, including the whole-frame views.
|
|
10
|
+
- **Close-up light factor as data (#364).** `calculateChartFraming` and `calculateSubjectDistanceForFraming` results gain `closeUpTransmission`, the thin-lens factor 1/(1 + m)² (m = v/s at the focus distance) by which the image-plane irradiance falls compared with infinity focus, and `closeUpLossStops`, 2·log₂(1 + m). Both come from the working f-number factor the scene-to-sensor irradiance applies (one internal function), so applications no longer compute it; a test checks them against the RAW signal ratio of `simulateCapture` between 0.35 m and 50 m within 0.5 %. Additive fields; no new exports (see the quick start's "Close-up light (2.6)").
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- **Region renders limit the diffraction convolution to the region (#354).** With `output.region` and `lens.diffraction`, the object-space convolution now covers only the scene lattice the region's native sites query (the native rectangle plus the anti-aliasing and box-aperture offsets, with the kernel radius of real scene radiance around it) instead of whole-width bands. Every kept value is the same gather with the same kernel, taps and order, so regions stay byte-identical to the same pixels of the whole render and to 2.5.0. A 192 × 128 crop of a 6000 × 4000 frame at 6 µm and f/16 with diffraction, the four-spot filter, AHD and a 2 × 2 box aperture took 39.6 s instead of 228.7 s (the convolution 6.5 s instead of 195.8 s; peak memory 545 MB instead of 1 285 MB); without the box 11.8 s instead of 60.8 s (on a GitHub-hosted 4-vCPU runner: 27.7 s instead of 170.9 s with the box, 8.3 s instead of 44.1 s without). The rest of the time is the per-site work, which follows the region's sites and scene queries. Region results with diffraction say so in the `output.region` assumption; region results without it keep the 2.5.0 text. Tested byte for byte against whole-frame crops (centre, corners, edges, 1-pixel strips and the full frame) at 2–250 µm and f/4–f/16 with the Quality options (see the quick start's "Region rendering").
|
|
15
|
+
- **Diffraction kernels are reused and built faster (#355).** `createDiffractionLimitedPsfProfile` (and so `lens.diffraction`) keeps the 256 most recently used Airy cell tables, keyed by β = pitch/(λ·N_w) and the kernel radius, and hands out copies; the frequency-domain quadrature takes cos θ and sin θ from a table computed with the same expressions. The values are unchanged (cell bytes locked against 2.5.0 at β = 0.4–250). A second build of the same profile in the same JavaScript realm (another tile of the frame in the same worker, or a capture that changes only the exposure, ISO, scene or output) takes milliseconds: 1–4 ms instead of 0.3–1.5 s at 375–912.5 µm, f/16, 20 wavelengths. A first build takes 15–30 % less (750 µm: 1.37 s instead of 1.63 s). On a GitHub-hosted 4-vCPU runner, a repeated whole-frame Quality capture (diffraction, four-spot filter, AHD, f/16) at 48 × 32 took 0.76 s instead of 1.66 s for a full-frame sensor and 0.67 s instead of 1.96 s for medium format; at 96 × 64 with the 2 × 2 box, where the per-site work dominates, 6.47 s instead of 6.79 s and 6.54 s instead of 6.99 s. The β ≥ 192 switch to the real-space method stays where it is: its Hankel expansion needs x = π·β/2 ≥ 300, so it cannot go below β = 191 without another Bessel method, whose cells would differ from today's in the last bits.
|
|
16
|
+
- **Less validation overhead for the engine's own chart scenes (#363).** In `simulateCapture` with a chart preset, the native environment no longer re-parses the fixed parts of each scene result: it skips the unknown-key scan and the re-parse of the deep-frozen uncertainty, evidence and limitations records when they are the same objects as an earlier result's that passed the full parse. Every scalar check (shape, schema, quantity, unit, status, identifiers, wavelength, basis, radiance) still runs. Caller scenes and caller providers keep the full validation. On a GitHub-hosted 4-vCPU runner a 192 × 128 Quality crop (diffraction, four-spot filter, AHD, 2 × 2 box) of a 6000 × 4000 frame took 19.4 s instead of 29.3 s, and a 96 × 64 whole-frame Quality capture 4.9 s instead of 7.5 s (median of three).
|
|
17
|
+
- **Display view Quality is 2–5× faster, byte for byte (#375).** Two exact speed-ups, with every display view unchanged (samples, expected values, noise and diagnostics, `sceneLookups` included): the AHD correction states are kept and shared across display pixels and across calls with the same noise seed, readout and development (packed, at most 65 536 states for each of the 4 most recent input sets, about 50 bytes each), and are 3× cheaper to compute (a table of ln n! with the same partial sums, per-mean Poisson constants, the AHD steps on flat arrays); and the per-site diffraction sums keep their order but are shared: lattice materials are looked up once per display row, a site whose kernel reads one material takes that material's precomputed sum, and site values carry over between neighbouring display pixels. Samples whose sites all read one material replay the recorded terms of the first such sample, and zero-weight terms are skipped. On a 4-vCPU Xeon (Siemens star, f/8, ISO 100, before and after interleaved, one run each), the 12 MP phone at 1.4 µm took 49.7 s instead of 222 s at 96 pixels wide, 106 s instead of 450 s at 384 and 272 s instead of 837 s at 800; 100 MP medium format 18.2 / 49.7 / 126 s instead of 64.9 / 208 / 424 s; 24 MP full frame 5.2 / 30.9 / 84.3 s instead of 15.5 / 65.0 / 162 s; dead leaves at 384 wide 1.8–4.1× faster. Speed is unchanged. The phone at 384 wide stays above the 60 s aimed for: its remaining time is the correction states' draws (40 s), the per-sample statistics (42 s) and the kernel sums (13 s), which cannot be cut further without approximation or parallel workers (`docs/DISPLAY_VIEW.md`, "Cost"). A second view with the same sensor, exposure and development reuses the states (the same phone view again: 73 s). Proof: all 155 validation requests at their seeds hash identically before and after, a new test locks six requests covering each path to hashes recorded before the change, and the capture hash locks and facade fingerprints are unchanged.
|
|
18
|
+
- **Display view: the same bytes on every Node.js version (#376).** The display view's own arithmetic no longer uses `Math.pow`, `**`, `Math.exp`, `Math.log`, `Math.cbrt` or `Math.cos`, whose last bits ECMAScript leaves to each engine (Node 22 and Node 24 differ in about 10 % of `x ** (1/2.4)`). It uses new internal portable functions (`src/core/portable-math.ts`: logarithm, exponential, power, cube root, e^x − 1, cos and sin of 2πu), each a fixed sequence of correctly rounded operations within 1 ulp (2 ulps for e^x − 1, cos and sin) of the exact function, by double-double arithmetic and Taylor series after range reduction (Dekker 1971; Muller, *Elementary Functions*, 3rd ed., ch. 11): for the sRGB encoding, the normal density, the Box–Muller and Poisson draws, the AHD correction's CIELAB cube root and grid, the engine's illuminant formulas (Planck's law, CIE A, the daylight locus) with the view's irradiance scale and scene-illuminant white balance, the Planckian locus of a Kelvin white balance, and a step wedge's equal-density levels. The display view's bytes changed once in their last bits and are now the same on Node 22.13, 22 LTS, 24 and 26: the hash locks hold one set instead of one per runtime, and 156 further requests across sensors, options, charts, exposures, illuminants and white balances hashed identically on Node 22.13, 24 and 26. Inputs the view takes from the capture's code (sensor spectral response, colour-matching fit, diffraction kernels, chart geometry) keep `Math`, so in browsers whose `Math` differs (Chromium 141 with diffraction, Chromium 153, JavaScriptCore) its expected values and noise can still differ in the last bits (`docs/DISPLAY_VIEW.md`, "Same bytes in every engine"). The portable functions cost more operations than `Math`; with both values of each Box–Muller pair used and no draws for the 16 sites AHD never reads, Speed is unchanged within run-to-run spread and Quality takes up to about a quarter longer on small pixels (one run each on a 4-vCPU Xeon: phone star at 96 wide 47.5 → 60.2 s, at 384 wide 97.2 → 114.6 s; full frame star at 384 wide 27.0 → 28.2 s). Captures are unchanged: every capture result, hash lock and fingerprint is byte-identical.
|
|
19
|
+
- **Faster seeded random streams, same draws (#375).** The engine's xorshift32 stream keeps its state in a `Uint32Array` instead of a closure variable, which halves the cost of a draw (a uint32 above 2^30 was boxed on every step); the draws are unchanged (tested against the previous form for 50 000 values).
|
|
20
|
+
- **Bundles that only capture leave out the display view (#381, issue #380).** `simulateDisplayView` moves to its own module, which hands its renderer to the shared request path, and the shared illuminant, white-balance, chart and readout code takes the display view's portable functions as arguments instead of importing them; the portable functions' tables are built by expressions marked pure. A bundler that imports only `simulateCapture` and `simulateIdealReference` now drops the display view and every portable function except the two captures use (rolldown 1.2.9, minified: 779,884 bytes, 208,090 gzip, instead of 820,912 and 224,431; 2.5.0: 774,225 and 205,841), and `calculateChartFraming` alone bundles to 4,708 bytes gzip instead of 5,362 (2.5.0: 4,265). A new test bundles a capture-only consumer with rolldown and checks that the display view is absent. Results are unchanged: the facade fingerprints, every capture and display-view hash lock, and captures, ideal references and display views through each changed path (Planckian, CIE A and daylight illuminants, Kelvin and scene-illuminant white balance, step wedges) hash identically before and after. `rolldown` (MIT, already installed with Vitest) becomes a declared development dependency.
|
|
21
|
+
|
|
22
|
+
| Consumer imports (rolldown 1.2.9, minified) | 2.5.0 | before #381 | 2.6.0 |
|
|
23
|
+
| --- | --- | --- | --- |
|
|
24
|
+
| `simulateCapture`, `simulateIdealReference` | 774,225 B / 205,841 B gzip | 820,912 / 224,431 | 779,884 / 208,090 |
|
|
25
|
+
| `calculateChartFraming` | 12,762 / 4,265 | 15,171 / 5,362 | 13,914 / 4,708 |
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
### Known limit
|
|
29
|
+
|
|
30
|
+
- **Display view, Quality (AHD): too much colour in the noise where a display pixel covers few sensor pixels (#379).** The noise model treats each output colour as driven only by sensor sites of its own colour, but AHD forms red and blue from green plus interpolated colour differences, so they read green sites as well and share noise with green. Over a large footprint those green weights cancel; over a small one they do not. On the 12 MP 1.4 µm sensor at 800 pixels wide (5 × 5 real pixels per display pixel) the view's R − G noise is about 9 % and its B − G noise 15–18 % larger than the resized capture's. Two validation cases are documented misses of this limit (owner decision, October 20, 2026; limits unchanged): case 138 (uniform 18 % grey, ISO 6400), predicted ÷ reference noise R 1.164, G 1.133, B 1.030 against 0.85–1.15; and case 92 (slanted edge, ISO 100), false colour +0.48 code against the 7-seed reference mean (limit 0.5; +0.49 ± 0.05 over seeds). The expected value is not affected, and Speed (bilinear) is exact. A redesign of the Quality noise model is planned for 2.7.0; see `docs/DISPLAY_VIEW.md`, "Known limit: Quality noise at small footprints".
|
|
31
|
+
|
|
32
|
+
### Documentation
|
|
33
|
+
|
|
34
|
+
- **Display-view validation regenerated on the final code, against seven reference noise seeds (#377).** All 155 cases of `scripts/validate-display-view.mjs` were re-run on `main` at `ea6dbe7` and replace the record in `docs/validation/display-view-2026-10-19/` (its lines came from a build a little before the merged display view). Each reference block is now rendered at noise seeds 1–7 and every reference-side metric is the mean over the seven, because one noise realization of the reference varies too much for the limits: on the 100 MP sensor at 800 pixels wide the reference's slanted-edge MTF50 ranged 0.521–0.554 over the seven seeds (standard deviation 2.1 %), so two single realizations differ by about 3 %, the size of the MTF50 limit. The limits are unchanged. Every case passes every limit except one noise ratio, a documented miss of the known limit of the Quality noise model (#379; case 138: 12 MP phone, Quality, uniform 18 % grey, 800 wide, ISO 6400: predicted ÷ reference R 1.164, G 1.133, B 1.030 against 0.85–1.15; limits unchanged); the slanted edge's MTF50 on the 100 MP sensor at 800 wide is −0.9 % (it was −4.4 % against seed 1 alone) and the phone's false colour at 800 wide +0.48 code (limit 0.5). The display views are unchanged.
|
|
35
|
+
- **Published sources for the diffraction model (#366).** The quick start's `lens.diffraction` notes and its "Sources" list now cite the scalar Fraunhofer diffraction and Airy pattern model the engine follows: Born & Wolf, *Principles of Optics*, 7th ed., §8.5.2; Goodman, *Introduction to Fourier Optics*, 4th ed., §4.4 and §7.3; Wyant & Creath (1992), §II, eqs. 1–4 (free); MIT OpenCourseWare 2.71, lectures 21–22 (free); the NIST DLMF §10.6(i), 10.17.3, §10.17(iii) and 10.9.2 (free) for the encircled energy and Bessel functions of the real-space path; and Airy (1835) as the original analysis. `docs/PSF_FOUNDATION.md` now cites §7.3 of Goodman's 4th edition for the incoherent transfer function (it said §6.3, the 3rd edition's numbering) and Wyant & Creath for the encircled energy; `docs/PHYSICS_FOUNDATION.md` cites the 2.44·λ·N Airy disk diameter. Documentation only: no code, result, hash lock or fingerprint changes.
|
|
36
|
+
- **A source for the one-arcminute print criterion; Goodman section numbers (#378).** The print planner's documentation (`calculatePrintPlan`, `calculatePrintSizeLimit`, `docs/PRINT_PLANNING.md` and the quick start's "Sources") now cites Alomar & Bojikian, "Visual Acuity Testing in Adults" (EyeWiki, American Academy of Ophthalmology, free to read) for the one-arcminute-per-pixel rule of thumb: one arcminute is normal (20/20) acuity under optimal conditions, which some viewers exceed and many do not. It stays labelled an approximate rule of thumb, and the planner still assumes no acuity value. Four comments and docs that cite Goodman's *Introduction to Fourier Optics*, 4th ed., now give that edition's section numbers (checked against the publisher's table of contents): §7.3 for the incoherent transfer function (it said §6.3) and §7.1 for incoherent imaging as a linear system (it said §6.1, which is the thin lens in the 4th edition). Documentation only: no code, result, hash lock or fingerprint changes.
|
|
37
|
+
|
|
38
|
+
Results are byte-identical to 2.5.0 (the hash locks and the facade fingerprints of 15 default-path results), apart from that assumption text on region renders with diffraction; their samples, RAW and files are unchanged.
|
|
39
|
+
|
|
40
|
+
## 2.5.0 — published October 18, 2026
|
|
4
41
|
|
|
5
42
|
A minor release for whole-frame views and chart framing: `simulateCapture` accepts pixel pitches up to 1000 µm, so a whole sensor frame can be rendered at a small output size with the real sensor's prime, minimum focus and framing, and `calculateChartFraming` reports how much of the frame a chart covers at any distance. Both are additive: every request that 2.4.1 accepted gives byte-identical results (the engine version recorded in captures aside). Root exports grow from 1,714 to 1,717 symbols; none is removed.
|
|
6
43
|
|
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.
|
|
12
|
+
version: "2.6.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.
|
|
35
|
+
Install the published ESM package below. The [2.6.0 release record](https://github.com/Photivra/photivra/blob/main/docs/RELEASE_2_6_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.
|
|
38
|
+
npm install @photivra/engine@2.6.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.
|
|
78
|
+
Repository `main` may describe newer, unreleased work. For the stable contract, use the docs bundled in the published package, or the exact `v2.6.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.
|
|
367
|
-
- Engine API contract: `2.
|
|
366
|
+
- Repository package version: `2.6.0` (release candidate prepared October 19, 2026; publication is separate)
|
|
367
|
+
- Engine API contract: `2.6.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.
|
|
372
|
+
From 1.0.1, the package version and `ENGINE_API_VERSION` are equal. New captures/plans record 2.6.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.
|
|
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.5.0**, published on October 18, 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
|
@@ -13,6 +13,7 @@ This file records direct development and CI dependencies intentionally used by t
|
|
|
13
13
|
| typescript-eslint | 8.70.0 | TypeScript ESLint integration | MIT |
|
|
14
14
|
| Vitest | 5.0.1 | Unit/integration testing | MIT |
|
|
15
15
|
| @vitest/coverage-v8 | 5.0.1 | V8-backed test coverage | MIT |
|
|
16
|
+
| rolldown | 1.2.9 | Bundling test of capture-only consumers (`test/capture-bundle.test.ts`) | MIT |
|
|
16
17
|
| actions/checkout | v7 / `3d3c42e5aac5ba805825da76410c181273ba90b1` | CI checkout | MIT |
|
|
17
18
|
| actions/setup-node | v7 / `820762786026740c76f36085b0efc47a31fe5020` | CI Node setup | MIT |
|
|
18
19
|
| actions/upload-artifact | v7.0.1 / `043fb46d1a93c77aae656e7c1c64a875d1fc6a0a` | Verified release artifact transfer | MIT |
|
|
@@ -197,7 +197,7 @@ export interface NativeEnvironmentLeanExecutor {
|
|
|
197
197
|
* caller then plans and executes this site in full. `fullPlan` builds the site's full plan without callbacks;
|
|
198
198
|
* it is called only when a sampled-PSF check fails and the full PSF quadrature must decide.
|
|
199
199
|
*/
|
|
200
|
-
execute(plan: LeanEnvironmentPlan, evaluate: EnvironmentRadianceEvaluator, aperture: EnvironmentApertureRadianceEvaluator | undefined, fullPlan: () => FullPlan): SensorEqeTemporalPhotoSignal | undefined;
|
|
200
|
+
execute(plan: LeanEnvironmentPlan, evaluate: EnvironmentRadianceEvaluator, aperture: EnvironmentApertureRadianceEvaluator | undefined, fullPlan: () => FullPlan, internalEvaluator?: boolean): SensorEqeTemporalPhotoSignal | undefined;
|
|
201
201
|
}
|
|
202
202
|
export declare function createNativeEnvironmentLeanExecutor(frame: NativeEnvironmentLeanFrame): NativeEnvironmentLeanExecutor;
|
|
203
203
|
/**
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
import { InvalidConfigurationError } from "../core/configuration-error.js";
|
|
3
3
|
import { apertureRadianceQuery } from "../sensor/environment-photo-signal.js";
|
|
4
4
|
import { createSensorEqeTemporalPhotoSignal } from "../sensor/temporal-photo-signal.js";
|
|
5
|
-
import {
|
|
5
|
+
import { isDeeplyFrozen } from "../core/internal-radiance-evaluator.js";
|
|
6
|
+
import { parseSceneRadianceEvaluationResult, parseSceneRadianceEvaluationResultWithValidatedRecords, validateSceneRadianceEvaluationBindings } from "../schema/scene-radiance.js";
|
|
6
7
|
import { calculateSceneRadianceToSensorIrradiance } from "../optics/scene-to-sensor-irradiance.js";
|
|
7
8
|
import { calculateMappedSensorEnvironmentRadianceQuery, prepareEnvironmentLensFieldMapping } from "../optics/sensor-environment-query.js";
|
|
8
9
|
import { calculateSensorPsfIrradianceQuadrature } from "../optics/sensor-psf-quadrature.js";
|
|
@@ -618,6 +619,18 @@ export function createNativeEnvironmentLeanExecutor(frame) {
|
|
|
618
619
|
const x = index % frame.nativeWidth, y = Math.floor(index / frame.nativeWidth);
|
|
619
620
|
return (y % frame.repeatHeight) * frame.repeatWidth + (x % frame.repeatWidth);
|
|
620
621
|
};
|
|
622
|
+
// 2.6: results of an engine-built evaluator (isInternalSceneRadianceEvaluator) reuse the parsed uncertainty, evidence
|
|
623
|
+
// and limitations of the first such result that passed the full parse, when they are the same deep-frozen objects.
|
|
624
|
+
let validatedRecords;
|
|
625
|
+
const parseInternalResult = (value) => {
|
|
626
|
+
if (validatedRecords)
|
|
627
|
+
return parseSceneRadianceEvaluationResultWithValidatedRecords(value, validatedRecords);
|
|
628
|
+
const parsed = parseSceneRadianceEvaluationResult(value), record = value;
|
|
629
|
+
if (isDeeplyFrozen(record.uncertainty) && isDeeplyFrozen(record.evidence) && isDeeplyFrozen(record.limitations)) {
|
|
630
|
+
validatedRecords = { uncertainty: record.uncertainty, evidence: record.evidence, limitations: record.limitations, parsed };
|
|
631
|
+
}
|
|
632
|
+
return parsed;
|
|
633
|
+
};
|
|
621
634
|
return {
|
|
622
635
|
plan(environment, index) {
|
|
623
636
|
if (!enabled)
|
|
@@ -680,7 +693,7 @@ export function createNativeEnvironmentLeanExecutor(frame) {
|
|
|
680
693
|
// Unverified: sites of this template run the full path.
|
|
681
694
|
}
|
|
682
695
|
},
|
|
683
|
-
execute(plan, evaluate, aperture, fullPlan) {
|
|
696
|
+
execute(plan, evaluate, aperture, fullPlan, internalEvaluator = false) {
|
|
684
697
|
const t = plan.template, c = t.execution, k = t.consts;
|
|
685
698
|
if (t.executionVerified !== true || !c || !k)
|
|
686
699
|
return undefined;
|
|
@@ -693,7 +706,8 @@ export function createNativeEnvironmentLeanExecutor(frame) {
|
|
|
693
706
|
for (const ray of support) {
|
|
694
707
|
const query = ray === undefined ? undefined : apertureRadianceQuery(tap.request, ray);
|
|
695
708
|
const request = query === undefined ? tap.request : query.request, apertureRay = query?.apertureRay;
|
|
696
|
-
const result =
|
|
709
|
+
const result = apertureRay === undefined ? (internalEvaluator ? parseInternalResult(evaluate(request)) : parseSceneRadianceEvaluationResult(evaluate(request))) :
|
|
710
|
+
parseSceneRadianceEvaluationResult(aperture(request, apertureRay));
|
|
697
711
|
if (result.sampleId !== request.sampleId || result.providerProfileId !== request.providerProfileId || result.sceneId !== request.sceneId ||
|
|
698
712
|
result.wavelengthNanometers !== request.wavelengthNanometers || result.wavelengthBasis !== request.wavelengthBasis) {
|
|
699
713
|
validateSceneRadianceEvaluationBindings({ ...k.sceneBindings, request, result });
|