Stop describing a base curve the pipeline no longer has

D19 retired the per-body base curve, moved the matrix ahead of the
edits and the film into the view transform's place, but a dozen doc
comments still listed the curve among what a pixel passes through, or
said the film skipped it. The detail stage's module doc still drew the
matrix after the edits and the last detail pass encoding, which the
view pass took over. The film crate's README gave the base curves as
its reason for being data, and the ops README's list of hand-written
nodes had neither the view transform nor three of the five kernels.

FR-MRG-2 gave the base curve as why the merge cuts below the profile;
the view transform is why now. The decision table still said colour
defaults were a per-body curve, and FR-DEV-3j said only the default
view transform skips a JPEG, where the node skips one whatever its
sliders say. frame-budget.md records the view pass as unmeasured.
This commit is contained in:
2026-09-27 19:42:41 -04:00
parent 31bcc3a462
commit affdaecaee
19 changed files with 107 additions and 61 deletions
+23
View File
@@ -515,3 +515,26 @@ texture and dehaze; the scenes without dehaze moved within ±2%. The
picture is the same bits: a minimum is exact in any order, the window is
the one the split passes covered, and the rgba8 output hashed identically
before and after in all 64 scene, view and size combinations measured.
## The view pass after the detail stage — 2026-09-27
**Status:** Not measured. Every figure above predates it.
**A chain with a detail stage is now one dispatch longer** (`c07f81e`,
D19). The fused pass used to end in the rendering — the base curve, then
the output transform — before it stored, so every detail pass convolved
display-referred values, and the last detail pass encoded. Now the fused
pass stops before the view transform, every detail pass writes a
scene-linear `rgba16float` intermediate, the last one included, and a view
pass composed from the same inputs reads the result and runs the view
transform (or the film stock), the output transform and the mask reveal.
What that adds, per frame with a detail stage: one render-sized read and
write, and a third intermediate for a one-pass chain. By the dehaze
section's own figure that is about 4 ms at 2560 × 1600 on the laptop
RTX 3050 under its power cap. What it removed: a capture sharpening too
fine to draw at the current scale no longer emits a pass-through, and
there is no resolve pass for an active kernel with nothing to draw. A
chain with no detail operation is unchanged, one fused dispatch with the
view transform at its tail. The rows above that name a detail operation
should be re-run before they are quoted.
+5 -3
View File
@@ -143,7 +143,9 @@ composer already makes that a matter of uniforms rather than structure: the
white balance, the matrix and the curve's active flag are all in the reserved
uniform block, and a fused pass with no operations, `as_shot_wb = 1`,
`cam_to_srgb = I` and `base_curve_last.z = 0` stores exactly camera-linear
RGB after the warp. So the tap is:
RGB after the warp. *(Since D19 there is no curve flag: the base curve is
gone, and the tap composes no view transform, so the white balance and the
matrix are the only uniforms it fills neutral.)* So the tap is:
- `EditGraph::compose_camera_linear()` — the `LinearWorking` tail with an
empty operation list and identity framing, paired by name with
@@ -158,8 +160,8 @@ be re-developed deserves the sensor's precision. The cost is 2× on buffers
FR-MRG-11 already bounds.
**What the DNG carries as a consequence:** the first source's `Make`,
`Model` and `UniqueCameraModel` — so `base_curve::for_body` finds the 6D's
curve (retired with the base curves, D19) — its `ColorMatrix1`/`2` with illuminants, and its `AsShotNeutral`. The
`Model` and `UniqueCameraModel` — so `base_curve::for_body` found the 6D's
curve, until D19 retired the per-body curves — its `ColorMatrix1`/`2` with illuminants, and its `AsShotNeutral`. The
composite then develops through the same profile as its sources, applied
once. The spike's 64 × 48 file (§8) already carries the matrix and neutral;
the body name is a string.
+10 -7
View File
@@ -565,8 +565,9 @@ display 0.18. A photograph with the view transform at its defaults is **unedited
is always composed, and "active" keeps meaning "moved from the defaults", so an untouched image
writes no parameters and every other operation's neutral is still the image.
An already-rendered source — a JPEG — is not rendered again: the default view transform is skipped
for it, as the base curve was. A film stock is not, because choosing one is an edit.
An already-rendered source — a JPEG — is not rendered again: the view transform is skipped for it,
as the base curve was, so its two sliders do not move a JPEG. A film stock is not skipped, because
choosing one is an edit.
*Acceptance:* monotone in each channel; a neutral stays neutral; middle grey lands within 0.01 of
0.18; between scene 0.03 and 1.0, the default is within 0.3 EV of the retired default curve; the
@@ -1911,7 +1912,7 @@ with no depth to recover — so it is where the shared machinery is built.
**FR-MRG-2 — What is stitched.** Each source enters the merge in **camera space**: after black
and white levels, demosaic and lens distortion correction, and before everything else — no white
balance, no base curve, no camera matrix, no edit. The composite carries the first source's body,
balance, no camera matrix, no edit, no view transform. The composite carries the first source's body,
colour matrix and as-shot neutral, so that it is developed afterwards exactly as one of its
sources would be: the camera profile, the white balance and every operation in §3.3 are applied
once, to the composite, in its own develop.
@@ -1920,9 +1921,11 @@ This is the clause that decides what the output *is*. Stitching the rendered edi
stitcher does; the result cannot be re-developed, and any difference between the frames' edits
becomes a seam. Stitching camera-space pixels produces a photograph the camera could have taken,
and nothing is applied twice. The cut sits *below* the profile, not above it, for a reason S15.3
found in the pipeline: the base curve is part of the profile (FR-DEV-3e) and is applied to every
frame of a known body, so a composite that baked it in and then developed as one would render the
curve twice. Lens correction alone sits above the cut, because a distorted frame does not align.
found in the pipeline: the profile's rendering was applied to every frame of a known body, so a
composite that baked it in and then developed as one would render it twice. *(Amended 2026-09-27,
D19: that rendering was the per-body base curve, retired; the view transform that replaces it is
applied to every develop, so the reason stands.)* Lens correction alone sits above the cut, because
a distorted frame does not align.
White balance sits below it because the sensor saw the same light in every frame: un-balanced
camera RGB agrees across the overlaps whether or not the camera's auto white balance drifted, and
the balanced values would not.
@@ -2429,7 +2432,7 @@ Settled by requirements calibration, 2026-08-08.
| Culling | **The core differentiator** (§3.9) |
| Focus checking | Peaking *and* zoom |
| Ingest | Full workflow — template rename, checksum verify, dual-destination |
| Colour defaults | Good, not obsessive — matrices plus per-body base curve |
| Colour defaults | Good, not obsessive — matrices plus one scene-referred view transform for every body (D19; the per-body base curves are retired) |
| Film simulation | Fujifilm explicitly targeted |
| AI | Denoise in v1; masking deferred. Per-face eye state and head pose are in v1 **as culling evidence, not AI** (FR-CULL-8a, FR-CULL-13); gaze deferred (§7) |
| Local adjustments | Full masking, GPU-rasterised |