Widen the deck row block to 1028 so the filter tie-line is exact, and accumulate the meter's block peaks instead of sampling one in 47

This commit is contained in:
2026-08-02 08:27:05 -04:00
parent 0627398bbb
commit df10ddacc2
29 changed files with 552 additions and 250 deletions
+35 -25
View File
@@ -12,6 +12,7 @@
// and which selections are inert).
#include "../src/core/instrument/ui/deck_groups.h"
#include "../src/core/instrument/ui/master_meter.h" // kMeterColumnW: MASTER's reserve IS this
#include "../src/core/instrument/ui/sample_bands.h"
#include <cmath>
@@ -310,12 +311,15 @@ static void testTheEditorFloorIsDerivedFromTheDeckWidthBudget() {
CHECK(kDeckRowBlockW + kDeckGroupGap + kDeckSpanningW + 2 * kPad == kEditorMinWidth);
// The budget: what is left between the derived floor and the hard ceiling, and it is spent
// once. A cell costs 60 of it.
CHECK(kEditorCeilingWidth - kEditorMinWidth == 90);
CHECK(kEditorCeilingWidth - kEditorMinWidth == 82);
// 82 still buys one more deck cell (60), which is the only purchase the ledger promises —
// the widen below spent 8 px of slack, not the layout's purchasing power.
CHECK(kEditorCeilingWidth - kEditorMinWidth >= kDeckCellW);
// The reflow's 112 px goes entirely to the waveform, so the height does not move.
CHECK(kEditorMinHeight == 680);
// The floor did not move to make the reflow fit — the reflow was fitted to the floor. This
// wave spends the budget it was handed; it does not widen it.
CHECK(kEditorMinWidth == 1190);
// 1190 + 8: the row block was widened 1020 -> 1028 to put the two rows' filter edges on
// one pixel, which is the only reason the floor moved off Γ-W1-T4's number.
CHECK(kEditorMinWidth == 1198);
CHECK(kEditorMinWidth <= kEditorCeilingWidth);
CHECK(kEditorMinHeight <= 720);
// And the row block really is what the two rows justify inside — derived from the floor
@@ -382,11 +386,11 @@ static void testBothRowsAndTheSpanningDeckFitTheBudget() {
}
// The gutters the justification law produces at the floor, and the alignment they buy. The
// SPEC (instrument-control-surface.md §1.2/§1.3) states row 1 as 12/14/14 with both filter
// edges at x = 636; equal division of 40 px over three gutters cannot produce that, so what is
// pinned here is what the LAW produces — 14/13/13, filter edge 638 — with row 2 exact at
// 72/72 and 636. The 2 px is flagged for review; a row block of 1028 (floor 1198, still under
// the 1280 ceiling) is the width at which the law puts both edges on 640.
// At the 1028 block the justification law makes the tie-line exact by arithmetic rather than
// by a special rule: row 1's slack is 48 over three gutters (16 each, no residue) and row 2's
// is 152 over two (76 each), which lands both filter edges on 640. Only two of the three
// properties §1.3 once claimed can hold at once — a smallest gutter of exactly kDeckGroupGap
// needs a 1016 block — and 12 is a floor, not a target, so 16 satisfies the real rule.
static void testGutterArithmeticAndTheFilterTieLineAtTheFloor() {
const std::vector<DeckGroupDesc> g = sampleDeckGroups(PlayMode::Gate);
const DeckLayout dl = layoutDeck(g, kPad, 0, kAvailAtMinWidth);
@@ -394,24 +398,25 @@ static void testGutterArithmeticAndTheFilterTieLineAtTheFloor() {
const auto box = [&](int id) {
return dl.groups[static_cast<std::size_t>(indexOfGroup(g, id))].box;
};
// Row 1: flush left, flush right on the block, gutters 14/13/13.
// Row 1: flush left, flush right on the block, and three EQUAL gutters — 48 divides by 3
// with no residue, so no gutter carries a leftover pixel.
CHECK(box(kGroupPitch).x == kPad);
CHECK(box(kGroupFilter).x - box(kGroupPitch).right() == 14);
CHECK(box(kGroupVelocity).x - box(kGroupFilter).right() == 13);
CHECK(box(kGroupVoice).x - box(kGroupVelocity).right() == 13);
CHECK(box(kGroupFilter).x - box(kGroupPitch).right() == 16);
CHECK(box(kGroupVelocity).x - box(kGroupFilter).right() == 16);
CHECK(box(kGroupVoice).x - box(kGroupVelocity).right() == 16);
CHECK(box(kGroupVoice).right() == kPad + kDeckRowBlockW);
// Row 2: flush left, flush right, and its two gutters exactly equal — the property the
// 1020 block was chosen for, and the one it does deliver.
// Row 2: flush left, flush right, two gutters exactly equal.
CHECK(box(kGroupPitchEnv).x == kPad);
CHECK(box(kGroupFilterEnv).x - box(kGroupPitchEnv).right() == 72);
CHECK(box(kGroupAmpEnv).x - box(kGroupFilterEnv).right() == 72);
CHECK(box(kGroupFilterEnv).x - box(kGroupPitchEnv).right() == 76);
CHECK(box(kGroupAmpEnv).x - box(kGroupFilterEnv).right() == 76);
CHECK(box(kGroupAmpEnv).right() == kPad + kDeckRowBlockW);
// The filter tie-line, block-relative. Row 2 lands on the specified 636; row 1 lands 2 px
// past it. See this test's header.
CHECK(box(kGroupFilterEnv).right() - kPad == 636);
CHECK(box(kGroupFilter).right() - kPad == 638);
// The tie-line, block-relative: both filter edges on ONE pixel, which is what the widen
// bought. Pinned as an identity too, so a group-width change cannot pass by moving both.
CHECK(box(kGroupFilterEnv).right() - kPad == 640);
CHECK(box(kGroupFilter).right() - kPad == 640);
CHECK(box(kGroupFilter).right() == box(kGroupFilterEnv).right());
// MASTER is right-anchored outside the block, one kDeckGroupGap clear of it.
CHECK(box(kGroupMaster).x - box(kGroupVoice).right() == kDeckGroupGap);
@@ -450,8 +455,8 @@ static void testGuttersHoldTheirMinimumAndTheTieLineDriftsAboveTheFloor() {
CHECK(drift <= lastDrift);
lastDrift = drift;
}
// It really does open up, and by far more than the 2 px it starts at — separation
// above the floor is the accepted outcome, not a near-miss to be pinned back.
// It really does open up: the tie-line is exact AT the floor and separates above it,
// which is the accepted outcome rather than a near-miss to be pinned back.
CHECK(lastDrift < -50);
}
}
@@ -467,8 +472,13 @@ static void testTheMasterDeckInteriorLandsOnBothRowBaselines() {
CHECK(m.box.width == 142);
CHECK(m.box.height == 216);
// 6 + 60 + 8 + 62 + 6 — the decomposition, not just the total.
CHECK(kDeckGroupPadX + kDeckCellW + kDeckColumnGap + 62 + kDeckGroupPadX == 142);
// 6 + 60 + 8 + 62 + 6 — the decomposition, not just the total, and the 62 is the meter
// module's own kMeterColumnW rather than a copy of it. That link is the whole point: the
// column is banked to GROW (§1.2), and a reserve that did not track it would leave the
// interior underfilling or overrunning with every test still green.
CHECK(kDeckGroupPadX + kDeckCellW + kDeckColumnGap + kMeterColumnW + kDeckGroupPadX == 142);
CHECK(m.column.id == cell(DeckParam::kMasterMeter));
CHECK(m.column.box.width == kMeterColumnW);
// One cell drawn (gain) and one slot RESERVED below it: the reserve is height at a fixed
// position and draws nothing.