Prove the render bounds at the boundary they cross, and name a short render whose count is exactly a millisecond-floored window

No truncation exists on our side of that boundary, so the read-back is the
only evidence available for whether REAPER kept the window — and it fires on
every tail mode, where only None was ever judged.
This commit is contained in:
2026-08-02 14:28:03 -04:00
parent e589addc54
commit 292d14d14c
6 changed files with 246 additions and 6 deletions
+1 -1
View File
@@ -52,7 +52,7 @@ Detail specific to these pure modules:
- `capture_name` — the REAPER-free composition of one capture's label + file-stem base from its source-track name(s), a local-calendar discriminator (`MM-DD HHMM`, from the shell's clock read), and an optional batch ordinal. The label and the stem deliberately diverge: the stem still passes through `capture_paths::sanitizeStem` (so a name that sanitizes to nothing files as `capture`), while the label keeps the source name verbatim. Stem uniqueness stays entirely `makeUniqueTag`'s — this module never disambiguates. - `capture_name` — the REAPER-free composition of one capture's label + file-stem base from its source-track name(s), a local-calendar discriminator (`MM-DD HHMM`, from the shell's clock read), and an optional batch ordinal. The label and the stem deliberately diverge: the stem still passes through `capture_paths::sanitizeStem` (so a name that sanitizes to nothing files as `capture`), while the label keeps the source name verbatim. Stem uniqueness stays entirely `makeUniqueTag`'s — this module never disambiguates.
- `insert_plan` — the REAPER-free logic behind the `insert` shell (M6): computes the `InsertMedia` `mode` bitmask from an `InsertOptions` struct (placement target, tempo-conform ratio, preserve-pitch flag), guaranteeing the &4 stretch-to-time-selection bit is never set and that no tempo bits are set when `conform == None`. - `insert_plan` — the REAPER-free logic behind the `insert` shell (M6): computes the `InsertMedia` `mode` bitmask from an `InsertOptions` struct (placement target, tempo-conform ratio, preserve-pitch flag), guaranteeing the &4 stretch-to-time-selection bit is never set and that no tempo bits are set when `conform == None`.
- `render_settings` — the REAPER-free logic behind the capture action family: `SourceMode``RENDER_SETTINGS` bit mapping, `P_RAZOREDITS` string parsing + range-union bounds, razor-else-time range inference, the FX-scope bypass plan (`fxBypassPlanFor`), the tail-mode → `RENDER_TAILFLAG`/`RENDER_NORMALIZE`/`RENDER_TRIMEND` mapping (`tailRenderSettingsFor`) and its realtime-window analog (`realtimeRecordWindowEnd`), the capture-action taxonomy table (`captureActionTable`) `main.cpp` iterates to register the CAPTURE_ITEM/CAPTURE_TRACK family, and `renderSourceLabel` (the source named in the offline backend's bounds refusal). - `render_settings` — the REAPER-free logic behind the capture action family: `SourceMode``RENDER_SETTINGS` bit mapping, `P_RAZOREDITS` string parsing + range-union bounds, razor-else-time range inference, the FX-scope bypass plan (`fxBypassPlanFor`), the tail-mode → `RENDER_TAILFLAG`/`RENDER_NORMALIZE`/`RENDER_TRIMEND` mapping (`tailRenderSettingsFor`) and its realtime-window analog (`realtimeRecordWindowEnd`), the capture-action taxonomy table (`captureActionTable`) `main.cpp` iterates to register the CAPTURE_ITEM/CAPTURE_TRACK family, and `renderSourceLabel` (the source named in the offline backend's bounds refusal).
- `render_window` — the REAPER-free frame arithmetic behind exact capture bounds: `frameCountFor` (the frame count a project-time window occupies at the project rate — the number the offline backend checks the rendered file against before landing it, so a render that printed something other than the window is refused rather than banked), `renderHonoredBounds` (the gate's verdict and the sole home of its one-frame tolerance, which is empirical rather than proven — the header states which renderer models it covers and which it does not), and `itemExtentPrintsWindow`, the predicate `render_settings::sourceModeForScope` consults to decide whether REAPER's selected-items render source can express a requested window at all. - `render_window` — the REAPER-free frame arithmetic behind exact capture bounds: `frameCountFor` (the frame count a project-time window occupies at the project rate — the number the offline backend checks the rendered file against before landing it, so a render that printed something other than the window is refused rather than banked), `renderHonoredBounds` (the gate's verdict and the sole home of its one-frame tolerance, which is empirical rather than proven — the header states which renderer models it covers and which it does not), and `itemExtentPrintsWindow`, the predicate `render_settings::sourceModeForScope` consults to decide whether REAPER's selected-items render source can express a requested window at all. It also owns the two short-render diagnostics: `msFlooredEndFrameCount` (the frames a window holds with its end floored to the millisecond — the shape two live short renders matched, quoted by the refusal as a count coincidence and nothing more) and `describeBoundsDrift` (the sentence the offline backend prints when `RENDER_STARTPOS`/`RENDER_ENDPOS` do not read back as they were written).
- `track_topology` — the REAPER-free folder arithmetic over a project's flat `I_FOLDERDEPTH` delta list: `directChildIndices` names a folder parent's DIRECT children, the set `shell/capture/render_isolation` silences so a ranged item capture does not print its track's children. Grandchildren are excluded by construction — they reach the parent only through the child that owns them. - `track_topology` — the REAPER-free folder arithmetic over a project's flat `I_FOLDERDEPTH` delta list: `directChildIndices` names a folder parent's DIRECT children, the set `shell/capture/render_isolation` silences so a ranged item capture does not print its track's children. Grandchildren are excluded by construction — they reach the parent only through the child that owns them.
- `tail_control` — the REAPER-free logic behind the docked `bank_panel`'s tail-mode toggle: the cycle order (None → Auto → Manual → None), the Manual-length clamp/scroll-wheel fine-adjust (`clampManualMs`/`adjustManualMs`, 250 ms/notch, 2000 ms default), the toggle's label text (e.g. "Tail: Manual 2.0s"), and the `TailSetting` JSON round-trip persist stores per-project. - `tail_control` — the REAPER-free logic behind the docked `bank_panel`'s tail-mode toggle: the cycle order (None → Auto → Manual → None), the Manual-length clamp/scroll-wheel fine-adjust (`clampManualMs`/`adjustManualMs`, 250 ms/notch, 2000 ms default), the toggle's label text (e.g. "Tail: Manual 2.0s"), and the `TailSetting` JSON round-trip persist stores per-project.
+42
View File
@@ -3,6 +3,7 @@
#include "core/capture/render_window.h" #include "core/capture/render_window.h"
#include <cmath> #include <cmath>
#include <cstdio>
namespace reasampler::capture { namespace reasampler::capture {
@@ -15,6 +16,22 @@ long long frameIndexAt(double seconds, int sampleRate) {
return std::llround(seconds * static_cast<double>(sampleRate)); return std::llround(seconds * static_cast<double>(sampleRate));
} }
// See the header for why whole milliseconds get a tolerance and why it is this small.
double floorToMilliseconds(double seconds) {
const double ms = seconds * 1000.0;
const double nearest = std::nearbyint(ms);
if (std::fabs(ms - nearest) < 1e-6) return nearest / 1000.0;
return std::floor(ms) / 1000.0;
}
// Full round-trip precision: a drift report whose two numbers print identically
// would be evidence of nothing.
std::string exactly(double seconds) {
char buf[32];
std::snprintf(buf, sizeof(buf), "%.17g", seconds);
return buf;
}
} // namespace } // namespace
long long frameCountFor(double startSeconds, double endSeconds, int sampleRate) { long long frameCountFor(double startSeconds, double endSeconds, int sampleRate) {
@@ -41,4 +58,29 @@ bool itemExtentPrintsWindow(double reqStart, double reqEnd,
&& frameIndexAt(reqEnd, sampleRate) == frameIndexAt(itemEnd, sampleRate); && frameIndexAt(reqEnd, sampleRate) == frameIndexAt(itemEnd, sampleRate);
} }
long long msFlooredEndFrameCount(double startSeconds, double endSeconds,
int sampleRate) {
return frameCountFor(startSeconds, floorToMilliseconds(endSeconds), sampleRate);
}
std::string describeBoundsDrift(double reqStart, double reqEnd,
double storedStart, double storedEnd,
int sampleRate) {
// Bit equality, deliberately: the caller wrote these exact doubles and read them
// straight back, so anything but the same bits is a value REAPER changed.
if (storedStart == reqStart && storedEnd == reqEnd) return {};
std::string s = "REAPER did not keep the render bounds it was handed -- asked for [" +
exactly(reqStart) + "s, " + exactly(reqEnd) + "s), read back [" +
exactly(storedStart) + "s, " + exactly(storedEnd) + "s).";
if (sampleRate > 0) {
s += " The stored window is " +
std::to_string(frameCountFor(storedStart, storedEnd, sampleRate)) +
" frames against the " +
std::to_string(frameCountFor(reqStart, reqEnd, sampleRate)) +
" the request asks for, at " + std::to_string(sampleRate) + " Hz.";
}
return s;
}
} // namespace reasampler::capture } // namespace reasampler::capture
+29 -2
View File
@@ -1,9 +1,12 @@
#pragma once #pragma once
// render_window — pure frame arithmetic for a capture's requested window: the // render_window — pure frame arithmetic for a capture's requested window: the
// frame count a project-time range occupies, and whether a render whose bounds // frame count a project-time range occupies, whether a render whose bounds come
// come from the selected items' own extent already prints that window. // from the selected items' own extent already prints that window, and the two
// diagnostics that say where a short render lost its frames.
// NO REAPER types; unit-tested by tests/test_render_window.cpp. // NO REAPER types; unit-tested by tests/test_render_window.cpp.
#include <string>
namespace reasampler::capture { namespace reasampler::capture {
// Frames the [startSeconds, endSeconds) window occupies at `sampleRate`. Both // Frames the [startSeconds, endSeconds) window occupies at `sampleRate`. Both
@@ -42,4 +45,28 @@ bool itemExtentPrintsWindow(double reqStart, double reqEnd,
double itemStart, double itemEnd, double itemStart, double itemEnd,
int sampleRate); int sampleRate);
// --- Diagnostics: where a short render lost its frames ------------------------
// The frames this window would hold if its END were resolved on a whole-millisecond
// grid, floored, instead of exactly. Two live short renders (48 kHz, TailMode::None)
// matched this count to the frame, which is the entire reason it exists.
//
// A COINCIDENCE OF COUNTS, not a claim about how anything resolved the end: nothing
// renders from this number and no capture path asks for it. Whole-millisecond values
// are recognized within a nanosecond, because a decimal millisecond is not always one
// in binary (0.029 * 1000 lands just below 29) and a bare floor would drop a
// millisecond from a window already on the grid. A nanosecond is far under one frame
// at any rate we render, so a real sub-millisecond remainder still floors.
long long msFlooredEndFrameCount(double startSeconds, double endSeconds,
int sampleRate);
// The sentence a capture prints when the render bounds it handed REAPER did not read
// back unchanged — the requested window, what came back, and both frame counts at
// `sampleRate` (omitted when the rate is unknown). EMPTY when both edges read back
// bit-identical, which is the only answer proving the request crossed into REAPER
// intact; a caller prints this only when it is non-empty.
std::string describeBoundsDrift(double reqStart, double reqEnd,
double storedStart, double storedEnd,
int sampleRate);
} // namespace reasampler::capture } // namespace reasampler::capture
+19
View File
@@ -34,6 +34,7 @@
#include "core/capture/wav_codec.h" // hashWavContent / collapseToMono — the one WAV/RIFF owner #include "core/capture/wav_codec.h" // hashWavContent / collapseToMono — the one WAV/RIFF owner
#include "core/util/file_bytes.h" #include "core/util/file_bytes.h"
#include "core/capture/render_settings.h" #include "core/capture/render_settings.h"
#include "core/capture/render_window.h" // describeBoundsDrift — the read-back's verdict
#include "shell/capture/render_bounds_gate.h" // the exact-bounds verdict on the landed render #include "shell/capture/render_bounds_gate.h" // the exact-bounds verdict on the landed render
#define REAPERAPI_MINIMAL #define REAPERAPI_MINIMAL
@@ -424,6 +425,12 @@ CaptureResult OfflineRenderBackend::capture(const CaptureRequest& request) {
GetSetProjectInfo(proj, "RENDER_STARTPOS", request.startSeconds, true); GetSetProjectInfo(proj, "RENDER_STARTPOS", request.startSeconds, true);
GetSetProjectInfo(proj, "RENDER_ENDPOS", request.endSeconds, true); GetSetProjectInfo(proj, "RENDER_ENDPOS", request.endSeconds, true);
// The requested window crosses out of this process HERE and nowhere else, so the
// read-back is the only evidence available on this side of that boundary for
// whether REAPER kept it. Reported below, once the project rate is known.
const double storedStart = GetSetProjectInfo(proj, "RENDER_STARTPOS", 0.0, false);
const double storedEnd = GetSetProjectInfo(proj, "RENDER_ENDPOS", 0.0, false);
// TAILFLAG/TAILMS/NORMALIZE/TRIMEND from the pure mapping: None -> exact // TAILFLAG/TAILMS/NORMALIZE/TRIMEND from the pure mapping: None -> exact
// bounds + disable-all normalize; Auto -> 8s tail + surgical trim-end // bounds + disable-all normalize; Auto -> 8s tail + surgical trim-end
// normalize + -72 dB TRIMEND; Manual -> clamped fixed tail + disable-all, no // normalize + -72 dB TRIMEND; Manual -> clamped fixed tail + disable-all, no
@@ -453,6 +460,18 @@ CaptureResult OfflineRenderBackend::capture(const CaptureRequest& request) {
GetSetProjectInfo(proj, "RENDER_SRATE", GetSetProjectInfo(proj, "RENDER_SRATE",
static_cast<double>(effectiveSampleRate), true); static_cast<double>(effectiveSampleRate), true);
} }
// Silent unless a bound came back changed. Fires on EVERY tail mode on purpose:
// only None is judged against its window after the render, so this is the sole
// signal an Auto/Manual capture was shortened before it ever started.
{
const std::string drift =
describeBoundsDrift(request.startSeconds, request.endSeconds,
storedStart, storedEnd, effectiveSampleRate);
if (!drift.empty())
ShowConsoleMsg(("ReaSampler capture: " + drift + "\n").c_str());
}
GetSetProjectInfo(proj, "RENDER_CHANNELS", GetSetProjectInfo(proj, "RENDER_CHANNELS",
static_cast<double>(request.channelCount), true); static_cast<double>(request.channelCount), true);
+12 -1
View File
@@ -99,6 +99,17 @@ BoundsVerdict checkRenderedBounds(const std::string& renderedPath,
const long long actualFrames = static_cast<long long>(layout.frameCount()); const long long actualFrames = static_cast<long long>(layout.frameCount());
if (renderHonoredBounds(expectedFrames, actualFrames)) return v; if (renderHonoredBounds(expectedFrames, actualFrames)) return v;
// Says whether this shortfall has the one shape two live short renders already
// matched to the frame, so every refusal from here on adds to (or breaks) that
// evidence instead of needing the arithmetic done by hand. A count coincidence
// only — it does not establish how the render resolved anything.
const std::string msNote =
actualFrames == msFlooredEndFrameCount(request.startSeconds,
request.endSeconds, rate)
? " Those are exactly the frames this window holds with its end floored to"
" the millisecond -- a match on the count, not a measured cause."
: std::string();
v.refused = true; v.refused = true;
v.message = "Render produced " + std::to_string(actualFrames) + v.message = "Render produced " + std::to_string(actualFrames) +
" frames but the requested range is " + std::to_string(expectedFrames) + " frames but the requested range is " + std::to_string(expectedFrames) +
@@ -108,7 +119,7 @@ BoundsVerdict checkRenderedBounds(const std::string& renderedPath,
std::to_string(request.endSeconds) + "s) -> frame indices [" + std::to_string(request.endSeconds) + "s) -> frame indices [" +
std::to_string(std::llround(request.startSeconds * rate)) + ", " + std::to_string(std::llround(request.startSeconds * rate)) + ", " +
std::to_string(std::llround(request.endSeconds * rate)) + ")." + std::to_string(std::llround(request.endSeconds * rate)) + ")." +
retainRefusedRender(renderedPath, projectDir); msNote + retainRefusedRender(renderedPath, projectDir);
return v; return v;
} }
+143 -2
View File
@@ -1,13 +1,14 @@
// Standalone tests for reasampler::render_window — no REAPER, no framework. // Standalone tests for reasampler::render_window — no REAPER, no framework.
// Covers the bounds-equality number (a window's exact frame count at the project // Covers the bounds-equality number (a window's exact frame count at the project
// rate), the verdict the offline backend refuses a capture on, and the predicate // rate), the verdict the offline backend refuses a capture on, the predicate
// that decides whether REAPER's selected-items render source can express a // that decides whether REAPER's selected-items render source can express a
// requested window at all. // requested window at all, and the two short-render diagnostics.
#include "../src/core/capture/render_window.h" #include "../src/core/capture/render_window.h"
#include <cmath> #include <cmath>
#include <cstdio> #include <cstdio>
#include <string>
using namespace reasampler::capture; using namespace reasampler::capture;
@@ -15,6 +16,10 @@ static int g_fail = 0;
#define CHECK(cond) do { if(!(cond)) { \ #define CHECK(cond) do { if(!(cond)) { \
std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++g_fail; } } while(0) std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++g_fail; } } while(0)
static bool contains(const std::string& haystack, const std::string& needle) {
return haystack.find(needle) != std::string::npos;
}
// --- frameCountFor: the bounds equality, stated as a number ------------------ // --- frameCountFor: the bounds equality, stated as a number ------------------
static void testFrameCountIsExactNotRounded() { static void testFrameCountIsExactNotRounded() {
@@ -226,6 +231,131 @@ static void testMultiItemUnionExtent() {
CHECK(!itemExtentPrintsWindow(1.0, 4.0, 1.0, 9.0, 48000)); CHECK(!itemExtentPrintsWindow(1.0, 4.0, 1.0, 9.0, 48000));
} }
// --- msFlooredEndFrameCount: the shape both live short renders had ------------
static void testMillisecondFlooredEndReproducesBothShortRenders() {
// Both DAW observations, as arithmetic. 48 kHz, TailMode::None, start at 0: the
// requested window's count, and the count its end floored to the millisecond
// holds — which is what each render actually printed.
CHECK(frameCountFor(0.0, 4.067797, 48000) == 195254);
CHECK(msFlooredEndFrameCount(0.0, 4.067797, 48000) == 195216);
CHECK(frameCountFor(0.0, 4.067797, 48000) -
msFlooredEndFrameCount(0.0, 4.067797, 48000) == 38);
CHECK(frameCountFor(0.0, 1.655172, 48000) == 79448);
CHECK(msFlooredEndFrameCount(0.0, 1.655172, 48000) == 79440);
CHECK(frameCountFor(0.0, 1.655172, 48000) -
msFlooredEndFrameCount(0.0, 1.655172, 48000) == 8);
}
static void testTheSixDecimalDisplayDidNotCreateTheEffect() {
// Both reported ends were printed to six decimals by the refusal. Each is one 4/4
// bar — at 59 BPM and at 145 BPM — so the full-precision doubles behind them are
// 240/59 and 240/145. Same counts either way: the display rounding is not what
// produces the shortfall.
CHECK(frameCountFor(0.0, 240.0 / 59.0, 48000) == 195254);
CHECK(msFlooredEndFrameCount(0.0, 240.0 / 59.0, 48000) == 195216);
CHECK(frameCountFor(0.0, 240.0 / 145.0, 48000) == 79448);
CHECK(msFlooredEndFrameCount(0.0, 240.0 / 145.0, 48000) == 79440);
}
static void testWindowAlreadyOnTheMillisecondGridLosesNothing() {
// The "sometimes it works" case: a bar at 120 BPM is exactly 2 s.
CHECK(msFlooredEndFrameCount(0.0, 2.0, 48000) == frameCountFor(0.0, 2.0, 48000));
// The binary-representation trap a bare floor would fall into. The premise, not an
// outcome: 1.007 s is a whole millisecond that really does land BELOW 1007 ms in
// double, so flooring it without a tolerance drops a millisecond from a window
// already on the grid.
CHECK(1.007 * 1000.0 < 1007.0);
CHECK(frameCountFor(0.0, 1.007, 48000) == 48336);
CHECK(msFlooredEndFrameCount(0.0, 1.007, 48000) == 48336);
// Same end reached from a non-zero start, so nothing here rests on the window
// beginning at 0.
CHECK(msFlooredEndFrameCount(0.5, 1.007, 48000) ==
frameCountFor(0.5, 1.007, 48000));
}
static void testOneFrameOfRemainderStillFloors() {
// The whole-millisecond tolerance must sit far below a frame, or it would swallow
// the very remainder this diagnostic exists to find. One frame at 48 kHz is 20.8 us
// — four orders of magnitude above the nanosecond tolerance.
const double oneFrame = 1.0 / 48000.0;
CHECK(frameCountFor(0.0, 1.0 + oneFrame, 48000) == 48001);
CHECK(msFlooredEndFrameCount(0.0, 1.0 + oneFrame, 48000) == 48000);
}
static void testMillisecondFloorAt44100WhereAMillisecondIsNotWholeFrames() {
// 44.1 kHz: a millisecond is 44.1 frames, so a floored end cannot be described as
// dropping a whole number of frames — the count still resolves exactly.
CHECK(frameCountFor(0.0, 0.0105, 44100) == 463);
CHECK(msFlooredEndFrameCount(0.0, 0.0105, 44100) == 441);
// And a window that IS on the millisecond grid there is untouched, even though its
// edge is not on a frame boundary.
CHECK(frameCountFor(0.0, 0.010, 44100) == 441);
CHECK(msFlooredEndFrameCount(0.0, 0.010, 44100) == 441);
}
static void testASubMillisecondStartWouldNotHideItself() {
// Both observations started at 0.000000s, the one value that hides a start-side
// truncation. A window whose START carries a sub-millisecond remainder counts from
// that exact start...
const double start = 1.0001724, end = 2.0001724;
CHECK(frameCountFor(start, end, 48000) == 48000);
// ...so a start floored to the millisecond would print a DIFFERENT count — 8 frames
// more, the same remainder the second observation lost off its end. A start-side
// truncation is therefore visible to the same frame-count gate, not silent.
CHECK(frameCountFor(1.000, end, 48000) == 48008);
CHECK(!renderHonoredBounds(frameCountFor(start, end, 48000),
frameCountFor(1.000, end, 48000)));
}
// --- describeBoundsDrift: the read-back's verdict ------------------------------
static void testBoundsThatReadBackUnchangedDescribeNothing() {
// The answer that proves the request crossed into REAPER intact — including for a
// window whose end is nowhere near a millisecond boundary.
CHECK(describeBoundsDrift(0.0, 4.067797, 0.0, 4.067797, 48000).empty());
CHECK(describeBoundsDrift(1.0001724, 2.0001724, 1.0001724, 2.0001724, 48000).empty());
}
static void testADriftedEndNamesBothWindowsAndBothCounts() {
const std::string s =
describeBoundsDrift(0.0, 4.067797, 0.0, 4.067, 48000);
CHECK(!s.empty());
// Both counts as literals from the DAW observation, not re-derived from the same
// functions the sentence was built with.
CHECK(contains(s, "195254")); // what the request asks for
CHECK(contains(s, "195216")); // what the drifted window would hold
CHECK(contains(s, "48000 Hz"));
}
static void testTheReportPrintsEnoughDigitsToShowTheDrift() {
// A report whose two numbers print identically is evidence of nothing. Two ends a
// single ULP apart — far under the sixth decimal a shorter rendering would stop at
// — must still read as two different numbers.
const double asked = 4.067797;
const double stored = std::nextafter(asked, 5.0);
const std::string s = describeBoundsDrift(0.0, asked, 0.0, stored, 48000);
CHECK(!s.empty());
CHECK(!contains(s, "4.067797s, read back [0s, 4.067797s)"));
}
static void testADriftedStartIsCaughtToo() {
// The edge both observations could not test.
const std::string s = describeBoundsDrift(1.0001724, 2.0, 1.000, 2.0, 48000);
CHECK(!s.empty());
CHECK(contains(s, "1.0001724"));
}
static void testAnUnknownRateStillReportsTheDriftWithoutFrames() {
// A project that never pinned a rate reads 0. The drift is still worth saying; a
// frame count over an unknown rate is not.
const std::string s = describeBoundsDrift(0.0, 4.067797, 0.0, 4.067, 0);
CHECK(!s.empty());
CHECK(!contains(s, "frames"));
}
int main() { int main() {
testFrameCountIsExactNotRounded(); testFrameCountIsExactNotRounded();
testFrameCountIsADifferenceOfIndicesNotADuration(); testFrameCountIsADifferenceOfIndicesNotADuration();
@@ -244,6 +374,17 @@ int main() {
testSubFrameDriftStillPrintsTheSameFrames(); testSubFrameDriftStillPrintsTheSameFrames();
testUnknownRateFallsBackToExactEquality(); testUnknownRateFallsBackToExactEquality();
testMultiItemUnionExtent(); testMultiItemUnionExtent();
testMillisecondFlooredEndReproducesBothShortRenders();
testTheSixDecimalDisplayDidNotCreateTheEffect();
testWindowAlreadyOnTheMillisecondGridLosesNothing();
testOneFrameOfRemainderStillFloors();
testMillisecondFloorAt44100WhereAMillisecondIsNotWholeFrames();
testASubMillisecondStartWouldNotHideItself();
testBoundsThatReadBackUnchangedDescribeNothing();
testADriftedEndNamesBothWindowsAndBothCounts();
testTheReportPrintsEnoughDigitsToShowTheDrift();
testADriftedStartIsCaughtToo();
testAnUnknownRateStillReportsTheDriftWithoutFrames();
if (g_fail) { std::printf("%d check(s) FAILED\n", g_fail); return 1; } if (g_fail) { std::printf("%d check(s) FAILED\n", g_fail); return 1; }
std::printf("render_window: all checks passed\n"); std::printf("render_window: all checks passed\n");