Skip to content

What the report's columns mean

preset bass mid treb onset drive anim rate cover level rise fall
Shatter 0.099 0.064 0.013 0.106 0.103 0.092 0.0357+ 0.997 0.0316 11+ 4+
Stipple 0.050 0.013 0.001 0.011 0.057 0.010 0.0132+ 0.222 0.5313 7+ 26+
columnquestion it answers
bass mid treb onsethow far the frame moves when that stimulus alone comes up, against silence — “does this preset respond to bass at all”
drivehow far the frame moves under the combined stimulus — silence against everything up at once, same depth and same size (the two motion readings)
animhow far the frame moves between two capture depths under silence — does it have a life of its own
ratehow far the frame moves frame to frame — the only column that walks consecutive frames, and the only one measured at 96x96 (the two motion readings)
coverfraction of the frame that differs from the corner background — a low value is often correct
levelhow much light the picture carries: mean linear light over the pixels cover counts as lit (what the level column measures)
rise fallthe transient probe (below) — frames to settle after a step up, and after the matching step down; a + suffix means the value is a lower bound, not a measurement (below); read them as evidence, not a verdict

What the level column measures

Linear light, over the lit set (ADR-0150). Two halves, and both matter:

  • Linear, because the stored pixels are sRGB-encoded and that curve is concave: a mean over the bytes under-reports a brightness change by roughly half. That factor of two is the property, and it is what the statistic is for. The move you see is not the trim you made, and expecting it to be will make a working column look broken. Trimming star_rosewindow’s brightness by 30 % moves this column from 0.0532 to 0.040823 %, measured 2026-09-01 on the DX12 software adapter. Two things eat the rest, and both are the pipeline rather than the statistic: the tonemap is the identity only below its knee and compressive above it, so bright content moves less than its source; and the lit set itself shrinks as dimmed pixels fall under cover’s threshold, which lifts the mean of what stays.
  • Over the lit set, the same pixels cover counts, so an authored background does not dilute the reading. The two columns read one picture from two directions: cover is how much of the frame is lit, level is how bright that lit part is. In the sample above Shatter fills the frame at a low level and Stipple lights a fifth of it, brightly.

It is a comparison number and never a threshold. There is no level a preset ought to hit, and one row’s cell says nothing on its own. What it is for is a before/after on the same preset — did this change make it brighter, and by how much — or an ordering across a family. The lit predicate is a threshold on the stored bytes, so the statistic is linear light over a set chosen in code space; ADR-0150 records why that seam is accepted rather than solved.

The blind spot, by construction: a preset that goes wrong by changing its background is invisible here. That is the price of not being a background detector, and cover is the column that sees it.

The name column is fourteen characters wide, and a longer name is elided in the middle, not at the tail: Tiled Rosette Mono prints as Tiled R~e Mono. The tail is what distinguishes a name in this library — Mono, Gallery, Bordered, Walk — and a tail truncation threw it away, which is how two presets came to print as one row label in all three tables (design-backlog 0131). A ~ in a label means characters were dropped there.

Two extra labeled blocks print under the table (the table itself stays un-widened, so every historical number keeps its place): the realistic-levels reading (reactivity_low — the same bands at the levels real music reaches, ADR-0042) and, since Plan 0077, the footprint reading (reactivity_footprint) — the same differentials divided by the union of lit pixels instead of the whole frame (metrics::footprint_diff, ADR-0091). Read the footprint block when a mean band column shows ~0.000: reactivity concentrated in a small footprint — a bloom_amount halo, a sparse figure — is diluted by the whole-frame mean, and before this reading existed the house workaround was binding a flash lever just so the report had something to see. On a backdrop-heavy preset the union mask approaches the whole frame and the reading degrades toward the mean column — it never sits meaningfully below it, so it fails toward the old behaviour rather than inventing reactivity.

Every one of those but the last two is a settled measurement: the capture holds one stimulus for every frame it renders, so each smoother has converged long before the pixels are read. That is the right question for “does it respond”, and it is exactly why those columns are identical for any [smoothing] constant.

The two motion readings

drive and rate were added by ADR-0134, which exists because the report could not see either axis. Every other statistic on this page is a settled differential — two frames captured a fixed count apart — so it cannot see rate at all, and the reactivity columns drive one band at a time, so they cannot see combined drive either. A preset needed four live passes with a person watching before the lane found its defect, and the harness stayed green and unchanged through all four.

drive is the silent 48-frame capture differenced against the fully-driven one: same frame count, same scene time, same REPORT_SIZE. It is the question the listener asks — does this change when the music does — and it is not the same question as the four band columns beside it, which drive one stimulus each. A preset reading several bands together can measure low on every one of them and still be strongly driven; one can read plausibly on all four and be driven by none of them together. A preset with no audio binding at all reads 0.000.

rate is the mean difference between consecutive frames over the settled tail of the transient probe’s loud plateau. It separates frozen from boiling, which is the axis the report has never had.

Both ride captures the report already takes — no extra render pass, no extra readback, no resize — so the full-library wall clock is unchanged.

Two things to know before reading either number:

  • rate is measured at PROBE_SIZE (96x96), not REPORT_SIZE (192x192) like every column printed beside it. It rides the probe’s frames, and the probe runs small for the reasons in the transient columns. frame_diff is a normalized mean so the two are broadly comparable, but “broadly” is not a property: never compare a rate against a number measured at another size. --json carries measured_at_px beside the value for exactly this reason.
  • A rate cell carries the same + mark the transient cells do, taken from the probe’s own rise_settled. An unsettled rise means the frames it averaged were still travelling toward the plateau, so the number describes the transient rather than the steady motion. The mark is common, not exceptional — see the transient section on why a great many presets never settle inside 48 frames.

Neither number is a gate, and rate does not sort the library. This is the finding the ADR turns on rather than a caveat bolted to it. Motion inside a static repeating structure reads as calm — the eye reads a pattern updating in place — while the same amount of motion in an unanchored world moves the whole sheet bodily and has to be far quieter to watch. Measured: fragment_tiledmono and fragment_drostemono both sit higher than a draft that was rejected for shaking, and both are comfortable. No pixel statistic in this harness models anchoring, so any threshold consistent with the rejected draft would fail two shipped presets. Read both columns against family neighbours, the way geom is read (ADR-0083), and never sort on them.

The transient columns

rise and fall are the one pair that is not settled. The probe drives a step — silence, a held stimulus, silence again — reads back every frame, and counts how many it takes for the frame to reach 90 % of its total change each way (ADR-0039). That makes ADR-0035’s { attack, release } pair visible: a scalar [smoothing] entry selects the same constant both ways, so its two numbers match; a pair snaps up and glides down, so fall runs well past rise.

Reading them:

  • 1 and 1 — no easing on whatever the stimulus drives. The frame is fully there the frame after the step.
  • equal, both large — a scalar [smoothing] entry, or an asymmetric one whose two constants are close.
  • fall much larger than rise — an { attack, release } pair doing its job.
  • 0 and 0 — the step did not move the frame at all. Check the reactivity columns: this usually means the preset does not respond to the stimulus, not that its easing is instant.

What the transient columns cannot see

The probe measures the frame, not the parameter. It reads pixels, so it can only see easing through whatever curve the scene puts between a bound value and its output — and for most scenes that curve is neither linear nor even monotone. Two consequences, both real and neither fixable by measuring harder:

  • A saturating response reads flat. rose_trails is the worked example: its 1.25 spin against a max-decay feedback drives the frame to the same place whatever thickness says, which is why the content lane once rendered five values from 1.10 to 2.30 — including the untouched original — and could not tell them apart. A preset like that will report a transient that has nothing to do with its [smoothing] table, and no column here will warn you.
  • The scene’s own motion is measured too. A fragment field’s fold, a feedback trail and a particle cloud all keep changing while the parameter settles, and the probe cannot separate that from the response. Over the shipped library this shows up as presets reporting fall below rise, which is backwards for any easing.

Measured over the shipped set on 2026-07-27 — a snapshot of what the probe sees, not a figure anyone maintains — presets carrying at least one { attack, release } entry had a median fall / rise of 1.02, with fall > rise in about half of them; presets with only scalar entries sat at 0.60, with barely any. So the columns separate the two populations directionally and lose the magnitude almost entirely: Smooth Pulse, the worked asymmetric example with a 0.60 s release, reads 26 / 31 where a purpose-built near-linear fixture at a 0.5 s release reads 3 / 61.

Those numbers were taken before the + marker existed, and every one of them would carry it today (Plan 0038 Phase 8). They were produced by exactly the defect this section goes on to describe: at PROBE_WINDOW = 48 a 0.60 s release is 1.33 τ, leaving ~26 % of the travel undone, and the fixture’s 3 / 61 is now known to be 3 / 69 when measured to settlement. Read the snapshot for its shape — two populations, separated directionally, magnitude lost — and not for its magnitudes, which is the same warning the rest of this section gives, now with the arithmetic behind it. It has not been re-taken: re-snapshotting is not what makes the columns trustworthy, marking them is.

Read the columns as evidence, not as a verdict. A wide fall / rise gap is good evidence the easing is working. A narrow one is not evidence it is broken. The place easing is proven is core/tests/easing.rs, against fixtures built to have a near-linear response precisely so the measurement is of the easing and not of a scene; everything else is a preset-shaped approximation of that.

One smaller limit, and it is sharper than it was first written: the probe’s window is 48 frames (0.8 s) each way, so a release constant longer than about 0.35 s does not fully settle inside it. This page used to say such a response “reads clamped” — it does not, and that word was the trap. frames_to_settle normalizes against the segment’s own last frame, so when that frame is still travelling the measured total is short and every threshold is crossed early. The number that comes back is not pinned at the window length and is not obviously wrong; it is a plausible, smaller frame count. Worse, the bias is uneven — the 0.9 threshold is pulled in harder than the 0.5 one — so a truncated fall also reads as a more even fall than it is.

That is not hypothetical. Plan 0038 Phase 3 measured two easing orderings, one of which had an effective time constant of 1.0 s against a 1.6 s window, and read the truncation as a difference in the shape of the two falls. Measured to settlement the two shapes are identical and differ only in speed — 73 frames against 145, where the truncated run said 61 against 78. See ADR-0040’s Outcome.

The rule, and the function that enforces it. frames_to_settle cannot detect this about itself: normalizing against the last frame guarantees the threshold is crossed inside the segment, so frames_to_settle(seg, f) < seg.len() is a tautology rather than a check. Before trusting a frame count, gate it on metrics::segment_settled(segment, tol), which extrapolates the geometric tail from three points spread across the segment and answers whether the last frame is within tol of the asymptote. Sample widely rather than from the end: captures are 8-bit, and a response slow enough to outrun its window moves by less than one code value per frame near the end, so adjacent frames decode as identical and read as settled exactly when they are not.

So --report marks rather than pretends (Plan 0038 Phase 8). A transient cell carries a + when segment_settled cannot certify the response arrived — 61+ means at least 61 frames, never 61. Each family’s table then names how many of its presets marked. --json carries the same fact as rise_settled and fall_settled booleans, so a consumer reading only the counts cannot mistake a truncated response for a settled one.

Expect most of the shipped set to mark, and for two different reasons the suffix does not separate. One is the window, above. The other is far more common here and is not a defect in the probe at all: a scene whose own motion never stops — a fragment field’s fold, a feedback trail, a particle cloud — has no asymptote to settle to, so segment_settled correctly declines to certify one. That is the same limitation this page already describes as “the scene’s own motion is measured too”; the mark just moves it from a caveat you have to remember into the cell itself. A preset whose cells are unmarked is the interesting case: it means the number is a measurement.

Widening the --report window does not fix that table’s separation problem, which is a different thing — measured at 96 frames the scalar-only median got worse (0.60 → 0.92) for double the wall clock. Scene saturation is what hides the magnitude there. Window length is what corrupts a slow response’s shape, and the two are not the same defect.

A low cover is not a defect

cover counts pixels differing from the corner-sampled background by more than a threshold, on any channel — a symmetric difference, so dark-on-light and light-on-dark are measured identically. An ink-remapped look is not penalised by construction, and a low reading is not evidence of one.

What a low cover means is that the frame is sparse, and sparse is often the intent. reaction_coral_bloom reports 0.128, about as low as the shipped set goes, and is healthy — it is the family’s ink-on-paper variant, a pale print whose chaotic-branching regime genuinely covers an eighth of the frame. The number is truthful; what it cannot tell you is “sparse on purpose” from “dead”.

So the column names suspects rather than convicting them. A low cover alongside a dead anim and flat reactivity columns is worth investigating; a low cover on a preset that is deliberately a thin figure on a wide ground is the report working.

The second reading: the same columns at realistic levels

Under each family’s table is a second block (ADR-0042):

at realistic levels (bass 0.661 mid 0.575 treb 0.281 onset 0.145) — read the *gap* ...
preset bass mid treb onset gates ceils occ
Aurora 0.183 0.165 0.212 0.025 0 1 0
Ember 0.078 0.053 0.032 0.035 0 0 0

The realistic levels changed meaning in Plan 0048. They are now fractions of each signal’s own recent peak (ADR-0049), not magnitudes, which is why they read 0.661 where they used to read 0.04. Any --report number quoted in an older commit message, ADR Outcome or backlog entry was measured on the raw scale.

The four columns are measured exactly as the ones above, from stimuli set to what real material produces instead of to 1.0. The gap between the two rows is the reading, not either number alone, and it has a direction:

what you seewhat it means
both healthythe preset responds at levels it will actually meet
full scale lively, realistic ~0gained or gated against a magnitude music never reaches. This is the defect that hid six presets for months
realistic close to full scalea compressive curve/smoothstep doing its job, or a binding already saturating at low input
realistic > full scalesomething inverted or saturating — a parameter past its useful range at full scale, reading back down

The last row is why the full-scale columns stayed. They also keep every number quoted in an older commit, ADR or backlog entry meaning what it said.

Two things this pair cannot see. beat is an event, not a magnitude, so it is true in both readings — a beat-latched binding holds across the gap while an onset-scaled one falls away, which is a distinction, not a fault. And the band array is on its own scale: the low stimulus lights the spectrum slice to the same level as the scalar, while real material’s per-band mean is 0.020 with peaks to 0.338, so a bin()-reading preset reads lower here than the scalar gap suggests.

Reachability: gates the probe never drove both ways

The gates and ceils counts are not measured from pixels at all. They come from walking each preset’s expression trees while evaluating them over 12 s of dynamic:110 through the real analyzer, recording which way every comparison and every select() condition went, and how close every clamp() came to its upper bound. A frame differential structurally cannot answer this — select(c, 6, 8) and select(c, 6, 6) diff identically, and neither names which gate.

Four kinds of finding come out of that walk. Three are named one per line underneath the table; the ceilings are summarized:

  • GATE — a select() whose condition never went both ways. One branch of the preset has never rendered. Named with its source text, so the threshold to re-gain is in front of you.
  • COMP — a comparison (> < >= <= == !=) that only ever took one value, so it read as a constant 0 or 1 (ADR-0043). This catches two shapes a GATE line cannot. One is the bare comparison as a whole binding — reseed = "onset > 0.55", the idiomatic boolean-param form, which holds no select() at all. The other is one half of a composite condition: in select(min(tempo > 124, bass + treb > 0.38), 4, 1) the GATE line names the whole min(...), and since a tempo gate is legitimately one-sided here (below), a reader would dismiss it — so each half is also reported on its own, and the excusable one can no longer launder the other.
  • SAT — a clamp() whose inner value sat at its upper bound for 90 % or more of the probe (ADR-0062). The binding is a gain that has stopped being a function of the audio: it reads as the constant its ceiling is, for anything above a whisper. Named one per line with its occupancy, because unlike a decorative ceiling this is a HARD failure — see the saturation gate.
  • clamp ceilings — a clamp() upper bound the value never approached. The bound is decorative and the parameter’s real range is narrower than it reads. These are not printed one per line: a single summary line per family gives the count and names the furthest three. All of them are in --json.

A comparison that is the direct condition of a select() reports once, as the GATE line only — that line already names it and says which branch never ran, which a COMP line cannot. So GATE and COMP never double-report the same finding.

The gates column counts GATE + COMP together: both say a branch of the preset’s behavior has never happened. ceils counts the ceilings, and occ counts the saturated clamps.

ceils and occ are opposite ends of one measurement, taken on the same traversal from the same two numbers — a clamp()’s inner value and its upper bound. ceils asks how close the value ever came (its peak, as a fraction of the bound) and fires when the answer is “never near”: the ceiling is decorative. occ asks how long the value stayed there (the fraction of hops at or above the bound) and fires when the answer is “always”: the ceiling never released. A clamp can trip at most one of them, and the healthy state is neither — a bound reached on peaks and released in between. occ is by far the more serious of the two, which is why it is the one that is gated.

A flag is a suspect, not a conviction. It says this stimulus never drove the gate both ways, which is a fact about the probe as much as about the preset. The standing false positive is tempo: the probe runs at one BPM, so select(tempo > 132, ...) is correctly one-sided and will flag forever. Check those two by hand with --set tempo=90 / --set tempo=160 (see Examples); a gate on a band is the one worth acting on.

The probe runs 12 s rather than the 4 s a --signal filmstrip synthesizes, because the tempo tracker needs about 4 s to lock. Under a short clip tempo reads a flat 0 and every tempo comparison flags for the wrong reason.

The GATE and COMP half of this is advisory output. It is not a CI gate, and deliberately so — and as of Plan 0048 both of the reasons are live again. (The SAT half is gated, for reasons that do not apply to it — see below.)

The instrument is one of them, and that has not changed: the tempo single-BPM false positive above accounts for 17 of the 26 flags the shipped set currently produces, so a naive “fail if flags > 0” would fail CI permanently and a threshold would be tuned to noise. The precondition remains a multi-BPM probe or an explicit tempo exemption (ADR-0043).

The library is the other, and it regressed on purpose before being put back. Plan 0042’s re-audit measured 0 genuinely dead gates, and that held until ADR-0049 changed what a band level means: nine bindings written against raw levels then compared against normalized ones and never went false, so their else branches were dead. That was the priced cost of the one-time retune ADR-0049 chose, and Plan 0048 Phase 7 cleared it — the library is back to zero genuinely dead gates, with the residual flags all tempo one-sidedness.

The walk used to be unable to see a gain, and now it can. A comparison is a fork it can watch; clamp(bass * 16, 0, 0.3) is not — it has no select(), no comparison, nothing two-valued, and it is simply an arithmetic expression that has quietly become a constant. Plan 0048 Phase 7 measured 263 of 332 clamped band terms pinned at their ceiling, and 14 presets with no live audio term at all, none of it visible to any instrument this project had. The occ column and the SAT lines are that instrument (ADR-0062), and unlike the rest of the reachability block they are backed by a gate.

Saturation: a HARD gate on clamp occupancy

core/tests/saturation.rs runs the same walk over the embedded set and fails the build on any clamp() whose occupancy reaches the threshold. It is the one part of the reachability block that is not advisory, and the reason is the one Plan 0048 Phase 7 supplies: for the whole window between ADR-0049 landing and the retune, every automated signal was green and nobody had cause to run a report. An instrument that requires suspicion to fire does not address the failure that there was nothing to be suspicious of.

The threshold is a measured constant (SATURATED_OCCUPANCY), taken from the retuned library’s own distribution rather than reasoned to, and it therefore has a shelf life: re-measure it whenever the library changes materially.

A clamp() that is supposed to pin — a safety rail whose job is to bind at peak — declares itself in the preset:

[occupancy]
exempt = ["fade"] # this clamp is a rail, not a gain: pinning is the design

An exemption silences the gate, not the diagnostic: the binding still shows up as a SAT line and in the occ count, so it stays visible in review. That is deliberate — an exemption is a place to hide, and the mitigation is that it is explicit, in the file, and still reported.

Built from a8ce055 at version 0.115.0. This site tracks main and is not versioned per release.