fix: restore waveform symmetry about the midline, cut deck_values' link to the bank model, and unit-test the column arithmetic

The waveform column's vertical extents move to pure component_geometry so the shared
primitive stops being untested; PlaySeconds hoists into a header-only play_seconds target.
This commit is contained in:
2026-08-01 09:47:07 -04:00
parent 7f74b11dce
commit ca464397b2
17 changed files with 310 additions and 138 deletions
+73 -2
View File
@@ -4,8 +4,10 @@
// Covers (brief §L1 point 2 + §test cases): button box inset + graceful suppression; slider
// track/filled/handle geometry for representative values incl. endpoints, value->px inverse,
// too-small/degenerate suppression; list-row rect for representative indices, partial last
// row, hover hit-test returns the right row and "no hit" outside/past the last row; and the
// shared half-open box hit-test agrees with layout (no double-claimed pixel).
// row, hover hit-test returns the right row and "no hit" outside/past the last row; the
// shared half-open box hit-test agrees with layout (no double-claimed pixel); and the waveform
// column's vertical extents — symmetry about the zero line, monotonicity, and the band clamp
// on both the fill and the antialiased stroke, which draw_kit's shell cannot cover.
#include "../src/core/ui/component_geometry.h"
@@ -190,6 +192,70 @@ static void testWaveformColumnCount() {
CHECK(waveformColumnCount(KitBox{0, 0, 0, 40}) == 0);
}
// --- waveform column span ----------------------------------------------------
// The regression this exists to catch: rounding applied to the resulting y instead of to the
// scaled amplitude draws a symmetric column one pixel taller above the zero line than below.
static void testSymmetricColumnDrawsEqualHeightAboveAndBelowTheZeroLine() {
const WaveformBand band = waveformBand(0, 41); // odd height -> fractional half-span
// Sweep amplitudes whose scaled value is fractional, which is where the two edges can
// round in opposite directions.
for (int i = 1; i <= 20; ++i) {
const double a = i / 20.0;
const WaveformColumnSpan s = waveformColumnSpan(band, a, -a);
CHECK(band.midY - s.top == s.bottom - band.midY);
// The stroke's edges are symmetric to sub-ULP, not bit-exactly: midY +/- x rounds the
// two sides independently. Any REAL asymmetry here would be a whole pixel.
const double above = band.midY - s.topF;
const double below = s.bottomF - band.midY;
CHECK(above - below < 1e-9 && below - above < 1e-9);
}
}
// A column with no signal collapses onto the zero line rather than spanning a pixel of it.
static void testSilentColumnCollapsesOntoTheZeroLine() {
const WaveformBand band = waveformBand(10, 40);
const WaveformColumnSpan s = waveformColumnSpan(band, 0.0, 0.0);
CHECK(s.top == band.midY && s.bottom == band.midY);
CHECK(s.topF == band.midY && s.bottomF == band.midY);
}
// Amplitude grows the span monotonically, and a bigger amplitude never draws shorter.
static void testTallerAmplitudeNeverDrawsAShorterColumn() {
const WaveformBand band = waveformBand(0, 40);
int prevHeight = -1;
for (int i = 0; i <= 20; ++i) {
const WaveformColumnSpan s = waveformColumnSpan(band, i / 20.0, -(i / 20.0));
const int h = s.bottom - s.top;
CHECK(h >= prevHeight);
prevHeight = h;
}
}
// The clamp is the band's boundary for BOTH the fill and the antialiased stroke — a stroke
// vertex outside the band would draw into the neighbouring channel's lane.
static void testBothEdgesAndTheStrokeClampToTheBand() {
const WaveformBand band = waveformBand(100, 40);
const int lo = 100;
const int hi = 139;
// Past full scale in both directions (the display curve's own range is [-1,1], so this is
// the defensive case, not a reachable one).
const WaveformColumnSpan s = waveformColumnSpan(band, 8.0, -8.0);
CHECK(s.top == lo && s.bottom == hi);
CHECK(s.topF == lo && s.bottomF == hi);
// Full scale sits INSIDE the band by the half-span's 2px inset — the clamp is a guard,
// not the thing that produces the normal drawn height.
const WaveformColumnSpan full = waveformColumnSpan(band, 1.0, -1.0);
CHECK(full.top > lo && full.bottom < hi);
}
static void testBandMetricsMirrorTheDrawnInset() {
const WaveformBand b = waveformBand(50, 40);
CHECK(b.top == 50 && b.height == 40);
CHECK(b.midY == 70);
CHECK(b.halfSpan == 18.0); // half the band, less the 2px breathing room
}
int main() {
testHitTestBoxHalfOpen();
testButtonBoxInset();
@@ -208,6 +274,11 @@ int main() {
testListRowHitTestBoundedByCount();
testListRowLayoutHitAgreement();
testWaveformColumnCount();
testSymmetricColumnDrawsEqualHeightAboveAndBelowTheZeroLine();
testSilentColumnCollapsesOntoTheZeroLine();
testTallerAmplitudeNeverDrawsAShorterColumn();
testBothEdgesAndTheStrokeClampToTheBand();
testBandMetricsMirrorTheDrawnInset();
if (g_fail == 0) std::printf("component_geometry: all tests passed\n");
else std::printf("component_geometry: %d CHECK(s) FAILED\n", g_fail);