Ψ-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
+5
View File
@@ -117,6 +117,11 @@ bool isMultiTrackStemRender(SourceMode mode, int sourceTrackCount) {
}
std::string multiTrackRefusalMessage(CaptureScope scope) {
// Deliberately does not name realtime capture as a way out, though it is the one
// action that sums correctly here: realtime is non-deterministic (hardware/performed
// FX, no bit-identical-repeats guarantee), so pointing an offline refusal at it would
// trade one invariant for another rather than just naming a substitute. A stated
// choice, not an oversight.
switch (scope) {
case CaptureScope::Item:
return "This range is narrower than the selected items, so it renders "
+3 -2
View File
@@ -1,8 +1,9 @@
#pragma once
// render_settings — the REAPER-free logic behind the capture action family:
// sourceMode -> RENDER_SETTINGS bits, P_RAZOREDITS parsing + range union,
// razor-else-time inference, the FX-scope bypass plan, and the capture-action
// table main.cpp iterates. Bit MEANINGS below are transcribed verbatim from
// razor-else-time inference, the FX-scope bypass plan, the capture-action
// table main.cpp iterates, and the multi-track-stem refusal + its user-facing
// message text. Bit MEANINGS below are transcribed verbatim from
// reaper_plugin_functions.h; the CHOICE of which bits each mode sets is tested.
#include <string>
+5 -1
View File
@@ -129,7 +129,11 @@ enum class MonoCollapseOutcome {
// The capture message's collapse suffix — empty for Declined, so a capture that had
// nothing to collapse reads exactly as it did before the collapse existed. Shared by
// both backends so one outcome cannot be reported two ways.
// both backends so one outcome cannot be reported two ways. NOT user-observable on its
// own: CaptureResult::message on a successful capture is never printed by any caller, so
// the Collapsed/Failed text this returns reaches no one today — the one observable
// channel for a genuine Failed outcome is the backends' own reportCollapseFailure
// console line.
std::string monoCollapseSuffix(MonoCollapseOutcome outcome);
// --- Content identity (dedup hashes) -----------------------------------------
+20 -14
View File
@@ -231,23 +231,28 @@ CaptureName captureNameFor(const std::vector<std::string>& sourceNames,
namespace {
// One console line per genuine collapse failure. The capture itself is intact and was
// measured after this step, so the failure costs only the size win — but silence here is
// what made a failed rewrite read exactly like a legitimately stereo capture.
void reportCollapseFailure(const std::string& absolutePath, const char* what) {
ShowConsoleMsg(("ReaSampler capture: the lossless mono collapse " + std::string(what) +
" -- " + absolutePath +
" landed intact, as captured.\n").c_str());
// One console line per genuine collapse failure — silence here is what made a failed
// rewrite read exactly like a legitimately stereo capture. Deliberately does NOT claim
// the captured bytes are intact: a 0-byte render can reach this branch too (it passes the
// exists/bounds gates upstream; see docs/TODO.md), and this path never verified the bytes
// it's reporting on.
void reportCollapseFailure(const std::string& absolutePath, const char* what,
const char* consoleLabel) {
ShowConsoleMsg((std::string(consoleLabel) + ": the lossless mono collapse " +
std::string(what) + " -- " + absolutePath +
" already reached the bank; only the size win from the collapse "
"was lost.\n").c_str());
}
} // namespace
MonoCollapseOutcome collapseCapturedFileToMono(const std::string& absolutePath) {
MonoCollapseOutcome collapseCapturedFileToMono(const std::string& absolutePath,
const char* consoleLabel) {
const std::vector<std::uint8_t> bytes = util::readFileBytes(absolutePath);
if (bytes.empty()) {
// Failed, not Declined: the read that would have decided never happened, so
// "the channels differ" is a claim this path cannot make.
reportCollapseFailure(absolutePath, "could not read the captured file");
reportCollapseFailure(absolutePath, "could not read the captured file", consoleLabel);
return MonoCollapseOutcome::Failed;
}
@@ -265,7 +270,7 @@ MonoCollapseOutcome collapseCapturedFileToMono(const std::string& absolutePath)
{
std::ofstream out(tempPath, std::ios::binary | std::ios::trunc);
if (!out) {
reportCollapseFailure(absolutePath, "could not open its temporary file");
reportCollapseFailure(absolutePath, "could not open its temporary file", consoleLabel);
return MonoCollapseOutcome::Failed;
}
out.write(reinterpret_cast<const char*>(collapse.bytes.data()),
@@ -275,7 +280,7 @@ MonoCollapseOutcome collapseCapturedFileToMono(const std::string& absolutePath)
if (!wroteOk) {
std::error_code ec;
std::filesystem::remove(tempPath, ec);
reportCollapseFailure(absolutePath, "could not write the rebuilt file");
reportCollapseFailure(absolutePath, "could not write the rebuilt file", consoleLabel);
return MonoCollapseOutcome::Failed;
}
}
@@ -283,7 +288,7 @@ MonoCollapseOutcome collapseCapturedFileToMono(const std::string& absolutePath)
std::filesystem::rename(tempPath, absolutePath, ec);
if (ec) {
std::filesystem::remove(tempPath, ec); // don't leave litter on a failed rename
reportCollapseFailure(absolutePath, "could not replace the captured file");
reportCollapseFailure(absolutePath, "could not replace the captured file", consoleLabel);
return MonoCollapseOutcome::Failed;
}
return MonoCollapseOutcome::Collapsed;
@@ -497,8 +502,9 @@ CaptureResult OfflineRenderBackend::capture(const CaptureRequest& request) {
// (within a tolerance, see below) the requested window's frames, so a source
// mode that silently widened the render fails loudly here instead of landing as
// a successful capture. Auto and Manual add frames by design and are skipped.
// (The landed file is read three times on this path — this gate, the mono collapse,
// and stampCaptureSample — plus one rewrite when the collapse fires; a
// (On TailMode::None the landed file is read three times on this path — this gate,
// the mono collapse, and stampCaptureSample — plus one rewrite when the collapse
// fires; Auto/Manual skip this gate entirely, so they read it twice. A
// once-per-capture cost on an already-warm file, judged acceptable.) A
// bounded/header-only read is not a clean substitute: parseWavLayout only marks the
// data chunk valid when the buffer holds the chunk's FULL declared body
+12 -9
View File
@@ -80,14 +80,14 @@ struct CaptureRequest {
// Every failure is an explicit code, never a thrown exception across the REAPER boundary.
enum class CaptureStatus {
Ok,
NoProject, // no active project to render / resolve a bank folder
EmptyRange, // start >= end: nothing to render
UnsupportedMode, // backend does not implement this source mode
UnsupportedFormat, // requested bit depth has no known REAPER blob (Float32 only)
RenderFailed, // the render action ran but produced no output file
TransportBusy, // realtime backend: transport already playing/recording — refused
MultiTrackSelection,// a selected-tracks render over >1 track — would render N files
BoundsMismatch, // the rendered file's frame count is not the requested window's
NoProject, // no active project to render / resolve a bank folder
EmptyRange, // start >= end: nothing to render
UnsupportedMode, // backend does not implement this source mode
UnsupportedFormat, // requested bit depth has no known REAPER blob (Float32 only)
RenderFailed, // the render action ran but produced no output file
TransportBusy, // realtime backend: transport already playing/recording — refused
MultiTrackSelection, // a selected-tracks render over >1 track — would render N files
BoundsMismatch, // the rendered file's frame count is not the requested window's
};
struct CaptureResult {
@@ -130,7 +130,10 @@ CaptureName captureNameFor(const std::vector<std::string>& sourceNames,
// A Failed outcome is ALSO logged to the console here, because a successful capture's
// CaptureResult::message is not printed by any caller — the return value alone would
// leave a genuine I/O failure indistinguishable from a legitimately stereo capture.
MonoCollapseOutcome collapseCapturedFileToMono(const std::string& absolutePath);
// `consoleLabel` matches each caller's own console-prefix convention (offline:
// "ReaSampler capture"; realtime: "ReaSampler realtime capture").
MonoCollapseOutcome collapseCapturedFileToMono(const std::string& absolutePath,
const char* consoleLabel = "ReaSampler capture");
// Stamps the metadata shared by both backends onto `s`: trackGuids (echoed from the
// request) + channelCount (measured from the produced file's `fmt`; 0/unknown as the
@@ -186,7 +186,8 @@ CaptureResult finalizeRecording(ReaProject* proj, MediaTrack* temp,
// Channel-domain rewrite, after the frame-domain trim so it acts on the final
// frame set; it preserves the frame count, so the trimmed length above still holds.
const MonoCollapseOutcome collapseOutcome = collapseCapturedFileToMono(destPath);
const MonoCollapseOutcome collapseOutcome =
collapseCapturedFileToMono(destPath, "ReaSampler realtime capture");
// Pure recorded-capture -> Sample mapping (identity, bounds echo, tier).
RecordedCapture cap;
+2 -1
View File
@@ -32,7 +32,8 @@
// it never sums back into the master — no feedback, no monitoring double).
// Multiple selected tracks sum in the one temp track — a real mix, which is why
// realtime accepts a multi-track selection where the offline track scope refuses
// it (that render source cannot express a sum; see shell/capture/CLAUDE.md).
// it (the offline render source is read as emitting one file per track, DAW-
// unverified — see src/shell/capture/CLAUDE.md §Gotchas).
//
// Why this needs no FxBypassGuard: CreateTrackSend defaults to I_SENDMODE=0
// (post-fader), which taps the source track after its own FX/fader/pan — its