@photivra/engine 0.5.0 → 0.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.
Files changed (79) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/CITATION.cff +1 -1
  3. package/README.md +37 -3
  4. package/dist/index.d.ts +15 -2
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +15 -2
  7. package/dist/index.js.map +1 -1
  8. package/dist/motion/camera-rotation.d.ts +60 -0
  9. package/dist/motion/camera-rotation.d.ts.map +1 -1
  10. package/dist/motion/camera-rotation.js +95 -0
  11. package/dist/motion/camera-rotation.js.map +1 -1
  12. package/dist/motion/capture-rotation-inverse-mapping.d.ts +100 -0
  13. package/dist/motion/capture-rotation-inverse-mapping.d.ts.map +1 -0
  14. package/dist/motion/capture-rotation-inverse-mapping.js +171 -0
  15. package/dist/motion/capture-rotation-inverse-mapping.js.map +1 -0
  16. package/dist/motion/capture-rotation-temporal-quadrature.d.ts +89 -0
  17. package/dist/motion/capture-rotation-temporal-quadrature.d.ts.map +1 -0
  18. package/dist/motion/capture-rotation-temporal-quadrature.js +116 -0
  19. package/dist/motion/capture-rotation-temporal-quadrature.js.map +1 -0
  20. package/dist/motion/capture-rotation-trajectory.d.ts +98 -0
  21. package/dist/motion/capture-rotation-trajectory.d.ts.map +1 -0
  22. package/dist/motion/capture-rotation-trajectory.js +153 -0
  23. package/dist/motion/capture-rotation-trajectory.js.map +1 -0
  24. package/dist/optics/lateral-chromatic-aberration.d.ts.map +1 -1
  25. package/dist/optics/lateral-chromatic-aberration.js +12 -4
  26. package/dist/optics/lateral-chromatic-aberration.js.map +1 -1
  27. package/dist/optics/radial-distortion.d.ts +4 -2
  28. package/dist/optics/radial-distortion.d.ts.map +1 -1
  29. package/dist/optics/radial-distortion.js +103 -28
  30. package/dist/optics/radial-distortion.js.map +1 -1
  31. package/dist/output/capture-geometry.d.ts.map +1 -1
  32. package/dist/output/capture-geometry.js +24 -11
  33. package/dist/output/capture-geometry.js.map +1 -1
  34. package/dist/sensor/capture-color-sampling-binding.d.ts +211 -0
  35. package/dist/sensor/capture-color-sampling-binding.d.ts.map +1 -0
  36. package/dist/sensor/capture-color-sampling-binding.js +423 -0
  37. package/dist/sensor/capture-color-sampling-binding.js.map +1 -0
  38. package/dist/sensor/capture-mode.d.ts +157 -0
  39. package/dist/sensor/capture-mode.d.ts.map +1 -0
  40. package/dist/sensor/capture-mode.js +434 -0
  41. package/dist/sensor/capture-mode.js.map +1 -0
  42. package/dist/sensor/color-sampling.d.ts +133 -0
  43. package/dist/sensor/color-sampling.d.ts.map +1 -0
  44. package/dist/sensor/color-sampling.js +226 -0
  45. package/dist/sensor/color-sampling.js.map +1 -0
  46. package/dist/sensor/exposure-window.d.ts +103 -0
  47. package/dist/sensor/exposure-window.d.ts.map +1 -0
  48. package/dist/sensor/exposure-window.js +291 -0
  49. package/dist/sensor/exposure-window.js.map +1 -0
  50. package/dist/sensor/optical-stack.d.ts +132 -0
  51. package/dist/sensor/optical-stack.d.ts.map +1 -0
  52. package/dist/sensor/optical-stack.js +286 -0
  53. package/dist/sensor/optical-stack.js.map +1 -0
  54. package/dist/sensor/readout-exposure-linkage.d.ts +111 -0
  55. package/dist/sensor/readout-exposure-linkage.d.ts.map +1 -0
  56. package/dist/sensor/readout-exposure-linkage.js +210 -0
  57. package/dist/sensor/readout-exposure-linkage.js.map +1 -0
  58. package/dist/sensor/readout-timing.d.ts +124 -0
  59. package/dist/sensor/readout-timing.d.ts.map +1 -0
  60. package/dist/sensor/readout-timing.js +230 -0
  61. package/dist/sensor/readout-timing.js.map +1 -0
  62. package/dist/sensor/sampling-aperture.d.ts +154 -0
  63. package/dist/sensor/sampling-aperture.d.ts.map +1 -0
  64. package/dist/sensor/sampling-aperture.js +374 -0
  65. package/dist/sensor/sampling-aperture.js.map +1 -0
  66. package/dist/sensor/spatial-sample-reduction.d.ts +128 -0
  67. package/dist/sensor/spatial-sample-reduction.d.ts.map +1 -0
  68. package/dist/sensor/spatial-sample-reduction.js +299 -0
  69. package/dist/sensor/spatial-sample-reduction.js.map +1 -0
  70. package/dist/sensor/spatial-sampling-quadrature.d.ts +155 -0
  71. package/dist/sensor/spatial-sampling-quadrature.d.ts.map +1 -0
  72. package/dist/sensor/spatial-sampling-quadrature.js +197 -0
  73. package/dist/sensor/spatial-sampling-quadrature.js.map +1 -0
  74. package/docs/ARCHITECTURE.md +17 -0
  75. package/docs/AUTOMATION_COORDINATION.md +188 -0
  76. package/docs/IMAGE_FORMATION.md +26 -0
  77. package/docs/MOTION_AND_SIGNAL.md +142 -0
  78. package/docs/USAGE.md +1273 -3
  79. package/package.json +5 -3
@@ -0,0 +1,197 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ import { approximationResult } from "../core/calculation-result.js";
3
+ import { InvalidScientificInputError, requirePositiveInteger } from "../core/validation.js";
4
+ import { resolveAntiAliasingSpatialKernel } from "./optical-stack.js";
5
+ import { resolveSensorSamplingAperture } from "./sampling-aperture.js";
6
+ const MAX_SPATIAL_QUADRATURE_NODES = 100_000;
7
+ function midpointCoordinate(index, count) {
8
+ return (index + 0.5) / count;
9
+ }
10
+ function insideImagingArea(pointMm, imagingArea) {
11
+ const halfWidth = imagingArea.widthMm / 2;
12
+ const halfHeight = imagingArea.heightMm / 2;
13
+ return (pointMm.x >= -halfWidth &&
14
+ pointMm.x <= halfWidth &&
15
+ pointMm.y >= -halfHeight &&
16
+ pointMm.y <= halfHeight);
17
+ }
18
+ /**
19
+ * Builds deterministic spatial quadrature nodes for one CFA/color-sampling site.
20
+ *
21
+ * The quadrature composes:
22
+ *
23
+ * 1. the resolved geometric sensitive aperture from the sampling-aperture
24
+ * foundation; and
25
+ * 2. the resolved effective AA point-splitting kernel from the optical-stack
26
+ * foundation.
27
+ *
28
+ * The first aperture integration uses a tensor-product uniform midpoint rule.
29
+ * For an AA component that moves optical energy by +delta on the sensor plane,
30
+ * a destination aperture point samples the pre-AA optical field at
31
+ * destination - delta. This inverse-source convention prevents applying the AA
32
+ * shift twice.
33
+ *
34
+ * The destination sensor site's CFA channel applies to every node. The
35
+ * pre-AA source coordinate is an optical-field location, not another CFA site;
36
+ * therefore shifted source coordinates never reassign the destination channel.
37
+ *
38
+ * Nodes whose pre-AA source coordinates fall outside the active imaging area
39
+ * are retained. They are neither clamped nor dropped/renormalized. A renderer
40
+ * or optical-field provider owns source-coverage policy.
41
+ *
42
+ * normalized weights approximate a spatial average. Square-micrometre measures
43
+ * approximate a geometric area integral. Neither includes radiometric
44
+ * throughput, QE, photon conversion, microlenses, diffusion/crosstalk, or
45
+ * temporal integration.
46
+ */
47
+ export function calculateSensorSpatialSamplingQuadrature(input) {
48
+ requirePositiveInteger("spatialSampleCountX", input.spatialSampleCountX);
49
+ requirePositiveInteger("spatialSampleCountY", input.spatialSampleCountY);
50
+ const samplingAperture = resolveSensorSamplingAperture({
51
+ imagingArea: input.imagingArea,
52
+ nativeRaster: input.nativeRaster,
53
+ colorSamplingProfile: input.colorSamplingProfile,
54
+ colorSamplingBindingProfile: input.colorSamplingBindingProfile,
55
+ samplingApertureProfile: input.samplingApertureProfile,
56
+ site: input.site
57
+ });
58
+ const antiAliasing = resolveAntiAliasingSpatialKernel(input.opticalStackProfile);
59
+ const apertureNodeCount = input.spatialSampleCountX *
60
+ input.spatialSampleCountY;
61
+ if (!Number.isSafeInteger(apertureNodeCount)) {
62
+ throw new InvalidScientificInputError("spatialSampleCountX × spatialSampleCountY must be a safe integer.");
63
+ }
64
+ const totalNodeCount = apertureNodeCount *
65
+ antiAliasing.components.length;
66
+ if (!Number.isSafeInteger(totalNodeCount)) {
67
+ throw new InvalidScientificInputError("Spatial quadrature node count must be a safe integer.");
68
+ }
69
+ if (totalNodeCount >
70
+ MAX_SPATIAL_QUADRATURE_NODES) {
71
+ throw new InvalidScientificInputError("Spatial quadrature node count exceeds the per-site safety limit of " +
72
+ MAX_SPATIAL_QUADRATURE_NODES +
73
+ ". Increase fidelity by tiling or batching downstream work rather than allocating one oversized node set.");
74
+ }
75
+ const aperture = samplingAperture.geometricSensitiveAperture;
76
+ const normalizedApertureWeight = 1 / apertureNodeCount;
77
+ const apertureAreaMeasureSquareMicrometers = aperture.areaSquareMicrometers /
78
+ apertureNodeCount;
79
+ const apertureCenterMm = aperture.centerFromOpticalAxisMm;
80
+ const widthMm = aperture.widthMicrometers / 1000;
81
+ const heightMm = aperture.heightMicrometers / 1000;
82
+ const leftMm = apertureCenterMm.x - widthMm / 2;
83
+ const topMm = apertureCenterMm.y - heightMm / 2;
84
+ const nodes = [];
85
+ let outsideCount = 0;
86
+ antiAliasing.components.forEach((aaComponent, antiAliasingComponentIndex) => {
87
+ const aaOffsetMm = {
88
+ x: aaComponent.offsetMicrometers.x /
89
+ 1000,
90
+ y: aaComponent.offsetMicrometers.y /
91
+ 1000
92
+ };
93
+ for (let apertureSampleYIndex = 0; apertureSampleYIndex <
94
+ input.spatialSampleCountY; apertureSampleYIndex += 1) {
95
+ const yPhase = midpointCoordinate(apertureSampleYIndex, input.spatialSampleCountY);
96
+ const destinationY = topMm + yPhase * heightMm;
97
+ for (let apertureSampleXIndex = 0; apertureSampleXIndex <
98
+ input.spatialSampleCountX; apertureSampleXIndex += 1) {
99
+ const xPhase = midpointCoordinate(apertureSampleXIndex, input.spatialSampleCountX);
100
+ const destinationX = leftMm + xPhase * widthMm;
101
+ const destinationAperturePointMm = {
102
+ x: destinationX,
103
+ y: destinationY
104
+ };
105
+ const preAntiAliasingSourcePointMm = {
106
+ x: destinationX -
107
+ aaOffsetMm.x,
108
+ y: destinationY -
109
+ aaOffsetMm.y
110
+ };
111
+ const sourceInside = insideImagingArea(preAntiAliasingSourcePointMm, input.imagingArea);
112
+ if (!sourceInside) {
113
+ outsideCount += 1;
114
+ }
115
+ const combinedNormalizedSpatialWeight = normalizedApertureWeight *
116
+ aaComponent.normalizedWeight;
117
+ const combinedAreaMeasureSquareMicrometers = apertureAreaMeasureSquareMicrometers *
118
+ aaComponent.normalizedWeight;
119
+ nodes.push({
120
+ antiAliasingComponentIndex,
121
+ apertureSampleXIndex,
122
+ apertureSampleYIndex,
123
+ destinationAperturePointMm,
124
+ antiAliasingOffsetMicrometers: {
125
+ ...aaComponent.offsetMicrometers
126
+ },
127
+ preAntiAliasingSourcePointMm,
128
+ preAntiAliasingSourceInsideImagingArea: sourceInside,
129
+ normalizedApertureWeight,
130
+ normalizedAntiAliasingWeight: aaComponent.normalizedWeight,
131
+ combinedNormalizedSpatialWeight,
132
+ apertureAreaMeasureSquareMicrometers,
133
+ combinedAreaMeasureSquareMicrometers
134
+ });
135
+ }
136
+ }
137
+ });
138
+ const normalizedSpatialWeightSum = nodes.reduce((sum, node) => sum +
139
+ node.combinedNormalizedSpatialWeight, 0);
140
+ const combinedAreaMeasureSumSquareMicrometers = nodes.reduce((sum, node) => sum +
141
+ node.combinedAreaMeasureSquareMicrometers, 0);
142
+ return approximationResult({
143
+ coordinateSystem: "native-sensor-physical",
144
+ site: {
145
+ ...samplingAperture.site
146
+ },
147
+ channelId: samplingAperture.channelId,
148
+ quadratureScheme: "tensor-product-uniform-midpoint",
149
+ outputMeaning: "pre-aa-optical-field-spatial-quadrature-nodes",
150
+ spatialSampleCountX: input.spatialSampleCountX,
151
+ spatialSampleCountY: input.spatialSampleCountY,
152
+ antiAliasingComponentCount: antiAliasing.components.length,
153
+ totalNodeCount,
154
+ geometricApertureAreaSquareMicrometers: aperture.areaSquareMicrometers,
155
+ normalizedSpatialWeightSum,
156
+ combinedAreaMeasureSumSquareMicrometers,
157
+ preAntiAliasingSourceOutsideImagingAreaNodeCount: outsideCount,
158
+ sourcePointRule: "pre-aa-source-equals-destination-minus-aa-offset",
159
+ destinationCfaChannelAppliesToAllNodes: true,
160
+ cfaReassignmentBySourceCoordinate: false,
161
+ sourceCoveragePolicy: "not-applied",
162
+ nodes,
163
+ opticalFieldValuesIncluded: false,
164
+ radianceIncluded: false,
165
+ photonsIncluded: false,
166
+ electronsIncluded: false,
167
+ spectralResponseIncluded: false,
168
+ quantumEfficiencyIncluded: false,
169
+ opticalThroughputIncluded: false,
170
+ microlensResponseIncluded: false,
171
+ chargeDiffusionIncluded: false,
172
+ electricalCrosstalkIncluded: false,
173
+ temporalIntegrationIncluded: false,
174
+ rawSampleValueIncluded: false,
175
+ reconstructedPixelValueIncluded: false,
176
+ geometricAreaMeasureIsRadiometricCollectionArea: false,
177
+ componentEvidence: {
178
+ samplingApertureProfile: samplingAperture.provenance.profile,
179
+ samplingApertureLattice: samplingAperture.provenance.lattice,
180
+ geometricSensitiveAperture: samplingAperture.provenance.aperture,
181
+ opticalStackProfile: antiAliasing.provenance.profile,
182
+ antiAliasingResponse: antiAliasing.provenance
183
+ .antiAliasingResponse
184
+ }
185
+ }, "sensor-spatial-sampling-quadrature", "1.0.0", [
186
+ "Spatial aperture integration uses a deterministic tensor-product uniform midpoint rule.",
187
+ "The destination sensor site's semantic CFA/color channel applies to every quadrature node; pre-AA source coordinates never reassign the destination channel.",
188
+ "AA point-splitting is inverse-sampled: a +delta output displacement reads the pre-AA optical field at destination - delta.",
189
+ "AA component weights and aperture midpoint weights are dimensionless normalized spatial-average weights.",
190
+ "Square-micrometre measures are geometric integration measures only and are not effective radiometric collection area, throughput, QE, or photon count.",
191
+ "Pre-AA source points may leave the active physical imaging area and are not clamped, dropped, or renormalized.",
192
+ "The AA kernel remains the optical-stack foundation's field/wavelength/polarization-invariant approximation where applicable.",
193
+ "Microlens spatial response, charge diffusion, electrical/optical crosstalk, spectral response, radiometry, temporal integration, RAW sample generation, and reconstruction remain separate future work.",
194
+ "No geometry-only spatial quadrature error estimate is reported because downstream optical-field spatial frequency/content determines integration error."
195
+ ]);
196
+ }
197
+ //# sourceMappingURL=spatial-sampling-quadrature.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spatial-sampling-quadrature.js","sourceRoot":"","sources":["../../src/sensor/spatial-sampling-quadrature.ts"],"names":[],"mappings":"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"}
@@ -45,6 +45,18 @@ Equivalent focal length is also layered on top of physical capture geometry rath
45
45
 
46
46
  Sensor architecture is a separate descriptive layer. Illumination (FSI/BSI), integration/stacking, readout capabilities, and color-sampling family are independent evidence-backed facts. Their presence alone has no image-quality effect in the engine. Evidence origin is modeled independently from reuse rights, scalar facts may cite multiple evidence records, and multi-valued capabilities carry evidence per value. Omitted facts remain unknown rather than being inferred. Capture-mode semantics, readout timing, reconstruction, and calibrated radiometry consume these facts only through later explicit models.
47
47
 
48
+ Exact color-sampling topology is a separate layer again. `SensorColorSamplingProfile` can describe monochrome or an arbitrary periodic mosaic on an abstract native sensor sampling-site lattice, while layered color uses a separate unresolved spatial reference and remains structural-only until per-layer spatial sampling is modeled. This lattice is intentionally not `NativeImageRaster`; a future binding must establish how capture-mode effective samples relate to color-sampling sites. Active crop/orientation/output transforms therefore cannot reset periodic CFA phase, and architecture-family metadata cannot synthesize an exact tile.
49
+
50
+ That binding is now explicit through the native-effective-raster/color-site foundation. One evidence-backed profile ties one exact canonical native effective raster to regular sensor-anchored color-site blocks; a second resolver composes that relationship with a selected capture mode. Grouped modes require separately evidenced full-frame grouping phase, use absolute full-frame mode indices, and return compact pre-reconstruction source rectangles plus channel-site counts. Matching dimensions alone never create a binding. Pixel-shift metadata does not re-phase the CFA because the sensor/filter structure moves together; its optical-registration effect remains a later spatial-sampling concern. Declared-effective capture modes and unresolved layered layouts fail closed rather than receiving inferred mappings.
51
+
52
+ Sensor optical-stack metadata is a separate upstream layer. `SensorOpticalStackProfile` records ordered physical component roles and microlens presence independently from the effective anti-aliasing spatial response. Unknown AA response, documented absence/cancellation, unresolved presence, and an explicit normalized point-splitting kernel remain distinct. The first kernel is native-sensor-physical, field/wavelength/polarization-invariant, and spatial-only; it does not include throughput, spectral filtering, cover-glass refraction, microlens collection, or whole-stack PSF composition. This prevents OLPF presence from becoming a universal Gaussian/four-ray blur and prevents descriptive filter/microlens metadata from silently changing image formation.
53
+
54
+ Physical sampling aperture is a separate downstream prerequisite for CFA/site sampling. `SensorSamplingApertureProfile` explicitly registers the regular color-site center lattice in native sensor physical coordinates and can declare the first geometric sensitive-region approximation as a uniform rectangle. Site pitch/origin is never derived from `NativeImageRaster`, and aperture dimensions/offset are never inferred from pitch or a scalar fill fraction. The derived geometric sensitive-area fraction is diagnostic only. AA redistribution, microlenses, charge diffusion/crosstalk, spectral response, QE, optical throughput, radiometric collection area, and physical photodiode geometry remain separate models so spatial sampling can later compose them deliberately rather than double-count them.
55
+
56
+ The spatial-quadrature foundation now performs that first explicit composition of AA redistribution and geometric aperture support. It inverse-samples the pre-AA optical field, keeps the destination CFA channel authoritative, returns both normalized-average and geometric-area measures, and preserves off-imaging-area optical support without clamping. It still evaluates no optical/radiometric values and remains separate from temporal quadrature, microlens response, diffusion/crosstalk, RAW generation, and reconstruction.
57
+
58
+ The spatial-sample reducer now evaluates those quadrature weights against explicitly supplied nonnegative linear values. It supports either a dimensionless relative irradiance proxy or physical sensor-plane irradiance in W/m², matches values by node identity rather than position, and preserves both average and area-integral semantics. CFA channel labeling remains metadata until a later spectral response model; temporal exposure, photons/electrons, noise, ADC and RAW/reconstruction remain downstream.
59
+
48
60
 
49
61
  ## Image-formation ownership and ordering
50
62
 
@@ -118,6 +130,11 @@ Compatibility boundaries remain explicit:
118
130
  The POC still does **not** consume:
119
131
 
120
132
  - `SensorArchitectureProfile`;
133
+ - `SensorColorSamplingProfile`;
134
+ - `NativeEffectiveRasterColorSamplingBindingProfile`;
135
+ - `SensorOpticalStackProfile`;
136
+ - `SensorSamplingApertureProfile`;
137
+ - `CaptureModeProfile`;
121
138
  - `RadiometryReadinessProfile` or calibrated photon/noise output.
122
139
 
123
140
  That separation is deliberate. Further foundation APIs should remain independently testable and only enter the POC through explicit contract/version changes and migration review.
@@ -0,0 +1,188 @@
1
+ # Public-engine automation coordination
2
+
3
+ This document coordinates the two recurring automation lanes that work only in the public
4
+ `Photivra/photivra` repository. It is process guidance, not a scientific roadmap. Open issues and
5
+ the repository's scientific/API documentation remain authoritative for product and model semantics.
6
+
7
+ ## Lanes
8
+
9
+ - **Engine Science** (`engine-science`) owns scientific/foundational roadmap work.
10
+ - **Export & Performance** (`export-performance`) owns `SimulatedCapture`, TIFF/DNG export,
11
+ measured performance work, and CI-efficiency work.
12
+
13
+ Each roadmap issue should contain a short **Automation coordination** block identifying its owner
14
+ lane, dependencies, and cross-lane handoff. That block coordinates work; it does not override the
15
+ issue's scientific acceptance criteria.
16
+
17
+ ## Control plane
18
+
19
+ Automation coordination state lives on the repository's dedicated `automation-state` branch:
20
+
21
+ - `coordination/registry.v1.json`
22
+ - `coordination/registry.schema.json`
23
+
24
+ The control branch belongs to this public repository. No private repository or other project is a
25
+ coordination dependency.
26
+
27
+ Before any automation-owned project write:
28
+
29
+ 1. Read current `main`, open PRs, recent merges, the target issue, and applicable `AGENTS.md` /
30
+ scientific/API documentation.
31
+ 2. Read the registry from `automation-state` together with its exact blob SHA.
32
+ 3. Acquire the lane mutex and every shared resource required for the intended mutation in one
33
+ SHA-guarded registry update.
34
+ 4. If the registry changed, re-read and retry the compare-and-swap at most three times.
35
+ 5. Never steal an unexpired lease. A lease may be reclaimed only after its `expiresAt` is at
36
+ least two minutes in the past.
37
+ 6. Use a lease duration no longer than 55 minutes. If work cannot safely finish inside that
38
+ window, stop at a clean boundary and continue on the next scheduled run rather than holding a
39
+ resource indefinitely.
40
+ 7. Release every registry entry carrying the exact `leaseId` when the task is merged, abandoned,
41
+ or safely stopped.
42
+
43
+ If the registry cannot be read or validated, automation may inspect and plan but must not mutate
44
+ the repository.
45
+
46
+ ## Resource names
47
+
48
+ Every mutating run acquires exactly one lane mutex:
49
+
50
+ - `lane:engine-science`
51
+ - `lane:export-performance`
52
+
53
+ Acquire additional shared resources only when the change touches them:
54
+
55
+ - `shared:ci` — `.github/workflows/**`, CI policy/scripts, or test-runner configuration.
56
+ - `shared:package` — root `package.json`, `package-lock.json`, package exports, or dependency
57
+ changes.
58
+ - `shared:api-version` — `ENGINE_API_VERSION`, `POC_SIMULATION_API_VERSION`, or public schema
59
+ version surfaces.
60
+ - `shared:changelog` — `CHANGELOG.md` when both lanes could plausibly update it.
61
+ - `issue:<number>` — when mutating issue state/body/comments as part of active implementation.
62
+ - `path:<repository-path>` — for another high-collision file not covered by a shared resource.
63
+
64
+ Do not acquire broad resources speculatively. The registry is intended to prevent collisions, not
65
+ serialize unrelated science and export work.
66
+
67
+ ## Branch and PR ownership
68
+
69
+ Use focused branches:
70
+
71
+ - Engine Science: `science/*`, `sensor/*`, `motion/*`, `optics/*`, or another clearly
72
+ science-owned prefix.
73
+ - Export & Performance: `export/*`, `dng/*`, `perf/*`, or `ci/*`.
74
+
75
+ Normally each lane has at most one active behavioral PR. Before creating new work, inspect existing
76
+ open PRs and reuse an automation-owned branch/PR when it represents the same task.
77
+
78
+ Never modify another lane's active branch. A dependent lane consumes prerequisite work only after it
79
+ is merged to `main`, unless the project owner explicitly approves another integration path.
80
+
81
+ After every merge, rescan open PRs and the relevant dependency issues before selecting the next task.
82
+
83
+ ## Cross-lane handoff
84
+
85
+ When one lane lands a prerequisite for the other:
86
+
87
+ 1. Merge the prerequisite independently with its own tests and contract documentation.
88
+ 2. Record the exact merged commit and public API/contract surface on the dependent issue when a
89
+ handoff note materially helps.
90
+ 3. The dependent lane starts from current `main`; it does not continue on the prerequisite
91
+ lane's branch.
92
+ 4. If a downstream task exposes a missing scientific contract, Export & Performance records the
93
+ dependency and moves to independent work instead of implementing science locally.
94
+ 5. If Engine Science needs a benchmark/CI change, it records the requirement and lets Export &
95
+ Performance own the shared implementation unless the change is inseparable from the science
96
+ patch.
97
+
98
+ ## DCO and automated commits
99
+
100
+ The repository requires Developer Certificate of Origin sign-off for commits intended for
101
+ inclusion.
102
+
103
+ Automation must fail closed rather than invent a contributor identity. When operating through the
104
+ repository owner's authenticated GitHub connection under explicit owner authorization, use the
105
+ owner's established DCO identity from existing signed-off commits. Otherwise, do not create a
106
+ merge-intended commit until an authorized sign-off identity/path is available.
107
+
108
+ Every automation-created merge-intended commit must contain a valid `Signed-off-by:` trailer.
109
+
110
+ ## Merge discipline
111
+
112
+ Automation-owned non-breaking PRs may be merged only when:
113
+
114
+ - the exact final head is current and mergeable;
115
+ - required checks for that head are green;
116
+ - no expected check is missing or silently skipped;
117
+ - the complete final diff is reviewed;
118
+ - the lane still owns every required lease;
119
+ - issue dependencies are satisfied;
120
+ - DCO sign-off is present;
121
+ - scientific, provenance/licensing, security, browser/package-boundary, compatibility, and release
122
+ gates remain satisfied.
123
+
124
+ Intentional breaking public API/schema changes, releases/publication, new paid services, ambiguous
125
+ provenance/legal decisions, or other owner-gated actions still require explicit human approval.
126
+
127
+ ## Productive-run contract
128
+
129
+ A scheduled implementation run is successful only when it produces at least one concrete delivery
130
+ outcome:
131
+
132
+ 1. a safe roadmap PR is merged;
133
+ 2. an active roadmap PR contains substantive implementation/documentation changes and exact-head CI
134
+ is running or has produced actionable results;
135
+ 3. a roadmap issue is closed or materially narrowed with repository evidence because current
136
+ `main` already satisfies part or all of its acceptance criteria; or
137
+ 4. a specific blocker that truly requires external/human action is recorded with the exact missing
138
+ prerequisite and the lane immediately moves to another independent ready issue when one exists.
139
+
140
+ A general audit, plan, status recap, repeated inspection, or rediscovery of an already-known blocker
141
+ does **not** count as a productive outcome while actionable work exists.
142
+
143
+ Before starting implementation, read the issue's latest **Roadmap reconciliation** section when
144
+ present. Do not redo capabilities already verified on `main`. If reconciliation evidence shows the
145
+ issue is already complete, close it with evidence and continue to the next ready issue in the same
146
+ run when practical.
147
+
148
+ ## Stalled-progress rule
149
+
150
+ A lane must not spend repeated scheduled runs on the same non-productive state.
151
+
152
+ If two consecutive expected lane runs have produced none of the productive outcomes above:
153
+
154
+ 1. rescan current `main`, open PRs, recent merges, reconciliation state, dependencies, and leases;
155
+ 2. stop repeating the same audit or retry path;
156
+ 3. choose the next independent ready issue owned by the lane; or
157
+ 4. if no owned issue is actionable, record the precise blocker/dependency and surface it through the
158
+ health process.
159
+
160
+ An ordinary CI failure, owned merge conflict, stale automation branch, test regression, or expired
161
+ lease is an implementation problem to resolve, not by itself a reason to declare the lane blocked.
162
+ Only owner-gated decisions, unavailable external prerequisites, unresolved legal/provenance
163
+ questions, release gates, or another genuinely external dependency qualify as persistent blockers
164
+ after safe remediation paths are exhausted.
165
+
166
+ The health watch should treat an enabled/on-cadence lane with two consecutive scheduled windows and
167
+ no concrete delivery outcome as potentially stalled when a reconciled ready issue exists. It should
168
+ identify the lane and ready issue rather than disabling the lane.
169
+
170
+ ## Roadmap reconciliation discipline
171
+
172
+ Open issues are the roadmap, but issue state must reflect current `main`.
173
+
174
+ - Periodically reconcile issue acceptance criteria against merged implementation and tests.
175
+ - Close an issue when all of its open-source acceptance criteria are demonstrably satisfied.
176
+ - For partially completed issues, record the landed capability and the exact remaining scope in the
177
+ issue itself so future runs do not recreate completed work.
178
+ - Unmerged historical branches are evidence to inspect, not authoritative project state.
179
+ - A private/product requirement outside the current open-source scope does not keep an otherwise
180
+ completed public-engine issue open.
181
+ - After reconciliation or a merge, select work from the remaining dependency graph rather than issue
182
+ age.
183
+
184
+ ## Avoiding coordination drift
185
+
186
+ Do not duplicate scientific requirements or rewrite roadmap acceptance criteria in this document or
187
+ the registry. If issue ownership/dependencies change, update the issue's coordination block and the
188
+ automation prompt together. The issue remains authoritative for the work itself.
@@ -99,6 +99,18 @@ Exposure duration and sensor readout timing are independent:
99
99
 
100
100
  This allows later rolling-shutter work to consume the same time-parameterized camera-motion model used for global exposure.
101
101
 
102
+ The standalone `calculateSensorReadoutTiming()` foundation now supplies an invariant native-raster spatial readout phase schedule. Its first model is a caller-declared uniform-linear single-axis approximation with independently evidenced data-readout duration and rolling spatial skew. It does not define exposure windows or shutter-curtain motion, and it does not infer physical hardware readout lines from the effective native image raster.
103
+
104
+ The additive `assessReadoutExposureTimingLinkage()` layer can validate an explicitly evidenced normalized spatial-phase relationship between rolling readout and an electronic exposure boundary. It never infers absolute temporal synchronization; total data-readout duration is not a surrogate for exposure timing, and matching direction/span alone is insufficient evidence of a shared clock.
105
+
106
+ The standalone `calculateCaptureExposureWindows()` foundation separately describes local opening/closing exposure boundaries. It uses the first opening-boundary phase as its explicit time reference, preserves independent nominal/opening/closing timing, validates positive local duration across the whole active rectangle, and keeps mechanical/EFCS/electronic actuator identity separate from timing. It does not automatically bind those exposure boundaries to sensor readout phase or to the existing camera-rotation time origin.
107
+
108
+ The standalone `calculateCaptureRotationTrajectories()` bridge now explicitly binds that first opening-boundary phase to `t = 0` of the existing pure-camera-rotation model and evaluates stationary reference rays at local exposure start/end. It remains forward temporal geometry only: it does not consume sensor readout timing, solve the implicit rolling-shutter image warp, or integrate motion blur over the exposure interval.
109
+
110
+ The additive `calculateCaptureRotationInverseMappings()` layer now supplies an instantaneous destination-to-reference mapping at a caller-selected phase within each local exposure window. Under the current pure-rotation model the inverse is analytic once destination location fixes local capture time, so no iterative solver is used. This remains pre-lens, pre-output temporal geometry and does not replace finite-exposure integration.
111
+
112
+ The additive `calculateCaptureRotationTemporalQuadrature()` layer now supplies deterministic uniform-midpoint time nodes and separate normalized-average/seconds-valued temporal measures across each local exposure. It preserves the instantaneous inverse mapping as the geometry source of truth but does not itself integrate radiance, visibility, shutter transmission, sensor response, PSF, or output pixels.
113
+
102
114
  ## Renderer semantics
103
115
 
104
116
  Renderer implementations may use a bounded real-time preview approximation or a higher-fidelity deterministic reference evaluation, but both must consume the same engine-owned scientific contract.
@@ -129,6 +141,20 @@ optical PSF
129
141
 
130
142
  Temporal exposure/readout couples into the photosite/charge stages rather than acting as an unrelated display blur.
131
143
 
144
+ The standalone capture-mode profile now describes acquisition sequence, per-frame effective sampling, optional inter-frame sensor offsets, reconstruction-stage labels, processed-image raster, and downstream dependencies. It does **not** implement the reserved CFA/photosite/reconstruction algorithms. A mode's processed raster remains upstream of physical orientation and final output crop/resampling and cannot redefine physical sensor geometry or FOV.
145
+
146
+ The standalone color-sampling topology profile now describes monochrome, arbitrary periodic mosaic phase/channel assignment, and layered color declarations with an explicitly unresolved spatial relationship. The periodic topology lives on a distinct native sensor sampling-site lattice rather than `NativeImageRaster`; no binding to effective image samples or physical photodiodes is inferred. This metadata foundation does not yet implement the reserved photosite/CFA sampling stage, spectral response, sparse site exceptions, RAW sampling, or reconstruction.
147
+
148
+ The additive native-effective-raster/color-site binding now provides the missing structural bridge between capture-mode effective samples and monochrome/periodic topology. It requires evidence for the exact native raster relationship and grouped-mode phase, preserves full-frame absolute CFA phase, and reports compact pre-reconstruction source regions/channel counts. This still does **not** implement the reserved photosite/CFA sampling stage: no signal weights, spectral response, photons/electrons, RAW values, aliasing, or reconstruction are calculated.
149
+
150
+ The standalone sensor optical-stack profile now makes the upstream stack boundary explicit without claiming a complete stack response. Ordered physical component roles and microlens presence are descriptive. Effective anti-aliasing response is separately modeled as unknown, absent, present-but-unresolved, or a normalized native-physical point-splitting approximation. The resolver exposes only that AA spatial term; cover/filter transmission/refraction, microlens angular/collection behavior, wavelength/field/polarization dependence, and convolution with the lens PSF remain future work. Consequently the `sensor-optical-stack` image-formation stage remains reserved rather than being promoted to a complete implementation.
151
+
152
+ The standalone sensor sampling-aperture profile now supplies explicit native-physical registration for the color-site center lattice and a first geometric sensitive-region footprint. The rectangle is a normalized spatial-area averaging support plus a separately reported geometric area/fraction; it is **not** QE or radiometric collection efficiency. AA point splitting, microlens redirection, diffusion/crosstalk, spectral response, photons/electrons, and RAW values are still absent. This advances the geometry needed by the reserved `photosite-cfa-sampling` stage without claiming that stage is fully implemented.
153
+
154
+ The additive sensor spatial-quadrature layer now combines the resolved AA point-splitting term with the geometric aperture using deterministic native-physical midpoint nodes. AA is applied by inverse source lookup, the destination site's CFA channel remains fixed, and off-active-area pre-AA source support is preserved. The result is still a geometry/measure plan only: no optical field, spectral response, radiometry, temporal integration, RAW value, or reconstruction is calculated, so the `photosite-cfa-sampling` stage remains reserved.
155
+
156
+ The additive spatial-sample reducer can now apply those weights to caller-supplied nonnegative linear relative values or physical irradiance. It does not turn a channel ID into spectral filtering, does not integrate over time/wavelength, and does not calculate photons, electrons or RAW codes. Consequently this is still a pre-response site sample and the full `photosite-cfa-sampling` stage remains reserved.
157
+
132
158
  ## Current implementation status
133
159
 
134
160
  Each stage is labeled:
@@ -51,6 +51,148 @@ This is camera **rotation only**. Translation is excluded because parallax requi
51
51
 
52
52
  The returned image-plane/sample components use +X right and +Y up. Native raster remains +X right/+Y down and requires the existing explicit coordinate transform at capture composition boundaries.
53
53
 
54
+ ## Sensor readout timing
55
+
56
+ `calculateSensorReadoutTiming()` provides a standalone capture-specific **native sensor scan-timing** foundation.
57
+
58
+ The model deliberately separates two evidence-backed seconds-valued facts:
59
+
60
+ - **capture data-readout duration** — the caller's declared duration for the selected capture mode's sensor data-readout operation;
61
+ - **rolling spatial-sampling skew** — the timing span that drives the first-to-last spatial scan phase in the current approximation.
62
+
63
+ Photivra does not assume those values are equal, and it does not infer an ordering relationship between them. For global readout, spatial skew is zero in this model even when the declared data-readout duration is non-zero.
64
+
65
+ For rolling readout, the caller also supplies an independently evidenced native scan direction. The current schedule is a `uniform-linear-single-axis` approximation over continuous native raster edge coordinates. This is intentionally **not** a claim that one `NativeImageRaster` row/column equals one physical photodiode row or one hardware readout line. Segmented, center-out, interleaved, multi-tap and other non-uniform schedules remain unsupported rather than being silently approximated as equivalent hardware.
66
+
67
+ Native sensor timing is orientation-invariant. `calculateSensorReadoutTiming()` therefore does not accept physical camera orientation or output raster geometry. When presentation/capture orientation is needed, callers compose the returned native direction vector with the existing native-to-oriented vector transform. Digital output crop/resolution cannot alter the native timing schedule.
68
+
69
+ Active-capture timing is also explicit: Photivra never scales a full-frame timing fact by crop dimensions to invent a cropped-mode timing. A caller must provide timing evidence appropriate to the selected capture mode/active area; later capture-mode work can bind those declarations more formally.
70
+
71
+ Mechanical, electronic-first-curtain and fully electronic shutter mechanisms are reported independently but remain scientifically inert in this slice. The function does **not** define local exposure start/end times, mechanical curtain travel, EFCS curtain interaction, rolling-shutter image distortion, flash/flicker bands, or motion integration. Those later models must combine their own timing with this sensor schedule explicitly rather than treating sensor readout as a surrogate shutter-curtain model.
72
+
73
+ ## Readout/exposure spatial linkage
74
+
75
+ `assessReadoutExposureTimingLinkage()` is the explicit boundary between the native sensor readout schedule and the capture exposure-window schedule.
76
+
77
+ The contract deliberately distinguishes **spatial phase/order** from **absolute temporal synchronization**.
78
+
79
+ A caller may declare:
80
+
81
+ - `unlinked` — Photivra asserts no relationship for this capture. This is not evidence that the physical processes are independent.
82
+ - `spatial-phase-linked` — one or more electronic exposure boundaries are asserted to share the rolling readout's normalized native spatial phase, either in the same direction or reversed.
83
+
84
+ Every positive link carries its own provenance. The underlying readout direction/skew and exposure-boundary direction/traversal facts keep their own separate evidence.
85
+
86
+ A positive spatial-phase link is valid only when:
87
+
88
+ - sensor readout is rolling and therefore has a spatial scan;
89
+ - the selected exposure boundary is electronic;
90
+ - the selected exposure boundary is itself a uniform-linear native scan;
91
+ - `same` phase orientation uses the same native scan direction;
92
+ - `reversed` phase orientation uses the exact opposite direction on the same native axis.
93
+
94
+ The rolling-readout spatial skew and exposure-boundary traversal duration do **not** have to be equal. Their dimensionless ratio is reported diagnostically. A ratio of 1 still does not prove a shared clock origin or temporal coincidence.
95
+
96
+ `captureReadoutDurationSeconds` is preserved with units/evidence but is never used to validate an exposure linkage. Data transfer/readout duration can describe a different physical interval from exposure-boundary traversal.
97
+
98
+ Absolute temporal alignment remains `not-established` in this foundation. A future mode profile may supply an evidence-backed temporal offset/synchronization relationship if defensible data exists, but Photivra will not infer it from shutter mechanism, scan direction, matching timing spans, output resolution, or adjacent camera specifications.
99
+
100
+ ## Capture exposure windows
101
+
102
+ `calculateCaptureExposureWindows()` adds the next standalone timing layer after native sensor readout timing.
103
+
104
+ It represents **opening and closing exposure boundaries separately**. Each boundary can currently be simultaneous or a caller-declared uniform-linear scan in invariant native sensor coordinates. The nominal exposure duration is another independent evidence-backed seconds-valued quantity.
105
+
106
+ The output time reference is the **first opening-boundary phase**, not a claim that every native location has the same local exposure start. For each point, local start and end offsets are derived independently, and the engine reports local exposure duration.
107
+
108
+ Mechanical, EFCS, and electronic shutter mechanisms determine only which conceptual actuator owns the opening/closing boundary. They do not invent traversal timing:
109
+
110
+ - mechanical: mechanical opening and closing;
111
+ - EFCS: electronic opening, mechanical closing;
112
+ - electronic: electronic opening and closing.
113
+
114
+ The complete active rectangle is validated so a zero/negative local exposure duration cannot hide in an unsampled region.
115
+
116
+ This contract remains deliberately separate from `calculateSensorReadoutTiming()`. Sensor readout phase is not automatically an exposure boundary. It also remains separate from `calculateCameraRotationImageMapping()`: that API still accepts seconds from its existing exposure-start reference, and a later integration layer must define how local capture-window times bind to that motion time basis.
117
+
118
+ The first exposure-window model does not include curtain acceleration, nonlinear or segmented electronic scheduling, flash/flicker interaction, shutter shock, EFCS-specific pupil/bokeh behavior, or rolling-shutter image distortion.
119
+
120
+ ## Capture rotation exposure trajectories
121
+
122
+ `calculateCaptureRotationTrajectories()` is the first explicit integration between local capture exposure windows and the existing pure-camera-rotation mapping.
123
+
124
+ For each caller-selected native raster point, the engine:
125
+
126
+ 1. resolves the point's local exposure start/end with `calculateCaptureExposureWindows()`;
127
+ 2. maps that native point into the pre-orientation image plane at the **first opening-boundary phase**;
128
+ 3. explicitly binds that capture reference to `t = 0` of `calculateCameraRotationImageMapping()` for this composition;
129
+ 4. evaluates the same stationary world ray at the local exposure start and end;
130
+ 5. reports the two mapped endpoints plus their image-plane chord.
131
+
132
+ The output is deliberately a **forward stationary-reference-ray trajectory**, not a rolling-shutter image warp.
133
+
134
+ That distinction matters because in a rolling capture the exposure/capture time depends on image location while camera motion also changes where a world ray lands. A renderer-ready rolling-shutter transform therefore requires a self-consistent inverse destination-to-source mapping rather than one forward displacement evaluated at a destination coordinate.
135
+
136
+ The endpoint chord also is **not a blur kernel**. Pure rotational image motion can follow a curved image-plane path, and exposure integrates radiance over the entire local interval rather than only its two endpoints.
137
+
138
+ Sensor data-readout timing from `calculateSensorReadoutTiming()` is intentionally absent. A particular electronic capture mode may have a documented relationship between sensor readout and exposure boundaries, but Photivra will require that relationship to be declared explicitly rather than assuming it universally.
139
+
140
+ The trajectory foundation remains rotation-only. Camera translation/parallax, subject motion, panning intent, object rotation/deformation, occlusion changes, exposure integration, flash/flicker, shutter shock, and the final inverse rolling-shutter warp remain future work.
141
+
142
+ ## Capture rotation temporal quadrature
143
+
144
+ `calculateCaptureRotationTemporalQuadrature()` extends the instantaneous inverse mapping into a deterministic **temporal-geometry quadrature** over each destination point's complete local exposure interval.
145
+
146
+ The first model uses the uniform midpoint rule. For `N` temporal samples, normalized local phases are:
147
+
148
+ ```text
149
+ phase_i = (i + 0.5) / N
150
+ ```
151
+
152
+ so the exact opening and closing boundaries are not sampled. Each phase is evaluated through the already-public `calculateCaptureRotationInverseMappings()` contract rather than introducing a second camera-motion equation.
153
+
154
+ Every temporal node reports two different measures:
155
+
156
+ - `normalizedTimeWeight = 1 / N` — dimensionless weight for a downstream **time average** under the declared uniform temporal-response approximation;
157
+ - `timeMeasureSeconds = localExposureDuration / N` — seconds-valued `dt` measure for a downstream **time integral**.
158
+
159
+ These are intentionally separate. The seconds-valued measure is not shutter transmission, photon count, radiometric throughput, scene radiance, sensor response, or a calibrated energy measurement.
160
+
161
+ The local exposure interval remains destination-dependent. A scanned opening/closing schedule can therefore shift node times between sensor locations, and different opening/closing traversal schedules can produce different local durations and therefore different `timeMeasureSeconds` values.
162
+
163
+ The first temporal-response model is `uniform-over-local-exposure`. That is an explicit approximation. Photivra does not yet model shutter-transmission ramps, exposure-dependent sensor response, scene flicker, flash pulses, or other time-varying radiometric weighting.
164
+
165
+ The API returns **nodes and weights only**. It does not average reference coordinates, compute a blur radius/kernel, sample scene radiance, resolve visibility/occlusion, or write output pixels. Averaging geometric coordinates is not a substitute for integrating the radiance seen along the time-varying rays.
166
+
167
+ No geometry-only quadrature error estimate is reported. Image-integration error depends on downstream scene radiance, visibility, texture/edge frequency, and reconstruction as well as the camera trajectory. Renderer/reference implementations can compare increasing temporal sample counts in their radiance domain when convergence evidence is needed.
168
+
169
+ ## Instantaneous capture rotation inverse mapping
170
+
171
+ `calculateInverseCameraRotationImageMapping()` is the exact inverse of the existing forward pure-rotation ray mapping under the same constant-axis assumptions. Once capture time is known, the captured image-plane ray is rotated analytically back into the exposure-start reference camera frame. There is no numerical root/fixed-point solve.
172
+
173
+ `calculateCaptureRotationInverseMappings()` composes that inverse with local exposure-window timing.
174
+
175
+ For each destination native sensor point:
176
+
177
+ 1. the destination point selects its local exposure window;
178
+ 2. the caller supplies `localExposurePhase` in `[0, 1]`;
179
+ 3. capture time is `start + phase × localDuration`;
180
+ 4. the destination native point is mapped into the ideal pre-lens image plane;
181
+ 5. the captured ray is analytically inverted to the first-opening-boundary reference frame;
182
+ 6. image-plane, native effective-sample, and physically oriented sample displacements are reported.
183
+
184
+ The phase is mandatory because a finite exposure has no single sharp geometry. Phase `0` means local exposure start, `0.5` is the local temporal midpoint, and `1` is local exposure end. None is automatically privileged as the final rendered image.
185
+
186
+ This inverse is renderer-oriented in the limited sense that it maps **destination capture location -> reference ray**. It still is not a finite-exposure renderer. Motion blur requires integration over the local interval, with an explicit exposure-weighting model if weighting is not uniform.
187
+
188
+ Sensor data-readout timing remains absent. A documented mode-specific relationship may later link readout and electronic exposure timing, but no such equality is assumed here.
189
+
190
+ The mapping is evaluated in the ideal pre-lens image plane. Radial distortion, chromatic aberration, PSF, output crop/resampling, camera translation/parallax, subject motion, occlusion changes, flash/flicker and shutter shock remain separate stages.
191
+
192
+ Reference rays are not clamped to the active source frame. Under camera motion, a valid captured destination can map to reference geometry outside the available reference image; a renderer must treat that as missing source coverage rather than silently stretching or clamping edge pixels.
193
+
194
+ The current inverse does not claim that an arbitrary rolling/capture-scan camera is globally one-to-one. More general motion models can produce folds, repeated visibility or other multi-perspective behavior; those cases require additional validity analysis rather than being inferred from this pure-rotation slice.
195
+
54
196
  ## Temporal image-formation basis
55
197
 
56
198
  The image-formation contract defines physical time in **seconds from exposure start**.