Files
DarkRoom/core/dr-pipeline/ops/capture_sharpen.yaml
dtourolleandClaude Opus 5 e235e99cce Move the film to Effect in the descriptor that is actually read
An earlier commit claimed to move `film_sim` from `[tone, colour]` to
`[effect]` and did not. It edited `ops/film_sim.yaml`, where `attributes:` is
read, validated against the vocabulary, and then dropped: a `rust:` node
publishes its own descriptor, and the type still said tone and colour. The
stock went on appearing in the Light group beside exposure and again in Colour
beside white balance, exactly as before, and every test passed.

Nothing caught it because nothing could. The declaration parsed, the parity
tests compare ids rather than attributes, and an operation filed under the
wrong groups renders perfectly. It surfaced only on screen, as a missing
Effects tab — which is indistinguishable from a category that genuinely has
nothing in it, and is precisely how `Optics` looked for as long as it was
empty.

So three changes rather than one:

`FilmSim`'s descriptor declares `Attribute::Effect`, which is the move the
earlier commit described.

`attributes:` joins the keys a `rust:` node may not carry, beside `params`,
`uniforms`, `wgsl`, `helpers`, `define` and `label`. The rule was already
written — "its descriptor comes from the type" — and attributes were the one
field that slipped past it. A key that is silently ignored is worse than one
that is rejected, because it reads as though it worked; the eight hand-written
declarations lose a line that never did anything.

And a test asserts that every attribute the chain carries reaches the tab
strip. That is the property that was actually broken, and its failure mode is
invisible from every direction: the controls exist, they are in the shader,
and there is no way to filter to them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-05 18:28:59 +02:00

33 lines
1.6 KiB
YAML

# A hand-written node, and a neighbourhood one: it reads the pixels around the
# one it is writing, so it runs in the detail stage rather than as a fragment
# in the fused pass. See `../src/detail.rs` for why that stage exists and
# `README.md`'s "Nodes that read their neighbours" for the contract.
#
# As with every `rust:` node, its descriptor, parameters and behaviour come
# from the type; this file exists so that `ops/` remains the one place the
# pipeline's order is written down.
id: capture_sharpen
order: 120
rust: CaptureSharpen
why_rust: |
A convolution, not a point function. The schema in `README.md` describes an
operation handed a colour with no way back to a coordinate, which is exactly
what a kernel cannot work with — and stretching it to cover taps, kernel
extents and a per-render conversion from source pixels to render pixels
would produce a worse language than Rust aimed at one caller.
placement: |
First among the detail nodes, because capture sharpening is a correction to
the capture: it recovers the acutance the anti-aliasing filter, the lens's
circle of confusion and the demosaic interpolation each took out, and it is
meaningful before any effect built on top of it. The compositional detail
controls — texture, clarity — reasonably follow it, since they are about the
picture rather than about the sensor.
Being in the detail group at all is what places it after every tonal and
chromatic operation: an amount tuned before a tone curve is amplified by
whatever slope that curve happens to have, so the amount that looked right
stops looking right the moment the curve moves.