Close three critical review findings on the render-bounds-channel verdict
Verdict can no longer print a false EXACT on an on-grid end, no longer names a bounds channel a content-derived render never consulted, and the grid-align doc premise is corrected without implementing it.
This commit is contained in:
+69
-20
@@ -710,37 +710,86 @@ refusal reuses `CaptureStatus::BoundsMismatch` rather than minting its own statu
|
||||
earlier note here preferred a distinct status, and that preference is unresolved, not
|
||||
withdrawn.
|
||||
|
||||
## An offline capture can be refused for a short render — root cause open
|
||||
## An offline capture can be refused for a short render — the millisecond floor
|
||||
|
||||
**Symptom (live, 2026-08-02).** A capture over [0.000000s, 4.067797s) at 48 kHz was
|
||||
refused: `Render produced 195216 frames but the requested range is 195254`. 38 frames
|
||||
short — 38x the gate's one-frame tolerance, so the tolerance is not what refused it.
|
||||
**Measured cause (live, 2026-08-02).** Two captures at 48 kHz, `TailMode::None`, matched
|
||||
their landed frame count to the frame with their window's END floored to the millisecond:
|
||||
`[0s, 1.6551724137931001s)` printed 79440 of 79448 (the console's own `%.17g` read-back),
|
||||
and `[0s, ~4.067797s)` — the double nearest the six-decimal value the original refusal
|
||||
actually printed, `4.0677966101694913`, not itself a captured console value — printed
|
||||
195216 of 195254. A three-checkpoint read-back on `RENDER_STARTPOS`/`RENDER_ENDPOS` was
|
||||
silent at store time and immediately before the render, so REAPER writes the floored end
|
||||
back as a side effect of rendering, not before it. Mechanism, why two
|
||||
`RENDER_BOUNDSFLAG` channels exist, and the live experiment this observation opened:
|
||||
`src/core/capture/render_settings.h`'s `RenderBoundsChannel` — the one narrative home;
|
||||
this entry stays the record of what was actually measured.
|
||||
|
||||
**Hypothesis A — the render bounds itself to the media it can see.** REAPER's
|
||||
selected-items render source (`&32`) derives its bounds from the selected items' own
|
||||
extents (`src/core/capture/CLAUDE.md` §Gotchas — itself an inference from an observed
|
||||
defect, not a header fact). If a time-bounded selected-tracks render (`&128`) does the
|
||||
same thing against content extent, a range running past the end of its material comes up
|
||||
exactly as short as the material is.
|
||||
**Disproven by that observation.** The two hypotheses this entry previously carried — that
|
||||
the render bounds itself to the media it can see, and that a trailing-silence trim fires
|
||||
despite `RENDER_NORMALIZE = &(4<<16)` — both predict a shortfall tracking CONTENT. This
|
||||
one tracks the WINDOW: it is the exact ms-floored count, whatever the material does.
|
||||
Neither is the cause. Do not reinstate either without a fresh observation.
|
||||
|
||||
**Hypothesis B — a trailing-silence trim fires anyway.** `TailMode::None` sets
|
||||
`RENDER_NORMALIZE = &(4<<16)` (disable all postprocessing) and `RENDER_TRIMEND = 0`. If
|
||||
REAPER trims regardless of that bit, a range whose material decays before its end loses
|
||||
exactly the decayed frames.
|
||||
**Still open — the START edge.** Every observation to date started at `0s`. Floor, ceil
|
||||
and round all leave `0s` alone (it is exactly representable in binary), so nothing is
|
||||
known about whether the start floors too, and it is the case that matters most: an end
|
||||
floor refuses loudly, a start floor would shift content and break the null test silently.
|
||||
|
||||
**A premise NOT to build on — an on-grid value is not uniformly safe from a bare floor.**
|
||||
That claim holds for `0s` only because `0` is exact in binary; it is FALSE in general for
|
||||
a decimal millisecond grid point: `1.007 * 1000 == 1006.9999999999999` (floors to 1006,
|
||||
not 1007), and `4.068 * 1000 == 4067.9999999999995` (floors to 4067, not 4068). If a
|
||||
future fix compensates for a discovered START floor by grid-aligning the extraction — e.g.
|
||||
slicing a buffer from `floor(start_ms)` — and builds that arithmetic on the false premise,
|
||||
a `1.007`-class start would resolve a whole millisecond early: frame count right, content
|
||||
shifted, exactly the silent null-test misalignment this effort exists to catch. Neither
|
||||
live observation above can detect this hazard (both are off-grid sub-millisecond
|
||||
remainders, not grid points) — a `1.007`-class grid point must be measured in the DAW
|
||||
before any extraction logic is built on this premise. `render_window`'s own
|
||||
`isOnMillisecondGrid`/`floorToMilliseconds` already handle this correctly, but only on OUR
|
||||
side of the boundary; that tolerance cannot influence how REAPER itself resolves a value we
|
||||
hand it, which is the open question here, not a closed one.
|
||||
|
||||
**Still open — where the floor lives.** Custom time bounds is one of eight
|
||||
`RENDER_BOUNDSFLAG` modes. Whether the floor sits in that field's own render-time
|
||||
resolution or downstream in the render engine (where no mode escapes it) cannot be
|
||||
answered from the SDK header. `RenderBoundsChannel`
|
||||
(`src/core/capture/render_settings.h`) is the experiment; `docs/VERIFICATION.md`
|
||||
§Capture range and bounds is the one smoke run that settles it.
|
||||
|
||||
**Not excluded — the gate itself.** `renderHonoredBounds`' one-frame tolerance is
|
||||
empirical, not proven (`src/core/capture/render_window.h`): a renderer that resolves the
|
||||
window's two edges by DIFFERENT conventions can sit two frames from `frameCountFor`'s
|
||||
answer on a correctly-honored render. That cannot account for 38 frames, so it is not
|
||||
this refusal — but it means a future one- or two-frame refusal may be ours, which is why
|
||||
these refusals — but it means a future one- or two-frame refusal may be ours, which is why
|
||||
the tolerance was not widened on speculation. Widening it is a precision-invariant
|
||||
decision, not a bug fix.
|
||||
|
||||
**How it gets decided.** `docs/VERIFICATION.md` §Capture range and bounds, the three
|
||||
numbered blocker steps: step 1 separates A's `&32` path from the shared `&128` path (and
|
||||
says how to tell when it failed to), step 2 asks whether the render is short at all, step
|
||||
3 reads the retained refused render to place the missing frames. Nothing here should be
|
||||
"fixed" before that comes back.
|
||||
## `capture.cpp` is over the ~600-line ceiling — documented, not split mid-experiment
|
||||
|
||||
**Context.** The bounds-channel live experiment (`RenderBoundsChannel`, this same
|
||||
section above) added the time-selection guard/read-back plumbing and the always-on
|
||||
verdict print to `OfflineRenderBackend::capture`, landing the file at 697 lines against
|
||||
root `CLAUDE.md`'s ~600-line ceiling. The named seam: the drift/verdict instrumentation
|
||||
block (`ScopedTimeSelection`/read-back/drift-report/verdict-print, roughly
|
||||
`capture.cpp:449-655`).
|
||||
|
||||
**Deferred, not silent.** ≈60 of the added lines are temporary probe instrumentation
|
||||
with a known removal date (the experiment closes when `docs/VERIFICATION.md` §Capture
|
||||
range and bounds comes back), and splitting the file mid-experiment risks moving the
|
||||
exact code the smoke run is measuring. Split after the experiment closes, onto the seam
|
||||
named above.
|
||||
|
||||
## bext TimeReference read-back is not a floor detector (dead end, recorded so it is not re-litigated)
|
||||
|
||||
Idea considered and dropped: read a captured file's `BWF:TimeReference` tag back as
|
||||
independent evidence on the START-edge millisecond-floor question above. `WDL/metadata.h`'s
|
||||
`WriteMetadataPrefPos` only writes it past its `prefpos > 0.0` guard (`:1301`) — that guard
|
||||
alone is enough to rule the approach out. One nuance worth recording separately: the
|
||||
millisecond quantization at `:1382-1383` (`AddMexMetadata`'s `ParseUInt64(val)/1000.0`)
|
||||
belongs to the MEX caller, not proven to be `WriteMetadataPrefPos`'s own behavior or the
|
||||
renderer's direct call into it — so even without the guard, a floored bext tag would show
|
||||
that MEX quantizes, not that the render engine does.
|
||||
|
||||
## Split `render_bounds_gate` on the verdict/message vs. filesystem seam
|
||||
|
||||
|
||||
Reference in New Issue
Block a user