// Standalone tests for reasampler::render_settings — no REAPER, no framework.
// Covers the pure pieces behind the capture family: the source-mode ->
// RENDER_SETTINGS bit mapping, the TailMode -> RENDER_* (tail/normalize/trim-end)
// mapping + the -72 dB derived ratio + the 8 s manual clamp, P_RAZOREDITS parsing
// -> ranges + union, scope -> source mode, range inference (razor-else-time), the
// FX-bypass plan (corrects the "items captured through parent FX" defect), and the
// capture-action taxonomy table (stable ids, scope x tail-variant matrix).

#include "../src/core/capture/render_settings.h"

#include <cmath>
#include <cstdio>
#include <cstring>
#include <set>
#include <string>

using namespace reasampler;
using namespace reasampler::capture;

static int g_fail = 0;
#define CHECK(cond) do { if(!(cond)) { \
    std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++g_fail; } } while(0)

// --- renderSettingsFor: wet-only bit mapping ---------------------------------

static void testMasterMixWet() {
    // Master mix -> value 0 (no source bits), supported.
    RenderSettingsChoice c = renderSettingsFor(SourceMode::MasterMix, 1.0);
    CHECK(c.settings == kRenderMasterMix);
    CHECK(c.supported);
    // TimeSelection aliases master mix — same result.
    CHECK(renderSettingsFor(SourceMode::TimeSelection, 1.0).settings == kRenderMasterMix);
    // wetDry argument is irrelevant (all three-scope capture actions are wet); passing 0.0
    // must still yield the same wet master-mix bits.
    CHECK(renderSettingsFor(SourceMode::MasterMix, 0.0).settings == kRenderMasterMix);
}

static void testSelectedTracksWet() {
    // Selected tracks -> via master (&128), header-confirmed.
    RenderSettingsChoice c = renderSettingsFor(SourceMode::SelectedTracks, 1.0);
    CHECK(c.settings == kRenderSelTracksViaMaster);
    CHECK(c.supported);
}

static void testSelectedItemsSingleFile() {
    // Items render to ONE file (single-file bit set) so N items -> 1 bank entry.
    RenderSettingsChoice c = renderSettingsFor(SourceMode::SelectedItems, 1.0);
    CHECK((c.settings & kRenderSelItems) != 0);
    CHECK((c.settings & kRenderSingleFile) != 0);
    CHECK(c.supported);
}

static void testRazorSingleFile() {
    // Razor edits render to ONE file (same single-file rationale as items).
    RenderSettingsChoice c = renderSettingsFor(SourceMode::RazorArea, 1.0);
    CHECK((c.settings & kRenderRazorEdits) != 0);
    CHECK((c.settings & kRenderSingleFile) != 0);
    CHECK(c.supported);
}

static void testRealtimeIsUnsupportedOffline() {
    // The realtime mode is not an offline-render source — must report unsupported
    // so the offline backend refuses it rather than rendering the master mix.
    CHECK(!renderSettingsFor(SourceMode::Realtime, 1.0).supported);
}

static void testEveryRenderSourceLabelIsPinnedVerbatim() {
    // docs/VERIFICATION.md's short-render bullet asks Daniel to report the refusal
    // line back verbatim, and that line always carries the render source
    // (render_bounds_gate.cpp appends "Render source: <label>."), so every label
    // is pinned to its literal — a typo in any of them breaks the report that
    // quotes it, and only a literal catches that.
    CHECK(std::strcmp(renderSourceLabel(SourceMode::MasterMix), "master mix") == 0);
    CHECK(std::strcmp(renderSourceLabel(SourceMode::TimeSelection), "master mix") == 0);
    CHECK(std::strcmp(renderSourceLabel(SourceMode::SelectedTracks),
                      "selected tracks via master") == 0);
    CHECK(std::strcmp(renderSourceLabel(SourceMode::SelectedItems),
                      "selected media items") == 0);
    CHECK(std::strcmp(renderSourceLabel(SourceMode::RazorArea), "razor edits") == 0);
    CHECK(std::strcmp(renderSourceLabel(SourceMode::Realtime), "realtime record") == 0);
}

static void testLabelsSeparateExactlyWhatTheRenderSeparates() {
    // The labels partition the offline modes the way RENDER_SETTINGS does, and no
    // finer: same bits => same words (MasterMix/TimeSelection both render the master
    // mix, unqualified — both hand their window over the same time-selection bounds
    // mode), different bits => different words. Naming two modes apart that render
    // identically would put a distinction in a bug report that does not exist in
    // the render.
    const SourceMode offline[] = {
        SourceMode::MasterMix, SourceMode::TimeSelection, SourceMode::SelectedTracks,
        SourceMode::SelectedItems, SourceMode::RazorArea,
    };
    constexpr std::size_t n = sizeof(offline) / sizeof(offline[0]);
    for (std::size_t i = 0; i < n; ++i) {
        const char* label = renderSourceLabel(offline[i]);
        CHECK(label != nullptr && label[0] != '\0');
        CHECK(std::strcmp(label, "unknown") != 0);
        CHECK(renderSettingsFor(offline[i], 1.0).supported);
        for (std::size_t j = i + 1; j < n; ++j) {
            const bool sameRender = renderSettingsFor(offline[i], 1.0).settings ==
                                    renderSettingsFor(offline[j], 1.0).settings;
            const bool sameLabel =
                std::strcmp(label, renderSourceLabel(offline[j])) == 0;
            CHECK(sameRender == sameLabel);
        }
    }
    // Realtime is not an offline render source at all, so it names its own mechanism
    // rather than a RENDER_SETTINGS value — outside the partition above by design.
    CHECK(!renderSettingsFor(SourceMode::Realtime, 1.0).supported);
    CHECK(std::strcmp(renderSourceLabel(SourceMode::Realtime),
                      renderSourceLabel(SourceMode::MasterMix)) != 0);
}

// --- tail: TailMode -> RENDER_* mapping (docs/product/capture-tail.md) --------

static TailRenderSettings tailFor(TailMode mode, double manualTailMs) {
    return tailRenderSettingsFor(mode, manualTailMs);
}

static void testTailNoneIsExactBounds() {
    // None -> exact bounds, byte-identical to the pre-tail capture: tail flag clear,
    // 0 ms, disable-all normalize (the current default), no trim. Asserting the exact
    // bit values (not just "some value") pins the byte-identical contract: if the
    // mapping regressed to set a tail bit or a non-disable-all normalize, this fails.
    TailRenderSettings t = tailFor(TailMode::None, 0.0);
    CHECK(t.tailFlag  == kTailFlagNone);       // 0
    CHECK(t.tailMs    == 0.0);
    CHECK(t.normalize == kNormalizeDisableAll); // 262144
    CHECK(t.trimEnd   == 0.0);
    // manualTailMs must be ignored for None (a stray tail from a leftover ms is the bug).
    TailRenderSettings t2 = tailFor(TailMode::None, 5000.0);
    CHECK(t2.tailFlag == kTailFlagNone);
    CHECK(t2.tailMs   == 0.0);
}

static void testTailAutoIsSurgicalTrim() {
    // Auto -> the time-selection tail bit, 8 s cap, SURGICAL normalize (ONLY &32768),
    // and the -72 dB TRIMEND ratio. The disable-all bit must NOT be set (it is
    // semantically opposed to trim — this catches a regression to the None normalize).
    TailRenderSettings t = tailFor(TailMode::Auto, 0.0);
    CHECK(t.tailFlag  == kTailFlagTimeSelection);  // &4
    CHECK(t.tailMs    == kMaxTailMs);              // 8000
    CHECK(t.normalize == kNormalizeTrimEnd);       // exactly 32768, nothing else
    CHECK((t.normalize & kNormalizeDisableAll) == 0);  // disable-all is NOT set
    // TRIMEND is the derived -72 dB ratio ~= 0.00025119 (the DAW-confirm value).
    CHECK(std::fabs(t.trimEnd - 0.00025119) < 1e-8);
    // manualTailMs is ignored for Auto (Auto always uses the 8 s cap).
    CHECK(tailFor(TailMode::Auto, 3000.0).tailMs == kMaxTailMs);
}

static void testAutoTrimRatioDerivesFromDb() {
    // The ratio must DERIVE from the -72 dB constant (10^(dB/20)), not be a hardcoded
    // float — recompute it independently and require an exact match with the mapping.
    double expected = std::pow(10.0, kAutoTrimThresholdDb / 20.0);
    CHECK(autoTrimEndRatio() == expected);
    CHECK(tailFor(TailMode::Auto, 0.0).trimEnd == expected);
    // Sanity: -72 dB is well below unity but above zero.
    CHECK(expected > 0.0 && expected < 0.001);
}

static void testTailManualFixedNoTrim() {
    // Manual -> the time-selection tail bit, the requested ms (within cap), disable-all
    // normalize (no trim). A Manual capture is a fixed tail, so it keeps today's
    // disable-all exactly like the no-tail path.
    TailRenderSettings t = tailFor(TailMode::Manual, 2500.0);
    CHECK(t.tailFlag  == kTailFlagTimeSelection);
    CHECK(t.tailMs    == 2500.0);
    CHECK(t.normalize == kNormalizeDisableAll);
    CHECK(t.trimEnd   == 0.0);
}

static void testTailManualClampsToCap() {
    // The 8 s cap is a runaway guard that applies to Manual too: ms > 8000 -> 8000.
    CHECK(tailFor(TailMode::Manual, 9000.0).tailMs == kMaxTailMs);
    CHECK(tailFor(TailMode::Manual, 8000.0).tailMs == kMaxTailMs);
    // Below the cap is passed through unchanged.
    CHECK(tailFor(TailMode::Manual, 100.0).tailMs == 100.0);
    // A negative request floors to 0 (no negative tail leaks into RENDER_TAILMS).
    CHECK(tailFor(TailMode::Manual, -50.0).tailMs == 0.0);
}

// --- bounds mode: RENDER_BOUNDSFLAG + the tail bit paired with it ---------------

static void testTheBoundsModeIsTheTimeSelectionAndItsTailBitIsPairedWithIt() {
    // Literals from the SDK header, pinned as numbers so neither can drift onto
    // another bounds mode's value: RENDER_BOUNDSFLAG 2 = time selection (~3042), and
    // RENDER_TAILFLAG's bits are keyed per bounds mode, &4 = time selection (~3047).
    // The custom-bounds pair (0 / &1) is DELIBERATELY absent — that mode floors the
    // window to the millisecond (render_settings.h) and must not come back.
    CHECK(kRenderBoundsTimeSelection == 2);
    CHECK(kTailFlagTimeSelection     == 4);
    CHECK(kTailFlagNone              == 0);
}

static void testEveryTailModeSetsTheBitTheBoundsModeReads() {
    // A tail set under a different bounds mode's bit renders no tail at all, so both
    // tail-bearing modes must carry &4 — a fix applied to Auto alone would leave
    // Manual silently tailless.
    for (TailMode mode : {TailMode::Auto, TailMode::Manual})
        CHECK(tailRenderSettingsFor(mode, 2500.0).tailFlag == kTailFlagTimeSelection);

    // None is exact bounds: no tail bit at all, whatever ms it is handed.
    CHECK(tailRenderSettingsFor(TailMode::None, 5000.0).tailFlag == kTailFlagNone);
    CHECK(tailRenderSettingsFor(TailMode::None, 0.0).tailFlag    == kTailFlagNone);
}

// --- realtimeRecordWindowEnd: the T2 record-window extension -----------------

static void testRealtimeWindowNoneIsExact() {
    // None -> the exact range end, no extra recording (byte-identical to today).
    CHECK(realtimeRecordWindowEnd(TailMode::None, 12.5, 2000.0) == 12.5);
    // manualTailMs is ignored for None.
    CHECK(realtimeRecordWindowEnd(TailMode::None, 12.5, 0.0) == 12.5);
}

static void testRealtimeWindowAutoAddsCap() {
    // Auto -> range end + the 8 s runaway cap (trimmed later by the decay scan).
    CHECK(realtimeRecordWindowEnd(TailMode::Auto, 10.0, 0.0) == 10.0 + kMaxTailSeconds);
    // manualTailMs is ignored for Auto (the cap is fixed).
    CHECK(realtimeRecordWindowEnd(TailMode::Auto, 10.0, 3000.0) == 10.0 + kMaxTailSeconds);
}

static void testRealtimeWindowManualAddsClampedLength() {
    // Manual -> range end + the set length in seconds (fixed, no trim).
    CHECK(realtimeRecordWindowEnd(TailMode::Manual, 5.0, 2000.0) == 5.0 + 2.0);
    // Clamped to the 8 s cap: > 8000 ms -> +8 s.
    CHECK(realtimeRecordWindowEnd(TailMode::Manual, 5.0, 9000.0) == 5.0 + kMaxTailSeconds);
    // Negative floors to 0 -> no extra window (never records before the range end).
    CHECK(realtimeRecordWindowEnd(TailMode::Manual, 5.0, -100.0) == 5.0);
}

// --- parseRazorEdits: P_RAZOREDITS string -> ranges --------------------------

static void testParseSingleTrackAudioArea() {
    // One track-audio triple: start end "" (empty quoted GUID = track audio).
    auto r = parseRazorEdits("1.5 3.25 \"\"");
    CHECK(r.size() == 1);
    CHECK(r[0].startSeconds == 1.5);
    CHECK(r[0].endSeconds == 3.25);
}

static void testParseMultipleAreas() {
    auto r = parseRazorEdits("0.0 1.0 \"\" 2.0 4.0 \"\"");
    CHECK(r.size() == 2);
    CHECK(r[0].startSeconds == 0.0 && r[0].endSeconds == 1.0);
    CHECK(r[1].startSeconds == 2.0 && r[1].endSeconds == 4.0);
}

static void testParseSkipsEnvelopeLaneAreas() {
    // A triple whose GUID is a real {…} is an ENVELOPE-lane area — skipped, since
    // razor captures render track audio only. Only the track-audio triple survives.
    auto r = parseRazorEdits(
        "1.0 2.0 \"\" 3.0 4.0 {AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE}");
    CHECK(r.size() == 1);
    CHECK(r[0].startSeconds == 1.0 && r[0].endSeconds == 2.0);
}

static void testParseEmptyAndMalformed() {
    CHECK(parseRazorEdits("").empty());
    // Empty/inverted range dropped (end <= start).
    CHECK(parseRazorEdits("5.0 5.0 \"\"").empty());
    CHECK(parseRazorEdits("5.0 1.0 \"\"").empty());
    // Trailing garbage token in a time field -> that triple dropped, not a crash.
    CHECK(parseRazorEdits("1.0x 2.0 \"\"").empty());
    // A dangling partial triple (missing GUID token) is ignored.
    CHECK(parseRazorEdits("1.0 2.0").empty());
}

static void testRazorUnionBounds() {
    // Union = min start .. max end across all areas (the exact render window).
    std::vector<RazorRange> ranges = {{2.0, 3.0}, {0.5, 1.0}, {4.0, 6.5}};
    RazorRange u = razorUnionBounds(ranges);
    CHECK(u.startSeconds == 0.5);
    CHECK(u.endSeconds == 6.5);
    // Empty -> {0,0} sentinel (caller treats as "no razor area").
    RazorRange empty = razorUnionBounds({});
    CHECK(empty.startSeconds == 0.0 && empty.endSeconds == 0.0);
}

// --- sourceModeForScope: scope -> render source mode -------------------------

static void testScopeSourceModes() {
    // Track scope always renders its selected tracks (there is no master scope — to
    // capture the master you render a track), whatever the window.
    CHECK(sourceModeForScope(CaptureScope::Track, true)  == SourceMode::SelectedTracks);
    CHECK(sourceModeForScope(CaptureScope::Track, false) == SourceMode::SelectedTracks);
    // Item scope renders the selected items only when their extent already prints
    // the requested window.
    CHECK(sourceModeForScope(CaptureScope::Item, true)   == SourceMode::SelectedItems);
    // Every scope's source mode is an offline-supported render source.
    CHECK(renderSettingsFor(sourceModeForScope(CaptureScope::Item, true), 1.0).supported);
    CHECK(renderSettingsFor(sourceModeForScope(CaptureScope::Track, true), 1.0).supported);
}

static void testRangedItemScopeRendersTimeBounded() {
    // The widening defect pinned at the bit level. A window the item extent does NOT
    // print must never reach the &32 selected-items source: that source is INFERRED
    // (unverified — src/core/capture/CLAUDE.md §Gotchas) to take its bounds from the
    // item extents, so RENDER_STARTPOS/ENDPOS cannot narrow it and the capture widens
    // to the whole item. The ranged item capture renders through the selected-tracks
    // source instead, which IS time-bounded.
    const SourceMode ranged = sourceModeForScope(CaptureScope::Item, false);
    CHECK(ranged == SourceMode::SelectedTracks);

    const RenderSettingsChoice c = renderSettingsFor(ranged, 1.0);
    CHECK(c.supported);
    CHECK((c.settings & kRenderSelItems)  == 0);   // the widening bit is absent
    CHECK((c.settings & kRenderSingleFile) == 0);  // and its single-file companion
    CHECK(c.settings == kRenderSelTracksViaMaster);

    // The regression floor, at the same resolution: an item capture whose window IS
    // the item extent still renders through &32 | single-file, unchanged.
    const RenderSettingsChoice floorCase =
        renderSettingsFor(sourceModeForScope(CaptureScope::Item, true), 1.0);
    CHECK((floorCase.settings & kRenderSelItems)   != 0);
    CHECK((floorCase.settings & kRenderSingleFile) != 0);

    // FX scope is orthogonal to the re-source: a ranged item capture still hears
    // take/item FX only (this is what makes the swap safe).
    CHECK(fxBypassPlanFor(CaptureScope::Item).bypassSelfFx);
}

static void testMultiTrackStemRenderIsNamedForRefusal() {
    // The one shape that cannot land: a selected-tracks render over more than one
    // track. That source is INFERRED to render one file per track with no single-file
    // bit available (unverified; see src/shell/capture/CLAUDE.md §Gotchas for what that
    // inference rests on), so N stems would collapse onto one render pattern and one
    // track's audio would land as a successful capture.
    CHECK(isMultiTrackStemRender(SourceMode::SelectedTracks, 2));
    CHECK(isMultiTrackStemRender(SourceMode::SelectedTracks, 7));

    // One track is the common case for BOTH scopes — it must still render. This is the
    // regression floor for the plain single-track track capture.
    CHECK(!isMultiTrackStemRender(SourceMode::SelectedTracks, 1));
    CHECK(!isMultiTrackStemRender(SourceMode::SelectedTracks, 0));

    // A full-extent item capture keeps the selected-items source, whose single-file
    // bit already sums a multi-track item selection into one file.
    CHECK(!isMultiTrackStemRender(SourceMode::SelectedItems, 3));
    // Razor's single-file bit does the same; master mix is one file by definition.
    CHECK(!isMultiTrackStemRender(SourceMode::RazorArea, 3));
    CHECK(!isMultiTrackStemRender(SourceMode::MasterMix, 3));
    // Realtime never reaches the offline render at all (sends sum into one temp track).
    CHECK(!isMultiTrackStemRender(SourceMode::Realtime, 3));

    // The predicate is reachable from the mappings it guards: BOTH the ranged item
    // capture's source mode and the track scope's resolve to the one it names, while a
    // full-extent item capture does not.
    CHECK(isMultiTrackStemRender(sourceModeForScope(CaptureScope::Item, false), 2));
    CHECK(isMultiTrackStemRender(sourceModeForScope(CaptureScope::Track, false), 2));
    CHECK(isMultiTrackStemRender(sourceModeForScope(CaptureScope::Track, true), 2));
    CHECK(!isMultiTrackStemRender(sourceModeForScope(CaptureScope::Item, true), 2));
}

static void testRefusalMessagesAreSiblingsWithDistinctExits() {
    const std::string item  = multiTrackRefusalMessage(CaptureScope::Item);
    const std::string track = multiTrackRefusalMessage(CaptureScope::Track);

    // Both name the same reason — a shape that cannot land as one file — so a user who
    // hits the mistake in either scope reads one story, not two.
    CHECK(item.find("single file")  != std::string::npos);
    CHECK(track.find("single file") != std::string::npos);

    // And both name a way out. The shared one is "one track at a time"; each scope then
    // adds the exit only it has (widen the range / capture the folder).
    CHECK(item.find("one track's items at a time") != std::string::npos);
    CHECK(item.find("range match the items' extent") != std::string::npos);
    CHECK(track.find("one track at a time") != std::string::npos);
    CHECK(track.find("folder") != std::string::npos);

    // Distinct texts: the track message must not be the item message's wording about
    // items and ranges, which would misdescribe what the user actually did.
    CHECK(item != track);
    CHECK(track.find("selected items") == std::string::npos);
}

// Golden literals: docs/verify-track-scope-multitrack.md §2 quotes the track message as
// an exact console match. A substring check alone leaves that doc free to drift from
// whatever ships, so pin both strings byte-for-byte here.
static void testRefusalMessagesMatchGoldenLiterals() {
    CHECK(multiTrackRefusalMessage(CaptureScope::Item) ==
          "This range is narrower than the selected items, so it renders "
          "through their tracks -- and those items span more than one track, "
          "which this shape cannot land as a single file. Capture one track's "
          "items at a time, or make the range match the items' extent.");
    CHECK(multiTrackRefusalMessage(CaptureScope::Track) ==
          "A track capture renders the selected tracks through the master, "
          "and more than one track cannot land as a single file. Capture one "
          "track at a time, or route them into a folder/bus track and capture "
          "that (a folder's own output is its children summed).");
}

// --- inferRangeSource: razor-else-time (orthogonal to scope) -----------------

static void testRangeInference() {
    // Razor present -> razor union wins; no razor -> time selection.
    CHECK(inferRangeSource(true)  == RangeSource::Razor);
    CHECK(inferRangeSource(false) == RangeSource::TimeSelection);
}

// --- fxBypassPlanFor: the FX-scope invariant ----------------------------------

static void testItemScopeBypassesEverythingButTake() {
    // Item = take/item FX ONLY. Bypass the item's own track FX, its ancestors, and
    // the master. (If this returned bypassSelfFx=false the M7 defect — items heard
    // through the track's FX — would recur; the assertion pins the fix.)
    FxBypassPlan p = fxBypassPlanFor(CaptureScope::Item);
    CHECK(p.bypassSelfFx);
    CHECK(p.bypassAncestorFx);
    CHECK(p.bypassMaster);
}

static void testTrackScopeKeepsSelfBypassesAncestorsAndMaster() {
    // Track = item FX + the track's OWN FX. Keep self FX; bypass ancestors + master.
    // Master stays a bypass target even though it is no longer a capture scope.
    FxBypassPlan p = fxBypassPlanFor(CaptureScope::Track);
    CHECK(!p.bypassSelfFx);        // the whole point: the track's own FX stays live
    CHECK(p.bypassAncestorFx);     // no parent/folder FX
    CHECK(p.bypassMaster);         // no master FX
}

// --- captureActionTable: the scope taxonomy ----------------------------------

static void testTableHasBothScopes() {
    const auto& table = captureActionTable();
    // Two rows: item + track. No master scope, and NO tail variants — tail is a
    // panel setting the capture reads at fire time, not a per-action row.
    CHECK(table.size() == 2);

    std::set<std::string> ids;
    int item = 0, track = 0;
    for (const auto& def : table) {
        // Every command SUFFIX (Phase V, V4 — the channel prefix is prepended by the shell)
        // is a non-empty, UNIQUE string (duplicate suffixes would collide once composed).
        std::string suffix = def.commandSuffix;
        CHECK(!suffix.empty());
        // The suffix is NOT prefixed with the channel family here — that is composed at
        // register time. A leftover "CEREBELLUM_REASAMPLER_" in the table would be a
        // double-prefix bug, so assert its ABSENCE.
        CHECK(suffix.rfind("CEREBELLUM_REASAMPLER_", 0) != 0);
        CHECK(ids.insert(suffix).second);  // false if duplicate
        // Every scope resolves to a supported offline source, on BOTH the
        // extent-prints-the-window path and the time-bounded one.
        CHECK(renderSettingsFor(sourceModeForScope(def.scope, true), 1.0).supported);
        CHECK(renderSettingsFor(sourceModeForScope(def.scope, false), 1.0).supported);

        if (def.scope == CaptureScope::Item)  ++item;
        if (def.scope == CaptureScope::Track) ++track;
    }
    // Exactly one row per scope — no dupes, no gaps, no tail variants.
    CHECK(item == 1);
    CHECK(track == 1);
}

static void testScopeActionIdsAreTheShippedStrings() {
    // Pin the shipped CAPTURE_ITEM / CAPTURE_TRACK SUFFIXES so a future edit that silently
    // changes them (breaking user keybindings once composed with the channel prefix) fails
    // the gate. The full stable id is prefix + suffix ("CEREBELLUM_REASAMPLER_CAPTURE_ITEM").
    const auto& table = captureActionTable();
    std::string itemId, trackId;
    for (const auto& def : table) {
        if (def.scope == CaptureScope::Item)  itemId  = def.commandSuffix;
        if (def.scope == CaptureScope::Track) trackId = def.commandSuffix;
    }
    CHECK(itemId  == "CAPTURE_ITEM");
    CHECK(trackId == "CAPTURE_TRACK");
}

int main() {
    testMasterMixWet();
    testSelectedTracksWet();
    testSelectedItemsSingleFile();
    testRazorSingleFile();
    testRealtimeIsUnsupportedOffline();
    testEveryRenderSourceLabelIsPinnedVerbatim();
    testLabelsSeparateExactlyWhatTheRenderSeparates();
    testTailNoneIsExactBounds();
    testTailAutoIsSurgicalTrim();
    testAutoTrimRatioDerivesFromDb();
    testTailManualFixedNoTrim();
    testTailManualClampsToCap();
    testTheBoundsModeIsTheTimeSelectionAndItsTailBitIsPairedWithIt();
    testEveryTailModeSetsTheBitTheBoundsModeReads();
    testRealtimeWindowNoneIsExact();
    testRealtimeWindowAutoAddsCap();
    testRealtimeWindowManualAddsClampedLength();
    testParseSingleTrackAudioArea();
    testParseMultipleAreas();
    testParseSkipsEnvelopeLaneAreas();
    testParseEmptyAndMalformed();
    testRazorUnionBounds();
    testScopeSourceModes();
    testRangedItemScopeRendersTimeBounded();
    testMultiTrackStemRenderIsNamedForRefusal();
    testRefusalMessagesAreSiblingsWithDistinctExits();
    testRefusalMessagesMatchGoldenLiterals();
    testRangeInference();
    testItemScopeBypassesEverythingButTake();
    testTrackScopeKeepsSelfBypassesAncestorsAndMaster();
    testTableHasBothScopes();
    testScopeActionIdsAreTheShippedStrings();

    if (g_fail == 0) std::printf("render_settings: all tests passed\n");
    else             std::printf("render_settings: %d CHECK(s) FAILED\n", g_fail);
    return g_fail == 0 ? 0 : 1;
}
