Type-enforce the waveform overlay contract, narrow waveformLanes to LaneSplit, fix laneCount/cache/path-fallback bugs

This commit is contained in:
2026-07-30 09:35:11 -04:00
parent 2a0d10fab5
commit fb12c53522
19 changed files with 233 additions and 174 deletions
+17 -13
View File
@@ -26,6 +26,10 @@ static int g_fail = 0;
#define CHECK(cond) do { if(!(cond)) { \
std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++g_fail; } } while(0)
// buildEnvelopePolyline takes the overlay type, not a bare Rect (the distinct-type
// enforcement in editor_geometry.h) — this wraps a plain test Rect for it.
static OverlayArea overlayOf(const Rect& r) { return OverlayArea{r}; }
// A comfortable overlay area: 1000px wide, 100px tall, offset so left/top != 0 (catches origin
// bugs). Under levelToY the level span is height-1 = 99 rows.
static Rect wideArea() { return Rect::ltrb(20, 10, 1020, 110); } // width 1000, height 100
@@ -112,7 +116,7 @@ static void testGateNodeOrderAndLevels() {
env.sustainLevel = 0.5;
env.releaseSeconds = 0.4;
const Rect a = wideArea();
const std::vector<EnvVertex> poly = buildEnvelopePolyline(env, a, 2.0);
const std::vector<EnvVertex> poly = buildEnvelopePolyline(env, overlayOf(a), 2.0);
// Six vertices, in draw order.
CHECK(poly.size() == 6);
@@ -147,7 +151,7 @@ static void testGateSchematicPlacement() {
env.sustainLevel = 0.5;
env.releaseSeconds = 0.4;
const Rect a = wideArea();
const std::vector<EnvVertex> poly = buildEnvelopePolyline(env, a, 2.0);
const std::vector<EnvVertex> poly = buildEnvelopePolyline(env, overlayOf(a), 2.0);
EnvVertex v;
CHECK(findNode(poly, EnvNode::AttackEnd, v) && v.x == a.x + 28);
@@ -169,7 +173,7 @@ static void testGateLayoutIndependentOfSampleDuration() {
env.sustainLevel = 0.5;
env.releaseSeconds = 0.06;
const Rect a = wideArea();
CHECK(buildEnvelopePolyline(env, a, 0.3) == buildEnvelopePolyline(env, a, 10.0));
CHECK(buildEnvelopePolyline(env, overlayOf(a), 0.3) == buildEnvelopePolyline(env, overlayOf(a), 10.0));
}
static void testGateMinSeparationAtDefaults() {
@@ -178,7 +182,7 @@ static void testGateMinSeparationAtDefaults() {
// renders on top of its neighbour, so each is individually grabbable.
const AmpEnvelope env; // struct defaults ARE the tier-0 Gate defaults
const Rect a = wideArea();
const std::vector<EnvVertex> poly = buildEnvelopePolyline(env, a, 2.0);
const std::vector<EnvVertex> poly = buildEnvelopePolyline(env, overlayOf(a), 2.0);
CHECK(poly.size() == 6);
for (size_t i = 1; i < poly.size(); ++i) {
CHECK(poly[i].x - poly[i - 1].x >= kGateNodeSepPx);
@@ -197,7 +201,7 @@ static void testGateSustainPlateauFixedWidth() {
env.releaseSeconds = 0.3;
const Rect a = wideArea();
const int plateauPx = a.width - gateTimedWidth(a); // 150
const std::vector<EnvVertex> poly = buildEnvelopePolyline(env, a, 2.0);
const std::vector<EnvVertex> poly = buildEnvelopePolyline(env, overlayOf(a), 2.0);
EnvVertex decay, plateauEnd;
CHECK(findNode(poly, EnvNode::DecayEnd, decay));
@@ -218,7 +222,7 @@ static void testGateReleaseVisibleInBounds() {
env.sustainLevel = 0.5;
env.releaseSeconds = 0.4;
const Rect a = wideArea();
const std::vector<EnvVertex> poly = buildEnvelopePolyline(env, a, 2.0);
const std::vector<EnvVertex> poly = buildEnvelopePolyline(env, overlayOf(a), 2.0);
EnvVertex plateauEnd, rel;
CHECK(findNode(poly, EnvNode::ReleaseStart, plateauEnd));
@@ -241,7 +245,7 @@ static void testGateOverrunCompressesFromRight() {
env.sustainLevel = 0.7;
env.releaseSeconds = 4.0;
const Rect a = wideArea();
const std::vector<EnvVertex> poly = buildEnvelopePolyline(env, a, 2.0);
const std::vector<EnvVertex> poly = buildEnvelopePolyline(env, overlayOf(a), 2.0);
CHECK(poly.size() == 6);
EnvVertex plateauEnd, rel;
@@ -279,7 +283,7 @@ static void testGateAllVerticesInBounds() {
huge.releaseSeconds = 1e12;
for (const AmpEnvelope& env : {base, big, zero, trig, huge}) {
for (const EnvVertex& v : buildEnvelopePolyline(env, a, 2.0)) {
for (const EnvVertex& v : buildEnvelopePolyline(env, overlayOf(a), 2.0)) {
CHECK(v.x >= a.x && v.x < a.right());
CHECK(v.y >= a.y && v.y < a.bottom());
}
@@ -297,7 +301,7 @@ static void testTriggerShape() {
env.fadeInFraction = 0.2;
env.fadeOutFraction = 0.3;
const Rect a = wideArea();
const std::vector<EnvVertex> poly = buildEnvelopePolyline(env, a, 2.0);
const std::vector<EnvVertex> poly = buildEnvelopePolyline(env, overlayOf(a), 2.0);
CHECK(poly.size() == 4);
CHECK(poly[0].node == EnvNode::Origin);
@@ -319,7 +323,7 @@ static void testTriggerFadeOverlapClamp() {
env.fadeInFraction = 0.8; // fade-in end at 0.8*2.0 = 1.6s -> x@800
env.fadeOutFraction = 0.6; // would be 1.4s -> clamped to 1-0.8=0.2 -> begins at 0.8*2.0 too
const Rect a = wideArea();
const std::vector<EnvVertex> poly = buildEnvelopePolyline(env, a, 2.0);
const std::vector<EnvVertex> poly = buildEnvelopePolyline(env, overlayOf(a), 2.0);
EnvVertex fin, fout;
CHECK(findNode(poly, EnvNode::FadeInEnd, fin));
@@ -338,7 +342,7 @@ static void testTriggerFullLengthZeroFadeOutInBounds() {
env.fadeInFraction = 0.1;
env.fadeOutFraction = 0.0;
const Rect a = wideArea();
const std::vector<EnvVertex> poly = buildEnvelopePolyline(env, a, 2.0);
const std::vector<EnvVertex> poly = buildEnvelopePolyline(env, overlayOf(a), 2.0);
EnvVertex fout, lend;
CHECK(findNode(poly, EnvNode::FadeOutStart, fout));
@@ -353,12 +357,12 @@ static void testTriggerFullLengthZeroFadeOutInBounds() {
static void testDegenerateFlatBaseline() {
AmpEnvelope env; // any params
const Rect zeroW = Rect::ltrb(0, 0, 0, 100);
const std::vector<EnvVertex> p1 = buildEnvelopePolyline(env, zeroW, 2.0);
const std::vector<EnvVertex> p1 = buildEnvelopePolyline(env, overlayOf(zeroW), 2.0);
CHECK(p1.size() == 2); // always a drawable line
CHECK(p1.front().level == 0.0 && p1.back().level == 0.0);
const Rect ok = wideArea();
const std::vector<EnvVertex> p2 = buildEnvelopePolyline(env, ok, 0.0); // no duration
const std::vector<EnvVertex> p2 = buildEnvelopePolyline(env, overlayOf(ok), 0.0); // no duration
CHECK(p2.size() == 2);
CHECK(p2.front().level == 0.0 && p2.back().level == 0.0);
CHECK(p2.front().x == ok.x && p2.back().x == ok.right() - 1); // spans the area, in-bounds