Γ-W1-T1 review fixes: mode-independent taper rounding, sharper drag-step test, reset-sweep verifies stored fields

Swap nearbyint for std::round (MXCSR-independent); derive the finest-drag test
from the editor floor, not the knob; verify resets against fields, not norms;
record the spline-point modifier exclusion.
This commit is contained in:
2026-08-01 20:07:48 -04:00
parent 3eb72d01c4
commit ee8a956fbd
8 changed files with 96 additions and 32 deletions
+39 -3
View File
@@ -9,6 +9,10 @@
#include "../src/core/instrument/ui/param_taper.h"
#include "../src/core/instrument/ui/envelope_overlay.h" // gateStageSlotPx: finest drag surface
#include "../src/core/instrument/ui/sample_bands.h" // kEditorMinWidth/kPad: the editor floor
#include <cfenv>
#include <cmath>
#include <cstdio>
@@ -78,10 +82,15 @@ static void testStageTimeIsMonotone() {
}
}
// The FINEST drag a user can make — Ctrl's 1/20 rate over the 128 px knob travel — must still
// move the value, or the output quantum would be observable as a dead zone.
// The FINEST drag a user can make on ANY surface this taper serves — not the knob's own 128 px
// travel, which is coarser than the AHDSR schematic's node drag at the editor floor. Derived from
// the floor constant and the overlay's own slot-width formula, so a later floor change sharpens
// (or coarsens) the step this test exercises automatically instead of leaving a copied number
// silently stale.
static void testEveryFinestDragStepMovesTheValue() {
const int steps = static_cast<int>(1.0 / kFineDragScale) * 128;
const Rect floorArea = Rect::ltrb(0, 0, kEditorMinWidth - 2 * kPad, 100);
const double slot = gateStageSlotPx(floorArea);
const int steps = static_cast<int>(slot / kFineDragScale);
for (int i = 0; i < steps; ++i) {
const double lo = timeSecondsFromNorm(static_cast<double>(i) / steps);
const double hi = timeSecondsFromNorm(static_cast<double>(i + 1) / steps);
@@ -108,6 +117,32 @@ static void testEveryWholeMicrosecondRoundTripsExactly() {
}
}
// MODE-INDEPENDENCE, the whole point of resolveTo's std::round over std::nearbyint. First shows
// the defect directly, generically: under round-toward-zero, the RETIRED std::nearbyint reads
// that mode and truncates a value whose fraction is well past half, while std::round (specified
// to round half-away-from-zero REGARDLESS of the current mode) does not. Then proves the
// production round trip itself — not a stand-in — survives the same hostile mode across the grid.
static void testRoundingSurvivesAHostileFpRoundingMode() {
const int saved = std::fegetround();
CHECK(std::fesetround(FE_TOWARDZERO) == 0);
CHECK(std::nearbyint(12.9) == 12.0); // the RETIRED behaviour: mode-dependent, wrong here
CHECK(std::round(12.9) == 13.0); // the fix: mode-independent, rounds to nearest
for (int us = 0; us <= 200000; us += 7) {
const double seconds = static_cast<double>(us) / 1e6;
CHECK(timeSecondsFromNorm(timeNormFromSeconds(seconds)) == seconds);
if (timeSecondsFromNorm(timeNormFromSeconds(seconds)) != seconds) break;
}
for (int milli = -24000; milli <= 24000; milli += 37) {
const double d = static_cast<double>(milli) / 1000.0;
CHECK(depthSemitonesFromNorm(depthNormFromSemitones(d, kDepth), kDepth) == d);
if (depthSemitonesFromNorm(depthNormFromSemitones(d, kDepth), kDepth) != d) break;
}
std::fesetround(saved); // restore — every other test in this binary assumes the default
}
// The converse round trip is NOT required, but its residual is worth pinning: it is bounded by
// the output quantum read back through the map, which stays four orders below one drag pixel.
// Pinned so a future quantum change cannot make the needle visibly lag the hand unnoticed.
@@ -239,6 +274,7 @@ int main() {
testStageTimeIsMonotone();
testEveryFinestDragStepMovesTheValue();
testEveryWholeMicrosecondRoundTripsExactly();
testRoundingSurvivesAHostileFpRoundingMode();
testNormRoundTripResidualStaysBelowOneDragPixel();
testTheStageTimeDefaultsRoundTripExactly();