Ψ-W3 remediation: fix the verify doc's wrong render source, add missing content checks, soften unverified claims

Corrected the Render-dialog source name the refusal's evidence depends on, added a by-ear content check and a file-size channel proxy, and stopped two comments from overclaiming.
This commit is contained in:
2026-08-01 23:01:50 -04:00
parent f69c4bf6bf
commit 2a9ab65944
11 changed files with 125 additions and 46 deletions
+27 -5
View File
@@ -527,11 +527,16 @@ message where a correct summed file used to land.
**Intended fix.** Run the observation in `docs/verify-track-scope-multitrack.md` §3 (a
hand-driven Render dialog, source "selected tracks via master", one literal filename, two
tracks selected — then count the files REAPER writes). If it comes back "one summed
file", the refusal is over-strict for the TRACK scope and should be narrowed back; the
ITEM-scope half stays regardless, since a per-item-track render is not the sum the user
asked for either way. If it comes back "one file per track", nothing to do and the
inference is retired into fact.
tracks selected — then count the files REAPER writes). If it comes back "one file per
track", nothing to do and the inference is retired into fact. If it comes back "one
summed file", the refusal is over-strict for the TRACK scope and should be narrowed back
— and the ITEM-scope half is then an OPEN question, not settled: a full-extent item
capture already sums a multi-track item selection via `&32|single-file`
(`tests/test_render_settings.cpp:262`), so if `&128` also sums, a ranged item capture
routed through it sums too, and keeping the item refusal in that branch would make item
scope inconsistent with itself across the range boundary (full-extent sums, ranged
refuses, same scope). Whether that inconsistency is acceptable or the item refusal should
narrow too needs its own look at that point — not decided here.
**The constraint the fix MUST handle.** Narrowing the refusal must keep the ITEM scope
refusing, must keep `renderOffline` the single seam (so a recipe replay cannot diverge
@@ -645,3 +650,20 @@ stereo file today.
**Done looks like.** A dead-center instrument bake lands as a 1-channel file with
`Sample::channelCount` matching, the same way an offline dead-center capture does; a
true-stereo bake is byte-identical to today's output.
## A 0-byte render can pass every gate and land as `Ok` (pre-existing, not a Ψ-W3 regression)
**Context (surfaced by Ψ-W3 review).** `OfflineRenderBackend::capture`'s exists-check
(`capture.cpp:489`) passes for a 0-byte file, and the bounds gate (`:507-546`) only fires
when `expectedFrames > 0` — an invalid/empty layout reads `expectedFrames == 0` and skips
the gate rather than refusing. A 0-byte render can therefore reach `stampCaptureSample`
and land as `CaptureStatus::Ok` with an empty `contentHash` and `channelCount == 0`.
**Not introduced by Ψ-W3.** The exists-check and the `expectedFrames > 0` guard both
predate this track; Ψ-W3 only added the mono-collapse failure report that sits downstream
of this hole and was careful not to assert bytes it never verified (see
`reportCollapseFailure` in `capture.cpp`).
**Intended fix.** After the exists-check, also reject a 0-byte file explicitly (its own
status, not folded into `BoundsMismatch`, since a 0-byte file was never bounds-checked at
all) before anything downstream reads it.
+28 -11
View File
@@ -30,9 +30,15 @@ Read off:
to ±1 — what you are confirming here is that it landed at all.
- The REAPER console shows **no** `ReaSampler capture failed:` line.
- Track `A` is still the only selected track afterwards.
Repeat with a **razor area** on `A` over the same 1012 s span and no time selection: same
three readings.
- **Content, not just length.** Listen to the landed file. `A` and `B` carry *audibly
different* content by the project setup above (tone vs. drum loop), so this is a by-ear
check, not a null test: the capture must be the tone alone, with **no** drum-loop bleed.
Expected: pure tone, matching `A` soloed. Failing: any trace of `B`'s drum loop audible
in the file. This is not a tautology check — the SDK header's own `RENDER_SETTINGS` line
admits a second reading, `(&(1|2)==0)=master mix`, under which a single-track track
capture could render the **whole master mix** (both `A` and `B`) rather than `A` alone;
drum-loop bleed here is exactly what that misreading would produce, and this is the
cheapest place in the whole doc to catch it.
**This is the byte-identical floor.** If either cell now refuses, the change is wrong —
the refusal must fire only above one track.
@@ -69,7 +75,10 @@ are believed to produce N files. That has never been observed.
Drive REAPER's own Render dialog by hand, with the extension out of the loop:
1. Select `A` and `B`.
2. File → Render. **Source:** *Selected tracks (stems)* — the dialog wording for `&128`.
2. File → Render. **Source:** *Selected tracks via master* — the dialog wording for `&128`
(SDK header ~3041). Do **not** pick *Stems (selected tracks)* — that is `&2`, a
different source bit that unambiguously writes one file per track and would confirm
nothing about `&128`.
**Bounds:** *Custom time range*, 10.000 to 12.000 s.
3. **File name:** a literal stem with **no wildcards at all** — e.g. `stemprobe`. Clear
`$track` / `$item` / anything else from the pattern; the extension writes exactly one
@@ -122,16 +131,24 @@ the divergence from §2 is deliberate and this cell is what confirms it is real.
## 7. Mono collapse — what is and is not reachable
Capture a range on a track whose content is dead-center (a mono source panned center, or
a duplicated-channel file). Read off:
a duplicated-channel file), using time selection **10.000 s to 12.000 s** (2.000 s, 96000
frames at 48000 Hz — the §1 convention, so the resulting file size is exact). The panel has
no channel-count readout anywhere (`Sample::channelCount` is not drawn by
`src/shell/panel/panel_render.cpp`), so read the proxy instead:
- The panel card reports **1 channel**.
- The console shows **no** `the lossless mono collapse ... landed intact, as captured.`
line.
- Check the landed `.wav`'s size on disk (Explorer → Properties, or a directory listing). A
successful collapse is the extension's own rebuild — canonical 44-byte header + 96000 ×
4 bytes = **384,044 bytes**. A file near double that (~768,044 bytes, plus whatever
REAPER's own render adds for `bext`/metadata chunks) means the collapse did not fire —
recheck the source is genuinely dead-center before treating this as a defect.
- The console shows **no** `the lossless mono collapse ... already reached the bank; only
the size win from the collapse was lost.` line.
**Not DAW-reachable:** the collapse's *failure* branch. It fires only if the captured file
cannot be read, or its temporary rewrite cannot be written or renamed, inside the same
call that just rendered the file — there is no manual way to inject that fault between the
render and the rename. The branch is covered only at its reporting seam
(`tests/test_wav_codec.cpp`, `testCollapseOutcomesReportDistinctly`), and its console line
has never been seen in a running REAPER. If you ever do see it, the capture in question is
intact and correctly measured — only the size win was lost.
(`tests/test_wav_codec.cpp`, `testCollapseOutcomeSuffixesAreDistinctStrings`), and its console line
has never been seen in a running REAPER. If you ever do see it, the render already reached
the bank — the report only tells you the collapse's size win was lost, not that the bytes
were verified (see `docs/TODO.md`'s 0-byte-render entry).