@photivra/engine 2.7.0 → 2.8.1

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 (49) hide show
  1. package/CHANGELOG.md +114 -1
  2. package/CITATION.cff +1 -1
  3. package/README.md +6 -6
  4. package/SECURITY.md +1 -1
  5. package/dist/capture/export-jpeg.js +25 -12
  6. package/dist/capture/export-jpeg.js.map +1 -1
  7. package/dist/capture/native-environment-lean.d.ts +35 -5
  8. package/dist/capture/native-environment-lean.js +175 -16
  9. package/dist/capture/native-environment-lean.js.map +1 -1
  10. package/dist/capture/native-environment-raw.d.ts +9 -1
  11. package/dist/capture/native-environment-raw.js +14 -9
  12. package/dist/capture/native-environment-raw.js.map +1 -1
  13. package/dist/capture/native-environment-tabulated.d.ts +58 -0
  14. package/dist/capture/native-environment-tabulated.js +91 -0
  15. package/dist/capture/native-environment-tabulated.js.map +1 -0
  16. package/dist/core/internal-radiance-evaluator.d.ts +28 -0
  17. package/dist/core/internal-radiance-evaluator.js +30 -0
  18. package/dist/core/internal-radiance-evaluator.js.map +1 -1
  19. package/dist/core/version.d.ts +1 -1
  20. package/dist/core/version.js +1 -1
  21. package/dist/core/version.js.map +1 -1
  22. package/dist/facade/diffraction-provider.js +33 -12
  23. package/dist/facade/diffraction-provider.js.map +1 -1
  24. package/dist/facade/display-view.d.ts +14 -83
  25. package/dist/facade/display-view.js +331 -846
  26. package/dist/facade/display-view.js.map +1 -1
  27. package/dist/facade/expected-signal-setup.d.ts +65 -0
  28. package/dist/facade/expected-signal-setup.js +52 -0
  29. package/dist/facade/expected-signal-setup.js.map +1 -0
  30. package/dist/facade/expected-signal.d.ts +83 -0
  31. package/dist/facade/expected-signal.js +571 -0
  32. package/dist/facade/expected-signal.js.map +1 -0
  33. package/dist/facade/scene-radiance.d.ts +6 -0
  34. package/dist/facade/scene-radiance.js +31 -10
  35. package/dist/facade/scene-radiance.js.map +1 -1
  36. package/dist/facade/simulate-capture.js +109 -28
  37. package/dist/facade/simulate-capture.js.map +1 -1
  38. package/dist/facade/simulate-display-view.js +26 -49
  39. package/dist/facade/simulate-display-view.js.map +1 -1
  40. package/dist/optics/site-diffraction-kernel.d.ts +5 -0
  41. package/dist/optics/site-diffraction-kernel.js +32 -0
  42. package/dist/optics/site-diffraction-kernel.js.map +1 -0
  43. package/dist/output/print-jpeg.js +70 -42
  44. package/dist/output/print-jpeg.js.map +1 -1
  45. package/docs/API_REFERENCE.md +1 -1
  46. package/docs/QUICK_START.md +1 -1
  47. package/docs/api/core-version.md +1 -1
  48. package/docs/api/exports.json +2 -2
  49. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"native-environment-tabulated.js","sourceRoot":"","sources":["../../src/capture/native-environment-tabulated.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Internal (#394 step 2, not exported from the package): the native RAW of an engine-built planar scene (a chart preset or a\n * caller material scene, still camera, rectilinear, no distortion, vignetting, pupil rays or caller PSF) from tabulated\n * expected electrons instead of the per-site environment chain.\n *\n * Each site's expected photo electrons come from the display view's tabulated expected signal (`createDisplayViewExpectedSignal`):\n * the same sum over pixel-aperture nodes, anti-aliasing spots, diffraction taps and spectral nodes as the environment chain,\n * formed in another order, so the value differs from the chain's only in the last bits. The sensor stage is the native one:\n * dark charge, both Poisson draws, read noise and the ADC with the site's own seeds (`createVerifiedNativeSiteReadout`). A\n * site whose expected electrons differ in the last bit can therefore, rarely, draw a different integer; the statistics are\n * unchanged.\n *\n * Verification at run time: one site of each CFA phase runs the full chain (planning, the evaluator's callbacks, composition,\n * parsing and `calculateNativeRawSite`). Its readout template must reproduce that site bit for bit, and its tabulated\n * electrons must agree with the chain's within `TABULATED_RELATIVE_TOLERANCE`; otherwise the caller runs the environment task.\n */\n\nimport type { EnvironmentRadianceEvaluator } from \"../sensor/environment-photo-signal.js\";\nimport { executeEnvironmentSensorPhotoSignal } from \"../sensor/environment-photo-signal.js\";\nimport { calculateCaptureExposureWindows, prepareCaptureExposureWindowSampler } from \"../sensor/exposure-window.js\";\nimport { createEnvironmentRawFrameCanonicalMemo, planEnvironmentRawSite, type SimulateEnvironmentSensorRawFrameInput } from \"./environment-raw-producer.js\";\nimport { admitNativeEnvironmentInput, type NativeEnvironmentRawInput } from \"./native-environment-raw.js\";\nimport { composeNativeEnvironmentSensorSite } from \"./native-environment-optical-stage.js\";\nimport { calculateNativeRawSite, parseNativeRawProducerSite } from \"./sensor-raw-producer.js\";\nimport { calculateSiteEffectiveSaturationCode, createPhaseWhiteLevelAccumulator } from \"./raw-white-level.js\";\nimport { createVerifiedNativeSiteReadout, type NativeSiteReadout } from \"./native-environment-lean.js\";\nimport type { NativeRawOutput } from \"./native-raw.js\";\n\n/** Largest relative difference between a verification site's tabulated and chain electrons: rounding only (a few ulps of each term). */\nexport const TABULATED_RELATIVE_TOLERANCE = 1e-9;\n/** Rows per expected-signal cache window. */\nconst BAND_ROWS = 32;\n/** Host-yield budget in milliseconds (the native task's cadence); the task also yields after its last row. */\nconst YIELD_MS = 8;\n\nlet tabulatedDisabled = false;\n/** Disables the tabulated path process-wide (internal; tests of the environment path itself turn it off). */\nexport function setTabulatedNativeRawEnabled(enabled: boolean): void { tabulatedDisabled = !enabled; }\n\nexport interface TabulatedNativeRawInput {\n input: NativeEnvironmentRawInput;\n /** Native rectangle to render (region rendering); absent: the whole raster. */\n region?: { x: number; y: number; width: number; height: number } | undefined;\n /** The engine-built site record of a native index (the facade's `siteInput`). */\n siteInput(index: number): SimulateEnvironmentSensorRawFrameInput[\"sites\"][number];\n /** The evaluator the environment task would use (for the verification sites). */\n evaluateRadiance: EnvironmentRadianceEvaluator;\n /** Expected photo electrons of native sites (`createDisplayViewExpectedSignal`). */\n electrons: { window(x0: number, y0: number, x1: number, y1: number): void; site(x: number, y: number): number; readonly lookups: number };\n /** Called once per native tile-equivalent (progress), as the environment task reads its tiles. */\n onTile?: () => void;\n tileWidth: number;\n yieldControl(): Promise<void>;\n checkAbort(): void;\n}\n\nexport interface TabulatedNativeRawResult {\n raw: NativeRawOutput;\n /** Evaluator callbacks of the verification sites plus the tabulated signal's scene lookups. */\n providerEvaluationCount: number;\n /** Largest relative difference between tabulated and chain electrons over the verification sites. */\n verificationRelativeDifference: number;\n}\n\n/** Renders the native RAW from tabulated expected electrons, or returns undefined when verification fails (see the module comment). */\nexport async function renderTabulatedNativeRaw(t: TabulatedNativeRawInput): Promise<TabulatedNativeRawResult | undefined> {\n if (tabulatedDisabled) return undefined;\n const { plan, sceneBinding } = admitNativeEnvironmentInput(t.input);\n const native = plan.exposure.geometry.nativeRaster, W = native.pixelWidth, layout = plan.frame.colorSamplingProfile.layout;\n if (layout.kind !== \"periodic-mosaic\" || layout.repeatWidthSites !== 2 || layout.repeatHeightSites !== 2 || !plan.exposureWindow) return undefined;\n const region = t.region ?? { x: 0, y: 0, width: W, height: native.pixelHeight };\n if (region.width < 2 || region.height < 2) return undefined;\n const seed = plan.exposure.noise.seedUint32, schedule = plan.exposureWindow, memo = createEnvironmentRawFrameCanonicalMemo(plan.frame, schedule);\n let evaluations = 0;\n const evaluate: EnvironmentRadianceEvaluator = (request) => { evaluations++; return t.evaluateRadiance(request); };\n // Verification: the region's top-left 2 × 2 block, one site per CFA phase, through the full chain.\n const readouts: NativeSiteReadout[] = [];\n let worst = 0;\n for (let k = 0; k < 4; k++) {\n const x = region.x + (k & 1), y = region.y + (k >> 1), index = y * W + x, site = t.siteInput(index);\n const full = planEnvironmentRawSite(site, index, plan.frame, sceneBinding, schedule, \"engine-owned\", memo, \"photo-only\");\n const photo = executeEnvironmentSensorPhotoSignal(full, evaluate, undefined, \"photo-only\").value.photo.value.photoSignal;\n const timing = calculateCaptureExposureWindows({ ...schedule, nativeRaster: native, samplePointsNative: [{ x: x + 0.5, y: y + 0.5 }] }).value.samples[0];\n const parsed = parseNativeRawProducerSite(composeNativeEnvironmentSensorSite(site, photo), index, plan.frame, timing);\n const result = calculateNativeRawSite(parsed, seed, index);\n const whiteCode = calculateSiteEffectiveSaturationCode(result.readout.value, parsed.readoutProfile, parsed.regimeId);\n const readout = createVerifiedNativeSiteReadout(parsed, result, whiteCode, photo, x, y, index, seed);\n if (!readout) return undefined;\n t.electrons.window(x, y, x + 1, y + 1);\n const chain = photo.expectedGeneratedElectronCount, tabulated = t.electrons.site(x, y);\n const difference = chain === tabulated ? 0 : Math.abs(tabulated - chain) / Math.max(Math.abs(chain), Number.MIN_VALUE);\n if (!(difference <= TABULATED_RELATIVE_TOLERANCE)) return undefined;\n worst = Math.max(worst, difference);\n readouts[(y & 1) * 2 + (x & 1)] = readout;\n }\n const length = region.width * region.height;\n const raw: NativeRawOutput = { plan, codes: new Uint16Array(length), blackLevels: new Uint16Array(length), digitalSaturationCodes: new Uint16Array(length),\n saturationFlags: new Uint8Array(length), effectiveSaturationCodes: new Uint16Array(4) };\n const white = createPhaseWhiteLevelAccumulator(W, 2, 2), sampler = prepareCaptureExposureWindowSampler({ ...schedule, nativeRaster: native });\n const tilesPerRow = Math.ceil(region.width / t.tileWidth), point = { x: 0, y: 0 };\n let last = Date.now();\n for (let y0 = region.y; y0 < region.y + region.height; y0 += BAND_ROWS) {\n const y1 = Math.min(region.y + region.height, y0 + BAND_ROWS);\n t.electrons.window(region.x, y0, region.x + region.width, y1);\n for (let y = y0; y < y1; y++) {\n for (let x = region.x; x < region.x + region.width; x++) {\n const index = y * W + x, at = (y - region.y) * region.width + x - region.x;\n point.x = x + 0.5; point.y = y + 0.5;\n const o = readouts[(y & 1) * 2 + (x & 1)]!.compute(t.electrons.site(x, y), sampler(point).localExposureDurationSeconds, x, y, index, seed);\n // A check of the full path failed for this site: let the environment task decide (it throws its own error).\n if (!o) return undefined;\n raw.codes[at] = o.rawCode; raw.blackLevels[at] = o.blackLevelCode; raw.digitalSaturationCodes[at] = o.digitalSaturationCode; raw.saturationFlags[at] = o.flags;\n white.record(index, o.whiteCode);\n }\n for (let k = 0; k < tilesPerRow; k++) t.onTile?.();\n if (y === region.y + region.height - 1 || Date.now() - last >= YIELD_MS) { await t.yieldControl(); t.checkAbort(); last = Date.now(); }\n }\n }\n raw.effectiveSaturationCodes.set(t.region ? white.partial() : white.finish());\n return { raw, providerEvaluationCount: evaluations + t.electrons.lookups, verificationRelativeDifference: worst };\n}\n"]}
@@ -2,5 +2,33 @@
2
2
  export declare function markInternalSceneRadianceEvaluator<T extends object>(evaluator: T): T;
3
3
  /** True only for an evaluator marked by the engine. */
4
4
  export declare function isInternalSceneRadianceEvaluator(evaluator: unknown): boolean;
5
+ /**
6
+ * Internal (#394): the lookup behind an engine-built evaluator whose result for an environment-direction query is a pure
7
+ * function of a location key (a chart material, a convolved lattice site) and the wavelength.
8
+ */
9
+ export interface InternalRadianceAccess {
10
+ /** The key the evaluator reads for a query with this outgoing direction; undefined where it would throw. */
11
+ keyOfDirection(direction: Readonly<{
12
+ x: number;
13
+ y: number;
14
+ z: number;
15
+ }>): number | undefined;
16
+ /** The radiance the evaluator returns for a query of that key at that wavelength. */
17
+ radianceOfKey(key: number, wavelengthNanometers: number): number;
18
+ /** True when keys are few (chart materials), so the native environment may keep a table of their radiances. */
19
+ tabulated: boolean;
20
+ }
21
+ /**
22
+ * Registers the lookup of an engine-built evaluator (#394), so the native environment may take its radiances from the key
23
+ * instead of calling it (its lean templates verify this against their own evaluations). Only for an evaluator already marked
24
+ * internal; caller scenes never register.
25
+ */
26
+ export declare function registerInternalRadianceAccess(evaluator: object, access: InternalRadianceAccess): void;
27
+ /** The registered lookup of an engine-built evaluator, if any. */
28
+ export declare function internalRadianceAccess(evaluator: unknown): InternalRadianceAccess | undefined;
29
+ /** Marks an engine-built native tile reader and returns it. */
30
+ export declare function markInternalNativeTileReader<T extends object>(reader: T): T;
31
+ /** True only for a tile reader marked by the engine. */
32
+ export declare function isInternalNativeTileReader(reader: unknown): boolean;
5
33
  /** True when the value and everything reachable from it are frozen (primitives count as frozen). */
6
34
  export declare function isDeeplyFrozen(value: unknown): boolean;
@@ -17,6 +17,36 @@ export function markInternalSceneRadianceEvaluator(evaluator) {
17
17
  export function isInternalSceneRadianceEvaluator(evaluator) {
18
18
  return typeof evaluator === "function" && INTERNAL_EVALUATORS.has(evaluator);
19
19
  }
20
+ const RADIANCE_ACCESS = new WeakMap();
21
+ /**
22
+ * Registers the lookup of an engine-built evaluator (#394), so the native environment may take its radiances from the key
23
+ * instead of calling it (its lean templates verify this against their own evaluations). Only for an evaluator already marked
24
+ * internal; caller scenes never register.
25
+ */
26
+ export function registerInternalRadianceAccess(evaluator, access) {
27
+ if (INTERNAL_EVALUATORS.has(evaluator))
28
+ RADIANCE_ACCESS.set(evaluator, access);
29
+ }
30
+ /** The registered lookup of an engine-built evaluator, if any. */
31
+ export function internalRadianceAccess(evaluator) {
32
+ return typeof evaluator === "function" ? RADIANCE_ACCESS.get(evaluator) : undefined;
33
+ }
34
+ /**
35
+ * Internal (#394): marks the native tile readers that `simulateCapture` builds. Each call returns fresh site records built by
36
+ * the engine (new top-level objects per site, shared nested records the engine never changes afterwards), never shown to
37
+ * supplied code, so the native environment may plan them as engine-owned data without the private structured clone it makes
38
+ * of a caller's tiles. Readers from callers are never marked. The mark is on the function object, which only the engine holds.
39
+ */
40
+ const INTERNAL_TILE_READERS = new WeakSet();
41
+ /** Marks an engine-built native tile reader and returns it. */
42
+ export function markInternalNativeTileReader(reader) {
43
+ INTERNAL_TILE_READERS.add(reader);
44
+ return reader;
45
+ }
46
+ /** True only for a tile reader marked by the engine. */
47
+ export function isInternalNativeTileReader(reader) {
48
+ return typeof reader === "function" && INTERNAL_TILE_READERS.has(reader);
49
+ }
20
50
  /** True when the value and everything reachable from it are frozen (primitives count as frozen). */
21
51
  export function isDeeplyFrozen(value) {
22
52
  if (value === null || typeof value !== "object")
@@ -1 +1 @@
1
- {"version":3,"file":"internal-radiance-evaluator.js","sourceRoot":"","sources":["../../src/core/internal-radiance-evaluator.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC;;;;;;;GAOG;AACH,MAAM,mBAAmB,GAAG,IAAI,OAAO,EAAU,CAAC;AAElD,sDAAsD;AACtD,MAAM,UAAU,kCAAkC,CAAmB,SAAY;IAC/E,mBAAmB,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IACnC,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,gCAAgC,CAAC,SAAkB;IACjE,OAAO,OAAO,SAAS,KAAK,UAAU,IAAI,mBAAmB,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;AAC/E,CAAC;AAED,oGAAoG;AACpG,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,OAAO,KAAK,KAAK,UAAU,CAAC;IACpF,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC1C,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzC,MAAM,UAAU,GAAG,MAAM,CAAC,wBAAwB,CAAC,KAAK,EAAE,GAAG,CAAE,CAAC;QAChE,IAAI,UAAU,CAAC,GAAG,KAAK,SAAS,IAAI,UAAU,CAAC,GAAG,KAAK,SAAS,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;IACtH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Internal (not exported from the package): marks the scene-radiance evaluators that the engine itself builds for\n * `simulateCapture` chart scenes (2.6). Their results are object literals with the schema's exact keys, constant\n * schema/quantity/unit/status fields, the request's identifiers and deep-frozen engine-owned uncertainty, evidence\n * and limitations; the native environment then skips re-parsing those fixed parts (see\n * `parseSceneRadianceEvaluationResultWithValidatedRecords`). Evaluators from callers are never marked and keep the\n * full validation. The mark is on the function object, which only the engine holds.\n */\nconst INTERNAL_EVALUATORS = new WeakSet<object>();\n\n/** Marks an engine-built evaluator and returns it. */\nexport function markInternalSceneRadianceEvaluator<T extends object>(evaluator: T): T {\n INTERNAL_EVALUATORS.add(evaluator);\n return evaluator;\n}\n\n/** True only for an evaluator marked by the engine. */\nexport function isInternalSceneRadianceEvaluator(evaluator: unknown): boolean {\n return typeof evaluator === \"function\" && INTERNAL_EVALUATORS.has(evaluator);\n}\n\n/** True when the value and everything reachable from it are frozen (primitives count as frozen). */\nexport function isDeeplyFrozen(value: unknown): boolean {\n if (value === null || typeof value !== \"object\") return typeof value !== \"function\";\n if (!Object.isFrozen(value)) return false;\n for (const key of Reflect.ownKeys(value)) {\n const descriptor = Object.getOwnPropertyDescriptor(value, key)!;\n if (descriptor.get !== undefined || descriptor.set !== undefined || !isDeeplyFrozen(descriptor.value)) return false;\n }\n return true;\n}\n"]}
1
+ {"version":3,"file":"internal-radiance-evaluator.js","sourceRoot":"","sources":["../../src/core/internal-radiance-evaluator.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC;;;;;;;GAOG;AACH,MAAM,mBAAmB,GAAG,IAAI,OAAO,EAAU,CAAC;AAElD,sDAAsD;AACtD,MAAM,UAAU,kCAAkC,CAAmB,SAAY;IAC/E,mBAAmB,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IACnC,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,gCAAgC,CAAC,SAAkB;IACjE,OAAO,OAAO,SAAS,KAAK,UAAU,IAAI,mBAAmB,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;AAC/E,CAAC;AAcD,MAAM,eAAe,GAAG,IAAI,OAAO,EAAkC,CAAC;AAEtE;;;;GAIG;AACH,MAAM,UAAU,8BAA8B,CAAC,SAAiB,EAAE,MAA8B;IAC9F,IAAI,mBAAmB,CAAC,GAAG,CAAC,SAAS,CAAC;QAAE,eAAe,CAAC,GAAG,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;AACjF,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,sBAAsB,CAAC,SAAkB;IACvD,OAAO,OAAO,SAAS,KAAK,UAAU,CAAC,CAAC,CAAC,eAAe,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACtF,CAAC;AAED;;;;;GAKG;AACH,MAAM,qBAAqB,GAAG,IAAI,OAAO,EAAU,CAAC;AAEpD,+DAA+D;AAC/D,MAAM,UAAU,4BAA4B,CAAmB,MAAS;IACtE,qBAAqB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAClC,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,wDAAwD;AACxD,MAAM,UAAU,0BAA0B,CAAC,MAAe;IACxD,OAAO,OAAO,MAAM,KAAK,UAAU,IAAI,qBAAqB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AAC3E,CAAC;AAED,oGAAoG;AACpG,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,OAAO,KAAK,KAAK,UAAU,CAAC;IACpF,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC1C,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzC,MAAM,UAAU,GAAG,MAAM,CAAC,wBAAwB,CAAC,KAAK,EAAE,GAAG,CAAE,CAAC;QAChE,IAAI,UAAU,CAAC,GAAG,KAAK,SAAS,IAAI,UAAU,CAAC,GAAG,KAAK,SAAS,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;IACtH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Internal (not exported from the package): marks the scene-radiance evaluators that the engine itself builds for\n * `simulateCapture` chart scenes (2.6). Their results are object literals with the schema's exact keys, constant\n * schema/quantity/unit/status fields, the request's identifiers and deep-frozen engine-owned uncertainty, evidence\n * and limitations; the native environment then skips re-parsing those fixed parts (see\n * `parseSceneRadianceEvaluationResultWithValidatedRecords`). Evaluators from callers are never marked and keep the\n * full validation. The mark is on the function object, which only the engine holds.\n */\nconst INTERNAL_EVALUATORS = new WeakSet<object>();\n\n/** Marks an engine-built evaluator and returns it. */\nexport function markInternalSceneRadianceEvaluator<T extends object>(evaluator: T): T {\n INTERNAL_EVALUATORS.add(evaluator);\n return evaluator;\n}\n\n/** True only for an evaluator marked by the engine. */\nexport function isInternalSceneRadianceEvaluator(evaluator: unknown): boolean {\n return typeof evaluator === \"function\" && INTERNAL_EVALUATORS.has(evaluator);\n}\n\n/**\n * Internal (#394): the lookup behind an engine-built evaluator whose result for an environment-direction query is a pure\n * function of a location key (a chart material, a convolved lattice site) and the wavelength.\n */\nexport interface InternalRadianceAccess {\n /** The key the evaluator reads for a query with this outgoing direction; undefined where it would throw. */\n keyOfDirection(direction: Readonly<{ x: number; y: number; z: number }>): number | undefined;\n /** The radiance the evaluator returns for a query of that key at that wavelength. */\n radianceOfKey(key: number, wavelengthNanometers: number): number;\n /** True when keys are few (chart materials), so the native environment may keep a table of their radiances. */\n tabulated: boolean;\n}\nconst RADIANCE_ACCESS = new WeakMap<object, InternalRadianceAccess>();\n\n/**\n * Registers the lookup of an engine-built evaluator (#394), so the native environment may take its radiances from the key\n * instead of calling it (its lean templates verify this against their own evaluations). Only for an evaluator already marked\n * internal; caller scenes never register.\n */\nexport function registerInternalRadianceAccess(evaluator: object, access: InternalRadianceAccess): void {\n if (INTERNAL_EVALUATORS.has(evaluator)) RADIANCE_ACCESS.set(evaluator, access);\n}\n\n/** The registered lookup of an engine-built evaluator, if any. */\nexport function internalRadianceAccess(evaluator: unknown): InternalRadianceAccess | undefined {\n return typeof evaluator === \"function\" ? RADIANCE_ACCESS.get(evaluator) : undefined;\n}\n\n/**\n * Internal (#394): marks the native tile readers that `simulateCapture` builds. Each call returns fresh site records built by\n * the engine (new top-level objects per site, shared nested records the engine never changes afterwards), never shown to\n * supplied code, so the native environment may plan them as engine-owned data without the private structured clone it makes\n * of a caller's tiles. Readers from callers are never marked. The mark is on the function object, which only the engine holds.\n */\nconst INTERNAL_TILE_READERS = new WeakSet<object>();\n\n/** Marks an engine-built native tile reader and returns it. */\nexport function markInternalNativeTileReader<T extends object>(reader: T): T {\n INTERNAL_TILE_READERS.add(reader);\n return reader;\n}\n\n/** True only for a tile reader marked by the engine. */\nexport function isInternalNativeTileReader(reader: unknown): boolean {\n return typeof reader === \"function\" && INTERNAL_TILE_READERS.has(reader);\n}\n\n/** True when the value and everything reachable from it are frozen (primitives count as frozen). */\nexport function isDeeplyFrozen(value: unknown): boolean {\n if (value === null || typeof value !== \"object\") return typeof value !== \"function\";\n if (!Object.isFrozen(value)) return false;\n for (const key of Reflect.ownKeys(value)) {\n const descriptor = Object.getOwnPropertyDescriptor(value, key)!;\n if (descriptor.get !== undefined || descriptor.set !== undefined || !isDeeplyFrozen(descriptor.value)) return false;\n }\n return true;\n}\n"]}
@@ -6,4 +6,4 @@
6
6
  * own versions. Release checks confirm it matches package.json and the packed package.
7
7
  * @see docs/RELEASE_1_0_1.md
8
8
  */
9
- export declare const ENGINE_API_VERSION: "2.7.0";
9
+ export declare const ENGINE_API_VERSION: "2.8.1";
@@ -7,5 +7,5 @@
7
7
  * own versions. Release checks confirm it matches package.json and the packed package.
8
8
  * @see docs/RELEASE_1_0_1.md
9
9
  */
10
- export const ENGINE_API_VERSION = "2.7.0";
10
+ export const ENGINE_API_VERSION = "2.8.1";
11
11
  //# sourceMappingURL=version.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/core/version.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,OAAgB,CAAC","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Version of the engine release. Since 1.0.1 it equals the npm package version.\n *\n * New captures and plans record this version as their creator. Archived records keep the\n * version they were made with. Proof-of-concept (POC), schema and model contracts have their\n * own versions. Release checks confirm it matches package.json and the packed package.\n * @see docs/RELEASE_1_0_1.md\n */\nexport const ENGINE_API_VERSION = \"2.7.0\" as const;\n"]}
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/core/version.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,OAAgB,CAAC","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Version of the engine release. Since 1.0.1 it equals the npm package version.\n *\n * New captures and plans record this version as their creator. Archived records keep the\n * version they were made with. Proof-of-concept (POC), schema and model contracts have their\n * own versions. Release checks confirm it matches package.json and the packed package.\n * @see docs/RELEASE_1_0_1.md\n */\nexport const ENGINE_API_VERSION = \"2.8.1\" as const;\n"]}
@@ -40,9 +40,9 @@
40
40
  * facade uses this path only when the lattice argument above holds. A query outside the window is refused too.
41
41
  */
42
42
  import { InvalidConfigurationError } from "../core/configuration-error.js";
43
- import { isInternalSceneRadianceEvaluator, markInternalSceneRadianceEvaluator } from "../core/internal-radiance-evaluator.js";
43
+ import { isInternalSceneRadianceEvaluator, markInternalSceneRadianceEvaluator, registerInternalRadianceAccess } from "../core/internal-radiance-evaluator.js";
44
44
  import { calculateIrradianceConvolution } from "../optics/irradiance-convolution.js";
45
- import { resolveLensPsfConvolutionKernels } from "../optics/lens-psf-kernels.js";
45
+ import { siteDiffractionKernel } from "../optics/site-diffraction-kernel.js";
46
46
  /** Rows of native sites per convolution band, and the bands kept. Banding changes memory only, never values. */
47
47
  const BAND_ROWS = 32;
48
48
  const CACHED_BANDS = 4;
@@ -71,15 +71,8 @@ export function createDiffractedPlanarProvider(input) {
71
71
  if (!(lx0 < lx1 && ly0 < ly1))
72
72
  throw new InvalidConfigurationError("Diffraction window must overlap the native raster.");
73
73
  const NW = lx1 - lx0;
74
- // One kernel per wavelength, on the site grid, from the engine's own tap-to-site mapping (field point 0, 0).
75
- const kernels = wavelengthsNm.map((wavelengthNm) => {
76
- const k = resolveLensPsfConvolutionKernels({ schemaVersion: "0.1.0", psf: input.psf, spectralWeights: [{ wavelengthNm, weight: 1 }],
77
- grid: { width: 1, height: 1, sitePitchMicrometers: { x: pitch, y: pitch }, opticalAxisSite: { x: 0.5, y: 0.5 } }, patchWidth: 1, patchHeight: 1,
78
- kernelRadius: R }).value;
79
- if (!k.exactSiteSampling || k.kernelRadius !== R)
80
- throw new InvalidConfigurationError("Diffraction kernels must sample the site pitch exactly.");
81
- return k.kernels;
82
- });
74
+ // One kernel per wavelength, on the site grid, from the engine's own tap-to-site mapping (field point 0, 0); kept across calls.
75
+ const kernels = wavelengthsNm.map((wavelengthNm) => siteDiffractionKernel(input.psf, wavelengthNm, pitch, R, "Diffraction kernels must sample the site pitch exactly."));
83
76
  const gridWidth = NW + 2 * R;
84
77
  const now = () => globalThis.performance?.now() ?? Date.now();
85
78
  const bands = new Map();
@@ -135,8 +128,36 @@ export function createDiffractedPlanarProvider(input) {
135
128
  };
136
129
  // 2.6: the result is the base result with only its radiance replaced (same keys and records), so an engine-built base
137
130
  // evaluator keeps its mark (see core/internal-radiance-evaluator.ts).
138
- if (isInternalSceneRadianceEvaluator(baseEvaluate))
131
+ if (isInternalSceneRadianceEvaluator(baseEvaluate)) {
139
132
  markInternalSceneRadianceEvaluator(evaluateRadiance);
133
+ // #394: its radiance is the convolved value at one lattice site (shift s, lattice column li, row lj) per wavelength, so
134
+ // the lean executor may read it by that key: the same plane point, lattice choice and band value as evaluateRadiance.
135
+ registerInternalRadianceAccess(evaluateRadiance, {
136
+ tabulated: false,
137
+ keyOfDirection(o) {
138
+ if (!(-o.z > 0))
139
+ return undefined;
140
+ const t = distance / -o.z, u = -o.x * t / scale, v = -o.y * t / scale, fi = u / g - 0.5 + W / 2, fj = -v / g - 0.5 + H / 2;
141
+ for (let s = 0; s < shifts.length; s++) {
142
+ const shift = shifts[s], i = Math.round(fi - shift.x), j = Math.round(fj - shift.y);
143
+ if (Math.abs(fi - shift.x - i) > LATTICE_TOLERANCE_SITES || Math.abs(fj - shift.y - j) > LATTICE_TOLERANCE_SITES)
144
+ continue;
145
+ const li = i + M, lj = j + M;
146
+ if (li < lx0 || lj < ly0 || li >= lx1 || lj >= ly1)
147
+ return undefined;
148
+ return (s * LH + lj) * LW + li;
149
+ }
150
+ return undefined;
151
+ },
152
+ radianceOfKey(key, nm) {
153
+ const w = index.get(nm);
154
+ if (w === undefined)
155
+ throw new InvalidConfigurationError("Diffraction query is off the site lattice; the object-space convolution does not apply.");
156
+ const li = key % LW, rest = (key - li) / LW, lj = rest % LH, s = (rest - lj) / LH;
157
+ return band(Math.floor((lj - ly0) / BAND_ROWS))[s][w][((lj - ly0) % BAND_ROWS) * NW + li - lx0];
158
+ }
159
+ });
160
+ }
140
161
  return {
141
162
  sceneId: base.sceneId, providerProfileId: base.providerProfileId, radianceAt: base.radianceAt,
142
163
  evaluateRadiance,
@@ -1 +1 @@
1
- {"version":3,"file":"diffraction-provider.js","sourceRoot":"","sources":["../../src/facade/diffraction-provider.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Fast execution of `simulateCapture`'s `lens.diffraction` (2.1): an object-space convolution in the scene\n * provider, instead of one scene query per PSF tap in the environment PSF quadrature.\n *\n * The environment quadrature computes, at each site centre q and spectral node λ,\n * E(q, λ) = Σ_k K_λ(k)·T·L(s_k(q), λ),\n * where s_k(q) is the tap's source point (`sensorPsfSourcePoint`), L the scene radiance at the plane point\n * that source point sees, and T the radiance-to-irradiance factor. For the facade's planar scene seen\n * through the ideal rectilinear projection with a still camera and no lens field mapping, field throughput or\n * pupil rays:\n * - T is the same at every sensor point (paraxial π/(4·N_w²), no cos⁴ term), and\n * - with the kernel sampled on the site pitch, every source point is the centre of another site, so the plane\n * points form one regular lattice: u = (i + ½ − W/2)·2/w, v = −(j + ½ − H/2)·2/w (frame units, native\n * site (i, j), W×H native sites, w output width).\n * So E(q, λ) = T·B(q, λ) with B = K_λ ⊛ L sampled on that lattice: the same sum, in a different floating-point\n * order. B is computed once per spectral node with the engine's isoplanatic convolution\n * (`calculateIrradianceConvolution`, kernels from `resolveLensPsfConvolutionKernels`, exact site sampling) over a\n * grid that holds every source (the native raster plus the kernel radius on each side), in row bands. The\n * provider then answers each site-centre query with B instead of L, and the environment request declares no\n * PSF. Incoherent imaging is a linear superposition of intensity PSFs, so convolving radiance on the object\n * plane and convolving irradiance on the image plane agree for a planar object and a space-invariant PSF\n * (MIT OpenCourseWare 2.71, lecture 22, free; also see: Goodman, Introduction to Fourier Optics, 4th ed., §7.1).\n *\n * With an anti-aliasing point-splitting filter (2.1, `sensor.antiAliasing`) the environment queries the PSF taps\n * around each pre-filter source point q − a (a = a split offset) instead of around q. Those taps lie on the site\n * lattice shifted by a, so the same argument holds on each shifted lattice: B is computed once per distinct\n * fractional shift (offsets that differ by whole sites share one lattice; the centred four-spot filter with a\n * one-site separation needs a single half-site-shifted lattice) over the native raster widened by the largest\n * offset, and the environment's spatial quadrature still forms the weighted split sum.\n *\n * Region renders (2.6, `window`): only the lattice rows and columns that the region's sites can query (the window\n * plus the largest offset on each side) are convolved, over a grid that adds the kernel radius on each side. Every\n * output value is the same gather as in the whole render: the grid always holds every tap of every value it keeps\n * (real scene radiance out to the kernel radius, never a truncated edge), the single kernel patch does not depend on\n * the grid's origin, and `convolveIrradianceRows` sums each value in a fixed tap order (v outer, u inner). Narrowing\n * the grid therefore removes only values that no query reads, and the kept values are bit-identical.\n *\n * A query that does not land on one of the lattices (within 1e-6 site) is refused rather than approximated: the\n * facade uses this path only when the lattice argument above holds. A query outside the window is refused too.\n */\n\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { isInternalSceneRadianceEvaluator, markInternalSceneRadianceEvaluator } from \"../core/internal-radiance-evaluator.js\";\nimport type { SceneRadianceEvaluationRequest, SceneRadianceEvaluationResult } from \"../schema/scene-radiance.js\";\nimport { calculateIrradianceConvolution } from \"../optics/irradiance-convolution.js\";\nimport { resolveLensPsfConvolutionKernels } from \"../optics/lens-psf-kernels.js\";\nimport type { ResolveLensSampledPsfInput } from \"../optics/lens-psf-profile.js\";\nimport type { PlanarSceneRadianceProvider } from \"./scene-radiance.js\";\n\n/** Rows of native sites per convolution band, and the bands kept. Banding changes memory only, never values. */\nconst BAND_ROWS = 32;\nconst CACHED_BANDS = 4;\nconst LATTICE_TOLERANCE_SITES = 1e-6;\n\nexport interface DiffractedPlanarProviderInput {\n base: PlanarSceneRadianceProvider;\n /** The optical state of the sampled PSF, as for `resolveLensSampledPsf`, without field point and wavelength. */\n psf: Omit<ResolveLensSampledPsfInput, \"fieldPointMm\" | \"wavelengthNm\">;\n /** Spectral nodes the environment queries (the profile's wavelength nodes). */\n wavelengthsNm: readonly number[];\n kernelRadius: number;\n nativeWidth: number;\n nativeHeight: number;\n /** Output (active) width in sites: frame units are 2/outputWidth per site. */\n outputWidth: number;\n sitePitchMicrometers: number;\n /** Planar geometry of the base provider, to map query directions to plane points exactly as it does. */\n distanceM: number;\n metresPerFrameUnit: number;\n /**\n * Offsets of the query points from the site centres, in sites (+x right, +y down): the anti-aliasing split\n * offsets with their sign reversed (the environment queries q − a). Default: one zero offset.\n */\n queryOffsetsSites?: readonly { x: number; y: number }[];\n /**\n * Region renders (2.6): the native sites that will be queried (x, y, width, height in the native raster). Only the\n * lattice they can reach is convolved; values are bit-identical to the whole render's. Default: the whole raster.\n */\n window?: { x: number; y: number; width: number; height: number };\n /** Called with the duration in ms of each band convolution (diagnostics only; never changes values). */\n onConvolution?: (milliseconds: number) => void;\n}\n\n/** Internal (not exported from the package): the base provider with diffraction applied as an object-space convolution. */\nexport function createDiffractedPlanarProvider(input: DiffractedPlanarProviderInput): PlanarSceneRadianceProvider {\n const { base, wavelengthsNm, nativeWidth: W, nativeHeight: H, kernelRadius: R } = input;\n const g = 2 / input.outputWidth, pitch = input.sitePitchMicrometers;\n const index = new Map(wavelengthsNm.map((nm, i) => [nm, i]));\n // One lattice per distinct fractional query offset; M extra sites per side hold the offset queries.\n const offsets = input.queryOffsetsSites ?? [{ x: 0, y: 0 }];\n const fraction = (value: number): number => { const f = value - Math.floor(value); return f > 1 - LATTICE_TOLERANCE_SITES ? 0 : f < LATTICE_TOLERANCE_SITES ? 0 : f; };\n const shifts: { x: number; y: number }[] = [];\n for (const o of offsets) {\n const f = { x: fraction(o.x), y: fraction(o.y) };\n if (!shifts.some((s) => Math.abs(s.x - f.x) <= LATTICE_TOLERANCE_SITES && Math.abs(s.y - f.y) <= LATTICE_TOLERANCE_SITES)) shifts.push(f);\n }\n const M = Math.max(0, ...offsets.map((o) => Math.ceil(Math.max(Math.abs(o.x), Math.abs(o.y)) - LATTICE_TOLERANCE_SITES)));\n const LW = W + 2 * M, LH = H + 2 * M;\n // Lattice window (indices i + M, j + M): the sites the region queries plus the largest offset on each side, clipped\n // to the lattice. A site i queries i + floor(offset), and floor(offset) lies in [-M, M].\n const win = input.window ?? { x: 0, y: 0, width: W, height: H };\n const lx0 = Math.max(0, win.x), lx1 = Math.min(LW, win.x + win.width + 2 * M);\n const ly0 = Math.max(0, win.y), ly1 = Math.min(LH, win.y + win.height + 2 * M);\n if (!(lx0 < lx1 && ly0 < ly1)) throw new InvalidConfigurationError(\"Diffraction window must overlap the native raster.\");\n const NW = lx1 - lx0;\n // One kernel per wavelength, on the site grid, from the engine's own tap-to-site mapping (field point 0, 0).\n const kernels = wavelengthsNm.map((wavelengthNm) => {\n const k = resolveLensPsfConvolutionKernels({ schemaVersion: \"0.1.0\", psf: input.psf, spectralWeights: [{ wavelengthNm, weight: 1 }],\n grid: { width: 1, height: 1, sitePitchMicrometers: { x: pitch, y: pitch }, opticalAxisSite: { x: 0.5, y: 0.5 } }, patchWidth: 1, patchHeight: 1,\n kernelRadius: R }).value;\n if (!k.exactSiteSampling || k.kernelRadius !== R) throw new InvalidConfigurationError(\"Diffraction kernels must sample the site pitch exactly.\");\n return k.kernels;\n });\n const gridWidth = NW + 2 * R;\n const now = (): number => globalThis.performance?.now() ?? Date.now();\n const bands = new Map<number, Float64Array[][]>();\n function band(b: number): Float64Array[][] {\n const cached = bands.get(b);\n if (cached) { bands.delete(b); bands.set(b, cached); return cached; }\n const started = input.onConvolution ? now() : 0;\n const y0 = ly0 + b * BAND_ROWS, rows = Math.min(BAND_ROWS, ly1 - y0), gridHeight = rows + 2 * R;\n const values = shifts.map((shift) => wavelengthsNm.map((nm, w) => {\n const samples = new Float64Array(gridWidth * gridHeight);\n for (let gy = 0; gy < gridHeight; gy++) {\n const v = shift.y === 0 ? -(y0 + gy - R - M + 0.5 - H / 2) * g : -(y0 + gy - R - M + 0.5 + shift.y - H / 2) * g;\n for (let gx = 0, x = lx0; gx < gridWidth; gx++, x++) samples[gy * gridWidth + gx] = base.radianceAt(shift.x === 0 ? (x - R - M + 0.5 - W / 2) * g : (x - R - M + 0.5 + shift.x - W / 2) * g, v, nm);\n }\n const blurred = calculateIrradianceConvolution({ schemaVersion: \"0.1.0\", width: gridWidth, height: gridHeight, samples, kernelRadius: R,\n patchWidth: gridWidth, patchHeight: gridHeight, kernels: kernels[w]!, boundary: \"zero-outside-grid\" }).values;\n const out = new Float64Array(NW * rows);\n for (let y = 0; y < rows; y++) out.set(blurred.subarray((y + R) * gridWidth + R, (y + R) * gridWidth + R + NW), y * NW);\n return out;\n }));\n bands.set(b, values);\n if (bands.size > CACHED_BANDS) bands.delete(bands.keys().next().value!);\n if (input.onConvolution) input.onConvolution(now() - started);\n return values;\n }\n function blurredAt(u: number, v: number, nm: number): number {\n const w = index.get(nm), fi = u / g - 0.5 + W / 2, fj = -v / g - 0.5 + H / 2;\n if (w !== undefined) for (let s = 0; s < shifts.length; s++) {\n const shift = shifts[s]!, i = Math.round(fi - shift.x), j = Math.round(fj - shift.y);\n if (Math.abs(fi - shift.x - i) > LATTICE_TOLERANCE_SITES || Math.abs(fj - shift.y - j) > LATTICE_TOLERANCE_SITES) continue;\n const li = i + M, lj = j + M;\n if (li < lx0 || lj < ly0 || li >= lx1 || lj >= ly1) break;\n return band(Math.floor((lj - ly0) / BAND_ROWS))[s]![w]![((lj - ly0) % BAND_ROWS) * NW + li - lx0]!;\n }\n throw new InvalidConfigurationError(\"Diffraction query is off the site lattice; the object-space convolution does not apply.\");\n }\n const scale = input.metresPerFrameUnit, distance = input.distanceM, baseEvaluate = base.evaluateRadiance;\n const evaluateRadiance = (request: Readonly<SceneRadianceEvaluationRequest>): SceneRadianceEvaluationResult => {\n const result = baseEvaluate.call(base, request), o = (request.target as { outgoingDirectionUnitVector: { x: number; y: number; z: number } }).outgoingDirectionUnitVector;\n const t = distance / -o.z;\n return { ...result, spectralRadianceWattsPerSquareMeterSteradianNanometer: blurredAt(-o.x * t / scale, -o.y * t / scale, request.wavelengthNanometers) };\n };\n // 2.6: the result is the base result with only its radiance replaced (same keys and records), so an engine-built base\n // evaluator keeps its mark (see core/internal-radiance-evaluator.ts).\n if (isInternalSceneRadianceEvaluator(baseEvaluate)) markInternalSceneRadianceEvaluator(evaluateRadiance);\n return {\n sceneId: base.sceneId, providerProfileId: base.providerProfileId, radianceAt: base.radianceAt,\n evaluateRadiance,\n evaluateApertureRadiance(): SceneRadianceEvaluationResult {\n throw new InvalidConfigurationError(\"Diffraction by object-space convolution does not take pupil rays.\");\n }\n };\n}\n"]}
1
+ {"version":3,"file":"diffraction-provider.js","sourceRoot":"","sources":["../../src/facade/diffraction-provider.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAC3E,OAAO,EAAE,gCAAgC,EAAE,kCAAkC,EAAE,8BAA8B,EAAE,MAAM,wCAAwC,CAAC;AAE9J,OAAO,EAAE,8BAA8B,EAAE,MAAM,qCAAqC,CAAC;AACrF,OAAO,EAAE,qBAAqB,EAAE,MAAM,sCAAsC,CAAC;AAI7E,gHAAgH;AAChH,MAAM,SAAS,GAAG,EAAE,CAAC;AACrB,MAAM,YAAY,GAAG,CAAC,CAAC;AACvB,MAAM,uBAAuB,GAAG,IAAI,CAAC;AA+BrC,2HAA2H;AAC3H,MAAM,UAAU,8BAA8B,CAAC,KAAoC;IACjF,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,WAAW,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,GAAG,KAAK,CAAC;IACxF,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,oBAAoB,CAAC;IACpE,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7D,oGAAoG;IACpG,MAAM,OAAO,GAAG,KAAK,CAAC,iBAAiB,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC5D,MAAM,QAAQ,GAAG,CAAC,KAAa,EAAU,EAAE,GAAG,MAAM,CAAC,GAAG,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,uBAAuB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,uBAAuB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACvK,MAAM,MAAM,GAA+B,EAAE,CAAC;IAC9C,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,MAAM,CAAC,GAAG,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACjD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,uBAAuB,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,uBAAuB,CAAC;YAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC5I,CAAC;IACD,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,uBAAuB,CAAC,CAAC,CAAC,CAAC;IAC1H,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACrC,oHAAoH;IACpH,yFAAyF;IACzF,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;IAChE,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,KAAK,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9E,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/E,IAAI,CAAC,CAAC,GAAG,GAAG,GAAG,IAAI,GAAG,GAAG,GAAG,CAAC;QAAE,MAAM,IAAI,yBAAyB,CAAC,oDAAoD,CAAC,CAAC;IACzH,MAAM,EAAE,GAAG,GAAG,GAAG,GAAG,CAAC;IACrB,gIAAgI;IAChI,MAAM,OAAO,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,YAAY,EAAE,EAAE,CAAC,qBAAqB,CAAC,KAAK,CAAC,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC,EAAE,yDAAyD,CAAC,CAAC,CAAC;IACzK,MAAM,SAAS,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;IAC7B,MAAM,GAAG,GAAG,GAAW,EAAE,CAAC,UAAU,CAAC,WAAW,EAAE,GAAG,EAAE,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;IACtE,MAAM,KAAK,GAAG,IAAI,GAAG,EAA4B,CAAC;IAClD,SAAS,IAAI,CAAC,CAAS;QACrB,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAC5B,IAAI,MAAM,EAAE,CAAC;YAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;YAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;YAAC,OAAO,MAAM,CAAC;QAAC,CAAC;QACrE,MAAM,OAAO,GAAG,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAChD,MAAM,EAAE,GAAG,GAAG,GAAG,CAAC,GAAG,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,GAAG,GAAG,EAAE,CAAC,EAAE,UAAU,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAChG,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE;YAC/D,MAAM,OAAO,GAAG,IAAI,YAAY,CAAC,SAAS,GAAG,UAAU,CAAC,CAAC;YACzD,KAAK,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,UAAU,EAAE,EAAE,EAAE,EAAE,CAAC;gBACvC,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,GAAG,GAAG,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;gBAChH,KAAK,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,EAAE,GAAG,SAAS,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE;oBAAE,OAAO,CAAC,EAAE,GAAG,SAAS,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,GAAG,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;YACtM,CAAC;YACD,MAAM,OAAO,GAAG,8BAA8B,CAAC,EAAE,aAAa,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC;gBACrI,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAE,EAAE,QAAQ,EAAE,mBAAmB,EAAE,CAAC,CAAC,MAAM,CAAC;YAChH,MAAM,GAAG,GAAG,IAAI,YAAY,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC;YACxC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE;gBAAE,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,SAAS,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,SAAS,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;YACxH,OAAO,GAAG,CAAC;QACb,CAAC,CAAC,CAAC,CAAC;QACJ,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QACrB,IAAI,KAAK,CAAC,IAAI,GAAG,YAAY;YAAE,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAM,CAAC,CAAC;QACxE,IAAI,KAAK,CAAC,aAAa;YAAE,KAAK,CAAC,aAAa,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,CAAC;QAC9D,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,SAAS,SAAS,CAAC,CAAS,EAAE,CAAS,EAAE,EAAU;QACjD,MAAM,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,GAAG,CAAC,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;QAC7E,IAAI,CAAC,KAAK,SAAS;YAAE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBAC5D,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAE,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;gBACrF,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,uBAAuB,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,uBAAuB;oBAAE,SAAS;gBAC3H,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;gBAC7B,IAAI,EAAE,GAAG,GAAG,IAAI,EAAE,GAAG,GAAG,IAAI,EAAE,IAAI,GAAG,IAAI,EAAE,IAAI,GAAG;oBAAE,MAAM;gBAC1D,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,CAAE,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,GAAG,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,GAAG,CAAE,CAAC;YACrG,CAAC;QACD,MAAM,IAAI,yBAAyB,CAAC,yFAAyF,CAAC,CAAC;IACjI,CAAC;IACD,MAAM,KAAK,GAAG,KAAK,CAAC,kBAAkB,EAAE,QAAQ,GAAG,KAAK,CAAC,SAAS,EAAE,YAAY,GAAG,IAAI,CAAC,gBAAgB,CAAC;IACzG,MAAM,gBAAgB,GAAG,CAAC,OAAiD,EAAiC,EAAE;QAC5G,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,CAAC,GAAI,OAAO,CAAC,MAA+E,CAAC,2BAA2B,CAAC;QAC1K,MAAM,CAAC,GAAG,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAC1B,OAAO,EAAE,GAAG,MAAM,EAAE,qDAAqD,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,EAAE,OAAO,CAAC,oBAAoB,CAAC,EAAE,CAAC;IAC3J,CAAC,CAAC;IACF,sHAAsH;IACtH,sEAAsE;IACtE,IAAI,gCAAgC,CAAC,YAAY,CAAC,EAAE,CAAC;QACnD,kCAAkC,CAAC,gBAAgB,CAAC,CAAC;QACrD,wHAAwH;QACxH,sHAAsH;QACtH,8BAA8B,CAAC,gBAAgB,EAAE;YAC/C,SAAS,EAAE,KAAK;YAChB,cAAc,CAAC,CAAC;gBACd,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;oBAAE,OAAO,SAAS,CAAC;gBAClC,MAAM,CAAC,GAAG,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,EAAE,EAAE,GAAG,CAAC,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;gBAC3H,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;oBACvC,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAE,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;oBACrF,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,uBAAuB,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,uBAAuB;wBAAE,SAAS;oBAC3H,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;oBAC7B,IAAI,EAAE,GAAG,GAAG,IAAI,EAAE,GAAG,GAAG,IAAI,EAAE,IAAI,GAAG,IAAI,EAAE,IAAI,GAAG;wBAAE,OAAO,SAAS,CAAC;oBACrE,OAAO,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC;gBACjC,CAAC;gBACD,OAAO,SAAS,CAAC;YACnB,CAAC;YACD,aAAa,CAAC,GAAG,EAAE,EAAE;gBACnB,MAAM,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;gBACxB,IAAI,CAAC,KAAK,SAAS;oBAAE,MAAM,IAAI,yBAAyB,CAAC,yFAAyF,CAAC,CAAC;gBACpJ,MAAM,EAAE,GAAG,GAAG,GAAG,EAAE,EAAE,IAAI,GAAG,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,EAAE,EAAE,EAAE,GAAG,IAAI,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC;gBAClF,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,CAAE,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,GAAG,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,GAAG,CAAE,CAAC;YACrG,CAAC;SACF,CAAC,CAAC;IACL,CAAC;IACD,OAAO;QACL,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,iBAAiB,EAAE,IAAI,CAAC,iBAAiB,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU;QAC7F,gBAAgB;QAChB,wBAAwB;YACtB,MAAM,IAAI,yBAAyB,CAAC,mEAAmE,CAAC,CAAC;QAC3G,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Fast execution of `simulateCapture`'s `lens.diffraction` (2.1): an object-space convolution in the scene\n * provider, instead of one scene query per PSF tap in the environment PSF quadrature.\n *\n * The environment quadrature computes, at each site centre q and spectral node λ,\n * E(q, λ) = Σ_k K_λ(k)·T·L(s_k(q), λ),\n * where s_k(q) is the tap's source point (`sensorPsfSourcePoint`), L the scene radiance at the plane point\n * that source point sees, and T the radiance-to-irradiance factor. For the facade's planar scene seen\n * through the ideal rectilinear projection with a still camera and no lens field mapping, field throughput or\n * pupil rays:\n * - T is the same at every sensor point (paraxial π/(4·N_w²), no cos⁴ term), and\n * - with the kernel sampled on the site pitch, every source point is the centre of another site, so the plane\n * points form one regular lattice: u = (i + ½ − W/2)·2/w, v = −(j + ½ − H/2)·2/w (frame units, native\n * site (i, j), W×H native sites, w output width).\n * So E(q, λ) = T·B(q, λ) with B = K_λ ⊛ L sampled on that lattice: the same sum, in a different floating-point\n * order. B is computed once per spectral node with the engine's isoplanatic convolution\n * (`calculateIrradianceConvolution`, kernels from `resolveLensPsfConvolutionKernels`, exact site sampling) over a\n * grid that holds every source (the native raster plus the kernel radius on each side), in row bands. The\n * provider then answers each site-centre query with B instead of L, and the environment request declares no\n * PSF. Incoherent imaging is a linear superposition of intensity PSFs, so convolving radiance on the object\n * plane and convolving irradiance on the image plane agree for a planar object and a space-invariant PSF\n * (MIT OpenCourseWare 2.71, lecture 22, free; also see: Goodman, Introduction to Fourier Optics, 4th ed., §7.1).\n *\n * With an anti-aliasing point-splitting filter (2.1, `sensor.antiAliasing`) the environment queries the PSF taps\n * around each pre-filter source point q − a (a = a split offset) instead of around q. Those taps lie on the site\n * lattice shifted by a, so the same argument holds on each shifted lattice: B is computed once per distinct\n * fractional shift (offsets that differ by whole sites share one lattice; the centred four-spot filter with a\n * one-site separation needs a single half-site-shifted lattice) over the native raster widened by the largest\n * offset, and the environment's spatial quadrature still forms the weighted split sum.\n *\n * Region renders (2.6, `window`): only the lattice rows and columns that the region's sites can query (the window\n * plus the largest offset on each side) are convolved, over a grid that adds the kernel radius on each side. Every\n * output value is the same gather as in the whole render: the grid always holds every tap of every value it keeps\n * (real scene radiance out to the kernel radius, never a truncated edge), the single kernel patch does not depend on\n * the grid's origin, and `convolveIrradianceRows` sums each value in a fixed tap order (v outer, u inner). Narrowing\n * the grid therefore removes only values that no query reads, and the kept values are bit-identical.\n *\n * A query that does not land on one of the lattices (within 1e-6 site) is refused rather than approximated: the\n * facade uses this path only when the lattice argument above holds. A query outside the window is refused too.\n */\n\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { isInternalSceneRadianceEvaluator, markInternalSceneRadianceEvaluator, registerInternalRadianceAccess } from \"../core/internal-radiance-evaluator.js\";\nimport type { SceneRadianceEvaluationRequest, SceneRadianceEvaluationResult } from \"../schema/scene-radiance.js\";\nimport { calculateIrradianceConvolution } from \"../optics/irradiance-convolution.js\";\nimport { siteDiffractionKernel } from \"../optics/site-diffraction-kernel.js\";\nimport type { ResolveLensSampledPsfInput } from \"../optics/lens-psf-profile.js\";\nimport type { PlanarSceneRadianceProvider } from \"./scene-radiance.js\";\n\n/** Rows of native sites per convolution band, and the bands kept. Banding changes memory only, never values. */\nconst BAND_ROWS = 32;\nconst CACHED_BANDS = 4;\nconst LATTICE_TOLERANCE_SITES = 1e-6;\n\nexport interface DiffractedPlanarProviderInput {\n base: PlanarSceneRadianceProvider;\n /** The optical state of the sampled PSF, as for `resolveLensSampledPsf`, without field point and wavelength. */\n psf: Omit<ResolveLensSampledPsfInput, \"fieldPointMm\" | \"wavelengthNm\">;\n /** Spectral nodes the environment queries (the profile's wavelength nodes). */\n wavelengthsNm: readonly number[];\n kernelRadius: number;\n nativeWidth: number;\n nativeHeight: number;\n /** Output (active) width in sites: frame units are 2/outputWidth per site. */\n outputWidth: number;\n sitePitchMicrometers: number;\n /** Planar geometry of the base provider, to map query directions to plane points exactly as it does. */\n distanceM: number;\n metresPerFrameUnit: number;\n /**\n * Offsets of the query points from the site centres, in sites (+x right, +y down): the anti-aliasing split\n * offsets with their sign reversed (the environment queries q − a). Default: one zero offset.\n */\n queryOffsetsSites?: readonly { x: number; y: number }[];\n /**\n * Region renders (2.6): the native sites that will be queried (x, y, width, height in the native raster). Only the\n * lattice they can reach is convolved; values are bit-identical to the whole render's. Default: the whole raster.\n */\n window?: { x: number; y: number; width: number; height: number };\n /** Called with the duration in ms of each band convolution (diagnostics only; never changes values). */\n onConvolution?: (milliseconds: number) => void;\n}\n\n/** Internal (not exported from the package): the base provider with diffraction applied as an object-space convolution. */\nexport function createDiffractedPlanarProvider(input: DiffractedPlanarProviderInput): PlanarSceneRadianceProvider {\n const { base, wavelengthsNm, nativeWidth: W, nativeHeight: H, kernelRadius: R } = input;\n const g = 2 / input.outputWidth, pitch = input.sitePitchMicrometers;\n const index = new Map(wavelengthsNm.map((nm, i) => [nm, i]));\n // One lattice per distinct fractional query offset; M extra sites per side hold the offset queries.\n const offsets = input.queryOffsetsSites ?? [{ x: 0, y: 0 }];\n const fraction = (value: number): number => { const f = value - Math.floor(value); return f > 1 - LATTICE_TOLERANCE_SITES ? 0 : f < LATTICE_TOLERANCE_SITES ? 0 : f; };\n const shifts: { x: number; y: number }[] = [];\n for (const o of offsets) {\n const f = { x: fraction(o.x), y: fraction(o.y) };\n if (!shifts.some((s) => Math.abs(s.x - f.x) <= LATTICE_TOLERANCE_SITES && Math.abs(s.y - f.y) <= LATTICE_TOLERANCE_SITES)) shifts.push(f);\n }\n const M = Math.max(0, ...offsets.map((o) => Math.ceil(Math.max(Math.abs(o.x), Math.abs(o.y)) - LATTICE_TOLERANCE_SITES)));\n const LW = W + 2 * M, LH = H + 2 * M;\n // Lattice window (indices i + M, j + M): the sites the region queries plus the largest offset on each side, clipped\n // to the lattice. A site i queries i + floor(offset), and floor(offset) lies in [-M, M].\n const win = input.window ?? { x: 0, y: 0, width: W, height: H };\n const lx0 = Math.max(0, win.x), lx1 = Math.min(LW, win.x + win.width + 2 * M);\n const ly0 = Math.max(0, win.y), ly1 = Math.min(LH, win.y + win.height + 2 * M);\n if (!(lx0 < lx1 && ly0 < ly1)) throw new InvalidConfigurationError(\"Diffraction window must overlap the native raster.\");\n const NW = lx1 - lx0;\n // One kernel per wavelength, on the site grid, from the engine's own tap-to-site mapping (field point 0, 0); kept across calls.\n const kernels = wavelengthsNm.map((wavelengthNm) => siteDiffractionKernel(input.psf, wavelengthNm, pitch, R, \"Diffraction kernels must sample the site pitch exactly.\"));\n const gridWidth = NW + 2 * R;\n const now = (): number => globalThis.performance?.now() ?? Date.now();\n const bands = new Map<number, Float64Array[][]>();\n function band(b: number): Float64Array[][] {\n const cached = bands.get(b);\n if (cached) { bands.delete(b); bands.set(b, cached); return cached; }\n const started = input.onConvolution ? now() : 0;\n const y0 = ly0 + b * BAND_ROWS, rows = Math.min(BAND_ROWS, ly1 - y0), gridHeight = rows + 2 * R;\n const values = shifts.map((shift) => wavelengthsNm.map((nm, w) => {\n const samples = new Float64Array(gridWidth * gridHeight);\n for (let gy = 0; gy < gridHeight; gy++) {\n const v = shift.y === 0 ? -(y0 + gy - R - M + 0.5 - H / 2) * g : -(y0 + gy - R - M + 0.5 + shift.y - H / 2) * g;\n for (let gx = 0, x = lx0; gx < gridWidth; gx++, x++) samples[gy * gridWidth + gx] = base.radianceAt(shift.x === 0 ? (x - R - M + 0.5 - W / 2) * g : (x - R - M + 0.5 + shift.x - W / 2) * g, v, nm);\n }\n const blurred = calculateIrradianceConvolution({ schemaVersion: \"0.1.0\", width: gridWidth, height: gridHeight, samples, kernelRadius: R,\n patchWidth: gridWidth, patchHeight: gridHeight, kernels: kernels[w]!, boundary: \"zero-outside-grid\" }).values;\n const out = new Float64Array(NW * rows);\n for (let y = 0; y < rows; y++) out.set(blurred.subarray((y + R) * gridWidth + R, (y + R) * gridWidth + R + NW), y * NW);\n return out;\n }));\n bands.set(b, values);\n if (bands.size > CACHED_BANDS) bands.delete(bands.keys().next().value!);\n if (input.onConvolution) input.onConvolution(now() - started);\n return values;\n }\n function blurredAt(u: number, v: number, nm: number): number {\n const w = index.get(nm), fi = u / g - 0.5 + W / 2, fj = -v / g - 0.5 + H / 2;\n if (w !== undefined) for (let s = 0; s < shifts.length; s++) {\n const shift = shifts[s]!, i = Math.round(fi - shift.x), j = Math.round(fj - shift.y);\n if (Math.abs(fi - shift.x - i) > LATTICE_TOLERANCE_SITES || Math.abs(fj - shift.y - j) > LATTICE_TOLERANCE_SITES) continue;\n const li = i + M, lj = j + M;\n if (li < lx0 || lj < ly0 || li >= lx1 || lj >= ly1) break;\n return band(Math.floor((lj - ly0) / BAND_ROWS))[s]![w]![((lj - ly0) % BAND_ROWS) * NW + li - lx0]!;\n }\n throw new InvalidConfigurationError(\"Diffraction query is off the site lattice; the object-space convolution does not apply.\");\n }\n const scale = input.metresPerFrameUnit, distance = input.distanceM, baseEvaluate = base.evaluateRadiance;\n const evaluateRadiance = (request: Readonly<SceneRadianceEvaluationRequest>): SceneRadianceEvaluationResult => {\n const result = baseEvaluate.call(base, request), o = (request.target as { outgoingDirectionUnitVector: { x: number; y: number; z: number } }).outgoingDirectionUnitVector;\n const t = distance / -o.z;\n return { ...result, spectralRadianceWattsPerSquareMeterSteradianNanometer: blurredAt(-o.x * t / scale, -o.y * t / scale, request.wavelengthNanometers) };\n };\n // 2.6: the result is the base result with only its radiance replaced (same keys and records), so an engine-built base\n // evaluator keeps its mark (see core/internal-radiance-evaluator.ts).\n if (isInternalSceneRadianceEvaluator(baseEvaluate)) {\n markInternalSceneRadianceEvaluator(evaluateRadiance);\n // #394: its radiance is the convolved value at one lattice site (shift s, lattice column li, row lj) per wavelength, so\n // the lean executor may read it by that key: the same plane point, lattice choice and band value as evaluateRadiance.\n registerInternalRadianceAccess(evaluateRadiance, {\n tabulated: false,\n keyOfDirection(o): number | undefined {\n if (!(-o.z > 0)) return undefined;\n const t = distance / -o.z, u = -o.x * t / scale, v = -o.y * t / scale, fi = u / g - 0.5 + W / 2, fj = -v / g - 0.5 + H / 2;\n for (let s = 0; s < shifts.length; s++) {\n const shift = shifts[s]!, i = Math.round(fi - shift.x), j = Math.round(fj - shift.y);\n if (Math.abs(fi - shift.x - i) > LATTICE_TOLERANCE_SITES || Math.abs(fj - shift.y - j) > LATTICE_TOLERANCE_SITES) continue;\n const li = i + M, lj = j + M;\n if (li < lx0 || lj < ly0 || li >= lx1 || lj >= ly1) return undefined;\n return (s * LH + lj) * LW + li;\n }\n return undefined;\n },\n radianceOfKey(key, nm): number {\n const w = index.get(nm);\n if (w === undefined) throw new InvalidConfigurationError(\"Diffraction query is off the site lattice; the object-space convolution does not apply.\");\n const li = key % LW, rest = (key - li) / LW, lj = rest % LH, s = (rest - lj) / LH;\n return band(Math.floor((lj - ly0) / BAND_ROWS))[s]![w]![((lj - ly0) % BAND_ROWS) * NW + li - lx0]!;\n }\n });\n }\n return {\n sceneId: base.sceneId, providerProfileId: base.providerProfileId, radianceAt: base.radianceAt,\n evaluateRadiance,\n evaluateApertureRadiance(): SceneRadianceEvaluationResult {\n throw new InvalidConfigurationError(\"Diffraction by object-space convolution does not take pupil rays.\");\n }\n };\n}\n"]}
@@ -2,89 +2,8 @@
2
2
  export declare const DISPLAY_VIEW_JITTER_STREAM_ID: 32;
3
3
  export declare const DISPLAY_VIEW_NOISE_STREAM_ID: 33;
4
4
  export declare const DISPLAY_VIEW_AHD_STREAM_ID: 34;
5
- /** A query offset from a site centre in sites (+x right, +y down) and its weight (pixel-aperture node × filter spot). */
6
- export interface DisplayViewQueryOffset {
7
- x: number;
8
- y: number;
9
- weight: number;
10
- }
11
- /** Scene access for the expected signal: chart materials (fast tables) or a radiance function. */
12
- export type DisplayViewScene = {
13
- kind: "materials";
14
- materialAt(u: number, v: number): number;
15
- /** Per material: `{ neutral: reflectance }` (radiance = reflectance × illuminant) or the spectral radiance at each node. */
16
- materials: readonly ({
17
- neutral: number;
18
- } | {
19
- radiance: Float64Array;
20
- })[];
21
- /** Illuminant radiance of a perfect white diffuser at each spectral node: irradianceScale·shape(λ)/π. */
22
- whiteRadiance: Float64Array;
23
- } | {
24
- kind: "radiance";
25
- radianceAt(u: number, v: number, nm: number): number;
26
- };
27
- export interface DisplayViewExpectedSignalInput {
28
- nativeWidth: number;
29
- nativeHeight: number;
30
- /** Frame output width: plane coordinates are 2 / outputWidth frame units per site. */
31
- outputWidth: number;
32
- /** Spectral nodes and, per CFA channel (red, green, blue), the expected electrons per unit spectral radiance at each node. */
33
- wavelengthsNm: readonly number[];
34
- electronsPerRadiance: readonly [Float64Array, Float64Array, Float64Array];
35
- offsets: readonly DisplayViewQueryOffset[];
36
- /** Diffraction: per-node kernels on the site lattice, (2R + 1)² taps in row-major order, out(x) = Σ in(x − t)·K(t). */
37
- kernels?: {
38
- radius: number;
39
- perNode: readonly Float64Array[];
40
- };
41
- scene: DisplayViewScene;
42
- /** CFA channel index (0 red, 1 green, 2 blue) of native site (x, y). */
43
- channelAt(x: number, y: number): number;
44
- /**
45
- * Without diffraction: the plane point (frame units) that the environment queries for offset `o` of native site (x, y),
46
- * computed with the environment's own arithmetic, so points that fall exactly on a chart boundary resolve to the same
47
- * material as in the capture. With diffraction the facade's object-space convolution uses the lattice formula, as here.
48
- */
49
- queryPoint?(x: number, y: number, offset: number, out: Float64Array): void;
50
- }
51
- /** Expected photo electrons of real native sites. */
52
- export interface DisplayViewExpectedSignal {
53
- /**
54
- * Starts a new cache window: native sites [x0, x1) × [y0, y1) that the following `site` calls mostly read. Sites and
55
- * scene lookups inside it are computed once; anything outside is computed without caching.
56
- */
57
- window(x0: number, y0: number, x1: number, y1: number): void;
58
- /** Expected photo electrons of native site (x, y) for its own CFA channel (x, y inside the native raster). */
59
- site(x: number, y: number): number;
60
- /** A cheap classifier value of the scene at plane point (u, v) frame units: the material, or the radiance at one node. */
61
- pilot(u: number, v: number): number;
62
- /** Scene lookups performed so far (material or radiance queries), for diagnostics. */
63
- readonly lookups: number;
64
- /**
65
- * The chart material that every kernel tap of the last `site` call read, when they all read one material (its value is
66
- * then a function of that material and the site's CFA channel only); −1 when unknown or mixed.
67
- */
68
- readonly uniformMaterial: number;
69
- /** Whether `uniformMaterial` is tracked in the current window (otherwise it is not meaningful). */
70
- readonly tracksMaterial: boolean;
71
- /**
72
- * Whether the current window caches its sites: a second `site` call for a site inside the window then returns the stored
73
- * value without any work or lookup (#392).
74
- */
75
- readonly cachesSites: boolean;
76
- }
77
- /**
78
- * Expected photo electrons per site: μ_c(i, j) = Σ_o w_o Σ_λ A_c(λ) Σ_t K_λ(t)·L(p_o(i, j) − t, λ), where p_o is the
79
- * query point of offset o and K the diffraction kernel (a single unit tap without diffraction). This is the sum the
80
- * facade's environment chain forms for a still, rectilinear, distortion-free planar scene, where the
81
- * radiance-to-irradiance factor is the same at every site (docs/DISPLAY_VIEW.md, "Expected signal").
82
- *
83
- * With diffraction, offsets that share a fractional shift read one lattice, and their kernels are combined in advance:
84
- * C_c(d) = Σ_members w·Σ_λ A_c(λ)·K_λ(t)·L(λ) over the taps t that reach lattice offset d. A neutral material's radiance
85
- * is its reflectance times the white radiance, so all neutral materials share one combined kernel per channel.
86
- */
87
- export declare function createDisplayViewExpectedSignal(input: DisplayViewExpectedSignalInput): DisplayViewExpectedSignal;
5
+ export { createDisplayViewExpectedSignal, type DisplayViewExpectedSignal, type DisplayViewExpectedSignalInput, type DisplayViewQueryOffset, type DisplayViewScene } from "./expected-signal.js";
6
+ import type { DisplayViewExpectedSignal } from "./expected-signal.js";
88
7
  /**
89
8
  * Internal (exported for tests). Moments of min(Y, top) for Y ~ N(m, v): mean, variance and the slope d E/d m = P(Y < top). Exact for a normal Y:
90
9
  * E[min(Y,t)] = m − s·(φ(z) − z·(1 − Φ(z))), z = (t − m)/s.
@@ -170,6 +89,11 @@ export interface DisplayViewRenderResult {
170
89
  uniformPixelCount: number;
171
90
  /** Display pixels whose expected value includes the AHD low-signal correction. */
172
91
  ahdCorrectedPixelCount: number;
92
+ /**
93
+ * 2.8 (#393): "exact-area-mean" when each display pixel averaged every real pixel of its footprint once, with its exact
94
+ * overlap weight (views where a footprint holds at most K² real pixels); "stratified-monte-carlo" otherwise.
95
+ */
96
+ estimation: "exact-area-mean" | "stratified-monte-carlo";
173
97
  }
174
98
  /** One tap of a linear demosaic kernel: the site at (dx, dy) from the pixel, and its weight. */
175
99
  export interface DisplayViewKernelTap {
@@ -209,5 +133,12 @@ export declare function footprintWeights(x: AxisSums, y: AxisSums, area: number,
209
133
  /** Internal (exported for tests): the correction states kept across calls, and clearing them. */
210
134
  export declare function displayViewCorrectionStateCount(): number;
211
135
  export declare function clearDisplayViewCorrectionStates(): void;
136
+ /**
137
+ * Most real pixels a footprint may hold for the exact area mean (#393). Measured on 8-row strips of a 24 MP view (Siemens star
138
+ * and dead leaves, one thread): the exact path matched the stratified estimator's time within run-to-run spread up to 25 real
139
+ * pixels per display pixel and was slower above (1.2× at 36, 1.4–1.5× at 64, 1.5–2.4× at 144), because every real pixel is
140
+ * evaluated while the stratified estimator takes a quarter of its samples per axis where the pilot finds no detail.
141
+ */
142
+ export declare const EXACT_AREA_MEAN_MAX_PIXELS = 25;
212
143
  /** Renders the display view (docs/DISPLAY_VIEW.md). */
213
144
  export declare function renderDisplayView(setup: DisplayViewSetup): Promise<DisplayViewRenderResult>;