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>
31 lines
1.5 KiB
YAML
31 lines
1.5 KiB
YAML
# A hand-written node. `rust:` names the type in `crate::ops` that implements
|
|
# `Operation`; its descriptor, its parameters and its passes come from that
|
|
# type rather than from this file.
|
|
#
|
|
# It appears here anyway so that `ops/` lists the whole pipeline in order —
|
|
# including the neighbourhood operations, which run as a group after every
|
|
# point operation but are still ordered among themselves.
|
|
id: noise_reduction
|
|
order: 110
|
|
|
|
rust: NoiseReduction
|
|
|
|
why_rust: |
|
|
A kernel, not four facts. The declarative schema hands a fragment a colour
|
|
and no coordinate, which is precisely what a denoiser cannot work with — it
|
|
is defined by what the neighbouring pixels are doing. It is therefore a
|
|
`DetailStage` (see `../src/detail.rs`), which means deciding at every render
|
|
how many dispatches to emit, converting a radius stated in *source* pixels
|
|
into the render pixels this frame is being drawn at, and declaring the halo
|
|
the tile scheduler needs. None of that is expressible as a uniform
|
|
expression, and stretching the schema to cover it would produce a worse
|
|
language than Rust aimed at one caller.
|
|
|
|
placement: |
|
|
First among the detail operations, because denoising is a repair and
|
|
everything else in this stage is an enhancement: sharpening or adding
|
|
clarity to a noisy frame amplifies the grain along with the detail, and no
|
|
later pass can separate them again. Its position relative to the point
|
|
operations is not this number's to decide — the whole detail stage runs
|
|
after the fused pass, in linear light, before the output transform.
|