HOW A NEGATIVE BECOMES A FRAME
One fused kernel,
not a chain of passes.
aces declares its stages as typed parts, each naming the colour state it operates in. The aggregator plans the state transitions, inserts the sealed transforms, and compiles the lot into a single WGSL dispatch.
Colour states
Every part names the state it runs in. The aggregator routes between them; a part never converts.
| state | what it is |
|---|---|
| aces.ap0 | ACES2065-1 — the negative's own primaries. The pipe's start_state. |
| aces.ap1 | ACEScg — the working space where grading happens. |
| display | display-referred RGB, after the Output Transform. The display_state. |
The sealed pipe
Not parts, not editable, inserted by the aggregator. This is what makes the result ACES-correct rather than merely ACES-shaped.
| id | in → out | call |
|---|---|---|
| xf.ap0_to_ap1 | aces.ap0 → aces.ap1 |
ap0_to_ap1($c) |
| xf.ap1_to_ap0 | aces.ap1 → aces.ap0 |
ap1_to_ap0($c) |
| output.rrt+odt | aces.ap0 → display |
output_transform($c) |
The parts, in pipe order
Every parameter is a prefill, not a lock — a look supplies values, the editor still exposes them.
| # | part | state | what it does | gated vs |
|---|---|---|---|---|
| 1 | exposure | aces.ap1 |
master brightness, in stops | standard |
| 2 | colour-balance | aces.ap1 |
ASC-CDL — lift / gain / power, each per-channel, plus saturation. An exact port of OCIO's GradingPrimary LIN forward | OCIO |
| 3 | grading-tone | aces.ap1 |
tonal-region trims — highlights, shadows, whites, blacks | provisional |
| 4 | vibrance | aces.ap1 |
saturation, weighted toward the less-saturated | provisional |
| 5 | hsl | aces.ap1 |
per-band hue / sat / luma trim across eight bands | provisional |
| — | Output Transform | aces.ap0 → display |
sealed — RRT + ODT | — |
| 6 | apply-look | display |
the look's colour cube, display-sRGB → display-sRGB | vibe |
| 7 | tonecurve | display |
the look's 1-D luma curve | vibe |
Every part is point-only and bakeable — which is what lets the whole colour
path fuse into one dispatch. Parts 1–5 are scene-referred, before the tone map; parts
6–7 are the look slot, after it. Tone and colour stay separate entries by invariant:
tone is never folded into the cube. lmt.gamut-compress is declared in the model ahead of
them all, gated against OCIO, but is not in the shipped view's part list.
What the kernel binds
Two assets, both authored offline by vibe, both bound by slot.
slot 0 · look look_n³ × 3
The display colour cube, baked by vibe's bakeCube.
slot 1 · curve 256 samples
The 1-D luma curve, from vibe's toneCurve.
Past the display seam
post has its own param namespace and a declared passthrough pipe — no colour states, no Output Transform.
Its first part is clarity: bilateral local contrast on luma with chroma preserved, gated
against darktable-cli's iop/bilat.c. It is the one part that is not
point-only, and strength = 0 is an exact identity. Sharpen, texture, grain,
vignette, dehaze and a geometry stage are planned. Noise reduction is deliberately not here —
sensor noise belongs to dneg.
Direction versus frame
The recipe carries two kinds of value, and confusing them is what breaks a portable look.
| belongs to | examples | |
|---|---|---|
| direction | the look — travels in a pack | key, the CDL, grading-tone, hsl, cube + curve |
| frame | this negative only — never travels | shotEv, the white-balance trim |
exposure.ev is the sum of the two at render: the frame's own
exposure plus the look's key — where key is relative to the target's
own auto-exposure. That is precisely why a look re-renders on any negative instead of
copying one frame's brightness onto another.
Invariants
The rules that keep the kernel composable.
- A part names a state and a call. It never converts between states — the aggregator does.
- A missing effect is added to aces, never to vibe. vibe contributes no runtime mechanism; it only prefills parameters and bakes assets.
- The sealed pipe and the Output Transform are not parts and are not editable.
- Order is the pipe order. Scene-referred grade → Output Transform → display look slot.
The authority for all of the above is
aces/etc/model/parts.json. If a page disagrees with it, the JSON is right and the page is
stale — read the source documents under Docs.