docs: close out Θ-W3-T2 into COMPLETED, collapse the W3 wave, and record the overlay contrast wart as open
This commit is contained in:
@@ -261,6 +261,74 @@ single derivation from the value type; `PlayParams` stays a plain copyable value
|
||||
- A filter envelope only advances while its depth is non-zero (the exact-skip at
|
||||
`modAmount == 0`), which is what keeps the at-rest path byte-identical.
|
||||
|
||||
### Θ-W3-T2 — staged-envelope-curves
|
||||
|
||||
Grows the envelope-overlay editor from an amp-only fixture into the shared graphical
|
||||
surface for all three envelopes (amp, pitch, filter): a corner radio switch per deck
|
||||
selects which envelope is overlay-active (none by default, exclusive); every sloped
|
||||
stage on every envelope (Attack/Decay/Release — Hold and Sustain stay flat) gets an
|
||||
editable curve exponent (0.1–10, 1.0 the linear neutral) via a paired inner knob dial
|
||||
and a round mid-segment overlay knot, both resolving through the one curve law in
|
||||
`src/core/util/curve_law.h`; the pitch envelope becomes AHD (Attack → Hold → Decay,
|
||||
Hold a fraction of the time remaining after Attack and Decay, so A+H+D ≤ span holds by
|
||||
construction, no clamp); AHDSR envelopes get a right-anchored release, dragged from
|
||||
its top node with the bottom-right corner fixed; and the Trigger amp/filter
|
||||
fade-in/fade-out pair is retired in favor of a Trigger AHD, consolidating what were
|
||||
two staged-shape mechanisms into one — item 8's rule (pitch always AHD; amp and filter
|
||||
AHDSR in Gate, AHD in Trigger) governs all three. The Trigger × Preserve end-of-sample
|
||||
click is fixed at its root cause: `freezeTail()` stopping the pitch shifter's writer a
|
||||
full window before the read head arrives.
|
||||
|
||||
**Open question resolved — per-mode stage-value state.** Gate and Trigger keep
|
||||
SEPARATE stored stage values, on both the amp (`PlaySeconds::adsr` +
|
||||
`PlaySeconds::trigAhd`) and the filter (`FilterSeconds::env` + `FilterSeconds::trigEnv`).
|
||||
Migration forces it: an old instance carries both an AHDSR and a fade pair, and one
|
||||
shared set cannot preserve both modes' prior sound. Cost: ~160 bytes of persisted
|
||||
state per instance, 6 additional `DeckParam` ids.
|
||||
|
||||
**Deviations from spec:**
|
||||
- **The migration exponent is FITTED, not neutral — Daniel's explicit ruling,
|
||||
resolving a spec contradiction.** PLAN.md stated both "pre-existing instances load
|
||||
at exponent 1.0" and "exponents at whatever reproduces the prior fade shape";
|
||||
those conflict, and the fix resolves toward the second, since it carries the
|
||||
migration guarantee. Attack lifts at **p = 0.6133**, decay at **q = 1.7437**; max
|
||||
deviation from the retired equal-power (sin/cos) fade shape drops from 0.2105 to
|
||||
0.0875. Every non-migrated curve still lifts to the 1.0 neutral.
|
||||
- **Item 4's fix is deliberately WIDER than spec.** The spec scoped the end-of-sample
|
||||
click fix to Trigger × Preserve; the landed fix is not mode-scoped, so Gate ×
|
||||
Preserve × source-exhaustion also now rings out (~4 ms) where it previously
|
||||
hard-cut. A held Gate note whose source runs out with no loop is cut at sustain
|
||||
level, landing on the same recycled synthetic tail — scoping the fix to Trigger
|
||||
alone would have knowingly left that click.
|
||||
- **Migration is lossy under a sample-rate mismatch** — a documented bound, not a
|
||||
bug. The retired fades were source frames; the lift divides by the project rate
|
||||
while the AHD rebuilds at decode rate, so a rate mismatch shifts migrated stage
|
||||
lengths by that ratio. Documented in the v10 version ladder
|
||||
(`component_state_io.h`) with a test.
|
||||
- **Payload version is v10.** `component_state_io.cpp` was split on the format seam
|
||||
into `component_state_io.cpp` + a new `params_payload.{h,cpp}`.
|
||||
- **New pure module:** `src/core/util/curve_law.h` — the one per-segment curve law
|
||||
(exponent domain, normalized-position→level map, the mid-segment inverse an overlay
|
||||
knot drags through, and the knob's norm↔exponent travel with an exact centre
|
||||
detent). The neutral exponent is a bit-identity. Measured cost of a non-neutral
|
||||
exponent: ~4.7 ns per evaluation, +224 ns/output frame worst case at 16 voices —
|
||||
3.1% → 4.1% of one core at 44.1 kHz.
|
||||
- **`OverlayEnv` and the overlay-selection state machine live in
|
||||
`core/instrument/ui/deck_groups`**, not the shell.
|
||||
- **The knot-creation gesture differs from spec.** Spec said dragging a segment
|
||||
*adds* a knot; the landed behavior draws the knot unconditionally on every sloped
|
||||
non-zero segment and responds to a drag within the grab radius. Daniel confirmed
|
||||
this reading stands.
|
||||
- **Loop markers moved from `AccentTertiary` to `AccentSecondary`** — they collided
|
||||
exactly with the envelope trace (RGB delta 0) in the same overlay rect. Daniel
|
||||
ruled. The palette is under active change on a separate branch, so the specific hex
|
||||
values are in flux (see `docs/TODO.md`).
|
||||
|
||||
**Still open — not closed by this track.** The envelope overlay's contrast against
|
||||
the waveform (tertiary purple, measured 1.37:1, below the 3:1 indicator floor) awaits
|
||||
Daniel's eye on a build; pinned as a flagged deviation in `tests/test_theme.cpp`. See
|
||||
`docs/TODO.md`.
|
||||
|
||||
### Ξ-W1-T1 — tracking-consolidation
|
||||
|
||||
Consolidates the provenance/usage territory into one system: the retired
|
||||
|
||||
+15
-213
@@ -172,219 +172,21 @@ exist before it can receive a corner radio switch and inner curve dials. T1 addi
|
||||
depends on W2-T1 for the filter itself: the filter is the first control set where the
|
||||
latched-at-note-on delivery model fails audibly, and it is what made the defect visible.
|
||||
|
||||
**Two tracks, and they are SERIAL — T1 before T2. This is not a priority hint.** Θ-W4's
|
||||
two tracks are disjoint and merely ordered; these two are not. T1
|
||||
(`live-parameter-delivery`) changes how every playback parameter *reaches* a sounding
|
||||
voice; T2 (`staged-envelope-curves`) changes what *shape* the three envelopes have. Both
|
||||
land inside `envelopes.h`'s stage math: T1 has to define what happens to a stage's
|
||||
counters when its duration changes mid-flight, and T2 rewrites the same level
|
||||
computation to apply a per-segment curve exponent and to grow the Trigger AHD. Two
|
||||
writers on those forty lines is a merge fight over the phase's hottest code.
|
||||
|
||||
The ordering is T1-first for one structural reason: **T2 adds parameters, T1 defines how
|
||||
parameters are delivered.** Authoring T2's curve exponents, Hold fraction, and Trigger
|
||||
AHD fields under the latching model means adding them, then re-plumbing every one of them
|
||||
through the live path afterwards — a backfill cliff, and the exact shape of avoidable
|
||||
rework. T1 first means every parameter T2 introduces is authored into the delivery
|
||||
mechanism from the start. The cost of the ordering is honest and worth stating: T2 is the
|
||||
wave's user-visible payload and T1 delays it by one track.
|
||||
|
||||
T1 has landed and specified its mid-stage rule in terms that survive T2: **hold
|
||||
normalized stage position** — φ = elapsed/duration held fixed across a duration change,
|
||||
then advancing at 1/newDuration (Daniel's pick among the candidates T1 laid out; see
|
||||
`docs/COMPLETED.md`). It is expressed over *normalized stage position* rather than over
|
||||
output level specifically so T2's exponent is a pure map of that position and composes
|
||||
with the rule rather than invalidating it.
|
||||
|
||||
Beyond the ordering, the W3 collision is the phase's densest, which is why neither track
|
||||
splits further: the envelope parameter model, the per-sample envelope evaluation, the
|
||||
deck-band inner dials, and the waveform-band overlay are one system with one source of
|
||||
truth (the settled S-VIEW rule: node-drag and slider-edit read/write the same model,
|
||||
structurally, not via a listener chain). Splitting T2 into model and UI tracks would put
|
||||
two writers on `play_params.h` + `component_state_io` and two writers on the overlay's
|
||||
forward/inverse map pair. The serialization is the correct answer.
|
||||
|
||||
#### Θ-W3-T1 — `live-parameter-delivery`
|
||||
|
||||
**Θ-W3-T1 has landed** — see `docs/COMPLETED.md` for the full narrative. Continuous
|
||||
playback controls (filter cutoff/Q/morph/drive/mod amount/key-track; every stage time
|
||||
and level on all three envelopes) are now delivered live to sounding voices via a
|
||||
seqlock-published `LiveValues` block owned at processor-instance scope, above
|
||||
`LoadedInstrument`, so a drain-slot voice keeps tracking the knob (the desired
|
||||
behavior). `foldLive(const PlayParams&)` is the single derivation from the value type.
|
||||
Both of Daniel's ruling questions are answered and implemented: **reload tier =
|
||||
Grouping B** (root note, loop span, and start frame still reload; everything else
|
||||
continuous goes live), and **mid-stage rule = candidate (iv), hold normalized stage
|
||||
position** (φ = elapsed/duration held fixed across a duration change, then advancing
|
||||
at 1/newDuration) — expressed over normalized position specifically so T2's
|
||||
per-segment curve exponent, below, composes with it. **Trigger's %-length and fades
|
||||
are NOT live** — they are baked into `SampleData` at build, so a Trigger-mode instance
|
||||
gets zero live amp delivery until T2 folds the fade pair into the AHD. The five
|
||||
non-live exclusions (`kKeyTrack`, `kFilterVel`, `kTrigLength`, `kTrigFadeIn`,
|
||||
`kTrigFadeOut`) are documented in `src/core/instrument/ui/deck_groups.h`, now their
|
||||
single home.
|
||||
|
||||
#### Θ-W3-T2 — `staged-envelope-curves`
|
||||
|
||||
**Prerequisite: Θ-W3-T1 has landed** — see `docs/COMPLETED.md`. Both tracks write
|
||||
`envelopes.h`'s stage math, and this track's curve exponent must be authored as a map of
|
||||
the normalized stage position T1's mid-stage rule holds fixed (φ = elapsed/duration,
|
||||
candidate (iv)).
|
||||
|
||||
**Goal.** Grow the envelope-overlay editor from an amp-only fixture into the shared
|
||||
graphical surface for every envelope, give every envelope shapeable segments, fix the
|
||||
overlay's layout and contrast failures, consolidate the Trigger amp fade pair into the
|
||||
AHD — and, in the same region, kill the Trigger×Preserve end-of-sample click.
|
||||
|
||||
**Consolidates items 1, 8, 14, 4.**
|
||||
|
||||
**Surface boundary — owns:**
|
||||
- `core/instrument/engine/` — the envelope evaluator (from W1's seam), `zone_params.h`,
|
||||
`pitch_shift.{h,cpp}` (item 4's tail), the voice-render envelope and tail path.
|
||||
- `core/instrument/map/` — `trigger_seam`, `component_state_io` (curve exponents, the
|
||||
pitch Hold stage, the Trigger AHD, migration).
|
||||
- `core/instrument/ui/` — `envelope_overlay`, `envelope_edit`, `knob_deck` (the inner
|
||||
dial).
|
||||
- `src/shell/instrument/` — the deck band (inner dials, radio switches) and the
|
||||
waveform band's **overlay layer only** (T2's lane geometry from W2 is consumed, not
|
||||
re-owned).
|
||||
|
||||
**Behavior — the overlay surface (item 1).**
|
||||
- **Radio switch per envelope deck.** Each envelope knob-deck group (AMP ENVELOPE,
|
||||
PITCH ENV, and now Filter) gains a radio switch in the corner of its deck. Selecting a
|
||||
deck's radio makes *that* envelope the one displayed and editable in the graphic
|
||||
waveform overlay — replacing today's always-on Amp AHDSR. The switch is exclusive:
|
||||
**one overlay-active envelope at a time, or none** — no-envelope-shown is a valid
|
||||
state, not an error.
|
||||
- **Default overlay: none.** The editor opens with **no envelope selected**.
|
||||
- **Segment curve values on all envelopes.** Amp AHDSR, Pitch AHD, and Filter AHDSR all
|
||||
gain an editable curve value per *sloped* segment. The curve is exponential; the
|
||||
per-segment parameter is the exponent scalar, range **0.1 to 10**. **1.0 is the linear
|
||||
neutral** (y = x^1.0 is linear).
|
||||
- **Which segments are sloped: every stage except Hold and Sustain.** For an AHDSR that
|
||||
is Attack, Decay, Release; for the Pitch AHD, Attack and Decay. Hold is flat
|
||||
everywhere and carries no curve dial.
|
||||
- **Inner dial on curvable-segment knobs.** Every knob controlling a sloped, curvable
|
||||
segment gains an **inner dial**: its own inner arc, its own hover accent (tertiary
|
||||
purple), its own needle, its own numerical label. The inner dial controls that
|
||||
segment's curve exponent; the outer knob keeps controlling the segment's time/level
|
||||
value as today.
|
||||
- **Curve editing in the overlay.** Dragging on a segment **adds a round mid-segment
|
||||
knot** whose drag adjusts that segment's curve — the overlay is a curve-edit surface
|
||||
in its own right, alongside (not instead of) the inner dial. Same one-model rule as
|
||||
the existing draggable nodes: knot-drag and inner dial cannot diverge.
|
||||
- **Overlay recolor.** Envelope segments change from secondary blue to **tertiary
|
||||
purple** — secondary blue contrasts poorly against the primary green waveform behind
|
||||
it. Re-verify the tertiary-purple-on-green pair against `theme`'s contrast-floor
|
||||
assertions.
|
||||
- **Pre-existing instances load at exponent 1.0** — the linear neutral — so their
|
||||
audible envelope behavior is unchanged.
|
||||
|
||||
**Behavior — layout policy and the pitch AHD (item 8).**
|
||||
- **The release segment anchors to the right edge** of the overlay. Release is dragged
|
||||
from its **top node** (the node joining sustain to release) instead of the bottom
|
||||
corner; the bottom corner — the envelope's end point — becomes **fixed/anchored**, not
|
||||
draggable. Today's failure is that with little or no release the sustain portion
|
||||
occupies only a small stretch and the whole figure reads off-center.
|
||||
- **Spec change: the pitch envelope becomes AHD** — Attack → Hold → Decay. **Hold ranges
|
||||
0 to 100% of the time remaining after Attack and Decay** (100% of
|
||||
`sample length − (attack + decay)`). At 100% Hold fills all remaining time; at 0% it
|
||||
takes none.
|
||||
- **The A + H + D ≤ sample-length bound holds by construction, not by a clamp.** Hold is
|
||||
expressed as a fraction of what is left, so the sum cannot overflow. Do not add a
|
||||
separate clamp; none is needed.
|
||||
- **Scope rule — split on the sustain stage.** The 1:1-overlay property and the
|
||||
combined-time bound apply to **all envelopes that do NOT have a sustain stage**;
|
||||
envelopes **with** a sustain stage get the right-anchored-release policy instead. The
|
||||
two policies **coexist rather than merge**. Daniel's rationale: the 1:1 mapping only
|
||||
makes sense for trigger, not gated envelopes. **The pitch envelope's D is not a
|
||||
release, so it is not right-anchored.**
|
||||
|
||||
**Behavior — mode-driven envelope shape (item 14).**
|
||||
- **Trigger mode: fades out, AHD in.** In Trigger mode the amp envelope becomes an AHD
|
||||
per item 8's definition. The fade-in-length and fade-out-length controls **go away in
|
||||
Trigger mode**; Attack and Decay carry those roles. Daniel's motive is stated:
|
||||
reducing code for the same job — one staged-envelope design covering what is currently
|
||||
two mechanisms.
|
||||
- **The Trigger AHD's sloped segments get the full curve treatment** — exponent 0.1–10,
|
||||
inner dials, mid-segment overlay knots, tertiary-purple rendering.
|
||||
- **Gate mode unchanged** — the amp envelope stays the AHDSR with the right-anchored
|
||||
release.
|
||||
- **The filter envelope follows the same rule.** Stated once, governing all three:
|
||||
**pitch is always AHD (1:1 overlay); amp and filter are AHDSR in Gate
|
||||
(right-anchored release) and AHD in Trigger (1:1 overlay).** The rule selects by the
|
||||
envelope's **current shape under the active playback mode**, not by which processor it
|
||||
modulates. No new rule is needed — item 8's rule already decides every case.
|
||||
- **Pre-existing instances reopen sounding identical.** A Trigger instance's prior
|
||||
fade-in/fade-out contour must be reproduced by the loaded AHD. The evident mapping
|
||||
(Attack ← fade-in, Decay ← fade-out, Hold ← the full remainder, exponents at whatever
|
||||
reproduces the prior fade shape) is a verify-at-implementation detail. **A prior zero
|
||||
fade-out is Decay = 0** — the abrupt end stays representable, so nothing the old
|
||||
controls could express is lost.
|
||||
|
||||
**Behavior — the Trigger×Preserve click (item 4).**
|
||||
- In Trigger mode — polyphonic or monophonic alike — with the pitch engine in
|
||||
**Preserve mode only**, the end of the sample produces an audible click. The scoping is
|
||||
the useful part of the report and is recorded as given: Trigger × Preserve ×
|
||||
end-of-sample; Varispeed is not implicated.
|
||||
- Expected: a Trigger one-shot in Preserve ends silently — no click or discontinuity at
|
||||
the sample end, in either voice mode.
|
||||
- **Why it is in this track and not earlier:** the defect sits in the region the
|
||||
fade-out currently governs, and this track retires that mechanism. Fixing it in a
|
||||
prior wave means fixing it, then re-verifying it under the surviving mechanism
|
||||
anyway — and the only other instrument-side track before this one owns the whole
|
||||
engine. The gate below is the *post-consolidation* gate.
|
||||
|
||||
**Acceptance criteria.**
|
||||
- Each envelope deck shows a corner radio switch; activating one puts that envelope in
|
||||
the overlay, editable there, and the overlay tracks the switch immediately. At most one
|
||||
envelope is overlay-active; with none active the overlay draws no envelope.
|
||||
- The editor opens with no envelope overlay-active.
|
||||
- Every curvable-segment knob shows the inner dial (inner arc, tertiary-purple hover
|
||||
accent, needle, numeric label); sweeping it 0.1 → 10 visibly reshapes the overlay
|
||||
segment and **audibly** reshapes the envelope on played notes. Hold and Sustain knobs
|
||||
carry no inner dial.
|
||||
- Dragging on an overlay segment adds a round mid-segment knot; dragging the knot adjusts
|
||||
that segment's curve, and the segment's inner dial reflects the same value.
|
||||
- Overlay envelope segments render in tertiary purple and are clearly legible against the
|
||||
primary green waveform.
|
||||
- With release at zero or minimum, the sustain segment extends to (near) the right edge —
|
||||
the overlay reads full-width, not bunched left. Dragging the sustain→release top node
|
||||
adjusts release; the bottom-right corner is fixed and not draggable.
|
||||
- Envelopes with a sustain stage follow that anchoring policy; the pitch envelope's Decay
|
||||
is **not** right-anchored.
|
||||
- The pitch envelope plays and displays three stages — Attack, Hold, Decay — with Hold
|
||||
spanning 0–100% of the time remaining after Attack and Decay. **No combination of A, H,
|
||||
D exceeds the sample length, and no clamp fires, because none exists.**
|
||||
- The overlay of any sustain-less envelope is 1:1 with the waveform's time axis: a stage
|
||||
boundary at N seconds sits over the waveform at N seconds.
|
||||
- In Trigger mode the amp deck shows Attack / Hold / Decay — **no fade-in or fade-out
|
||||
control anywhere in Trigger mode** — with curve inner dials on Attack and Decay and
|
||||
none on Hold.
|
||||
- Switching between Gate and Trigger switches the amp *and* filter envelope surfaces
|
||||
(deck and overlay) between AHDSR and AHD accordingly.
|
||||
- The Trigger amp AHD behaves stage-for-stage like the pitch AHD — Hold semantics, curve
|
||||
treatment, and overlay mapping match. (The observable proxy for the consolidation
|
||||
motive: one staged-envelope design, several consumers.)
|
||||
- **Migration:** a project saved before this change reopens sounding identical — every
|
||||
curve at exponent 1.0, and a Trigger instance's prior fade contour (including a zero
|
||||
fade-out's abrupt end) audibly reproduced by the loaded AHD.
|
||||
- **Item 4's gate, under the surviving mechanism:** a Trigger-mode note played in
|
||||
Preserve, in both Poly and Mono, ends with no audible click at the sample end —
|
||||
verified by ear *and* by inspecting the rendered output for a terminal discontinuity.
|
||||
Varispeed playback is unchanged.
|
||||
|
||||
**Open questions.**
|
||||
- **Stage-value state across the Gate/Trigger switch — shared or per-mode? [propose]**
|
||||
The Gate AHDSR and Trigger AHD share stage names (A, H, D); whether they share *values*
|
||||
(one envelope whose S and R fall away in Trigger) or keep per-mode state (item 3's
|
||||
"save but inactive" dual-state precedent) is unspecified — and the question covers the
|
||||
**filter envelope too**. Migration leans per-mode for the amp: an old instance carries
|
||||
both its AHDSR values and its Trigger fade values, and a shared-value model cannot
|
||||
preserve both modes' prior sound at once. The filter is new and carries no migration
|
||||
weight either way; matching the amp's answer is the natural default. Resolve with a
|
||||
proposal at review — the sound-identical gate must hold for whichever mode a saved
|
||||
instance plays in.
|
||||
**Both tracks have landed** — Θ-W3-T1 (`live-parameter-delivery`) and Θ-W3-T2
|
||||
(`staged-envelope-curves`) — see `docs/COMPLETED.md` for the full narrative of each.
|
||||
Between them: every continuous playback control (filter cutoff/Q/morph/drive/mod
|
||||
amount/key-track, every stage time and level on all three envelopes) now reaches a
|
||||
sounding voice live instead of latching at note-on, and the envelope-overlay editor
|
||||
grew from an amp-only fixture into the shared graphical surface for all three
|
||||
envelopes — a corner radio switch per deck (none active by default), curve-shapeable
|
||||
segments on every sloped stage (0.1–10 exponent, an inner dial paired with an overlay
|
||||
knot), the release-right-anchored AHDSR layout against the pitch envelope's 1:1 AHD,
|
||||
and the Trigger amp/filter fade pair folded into a Trigger AHD, consolidating what
|
||||
were two staged-shape mechanisms into one. The Trigger × Preserve end-of-sample click
|
||||
is fixed; the landed fix is wider than scoped, also ringing out Gate × Preserve ×
|
||||
source-exhaustion, previously a hard cut. The ordering was deliberately serial — T1's
|
||||
live-delivery mechanism landed first so T2's new curve exponents and Trigger AHD
|
||||
fields were authored directly into it rather than backfilled afterward.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -179,3 +179,17 @@ Forward-looking follow-ups. Deferred by decision, not oversight — each entry r
|
||||
**Priority / risk.** Low / deferred. Mitigated in the meantime: the console message names the stranded file's project-relative path, so the operator can recover or remove it rather than discovering it later as an unexplained orphan.
|
||||
|
||||
**Done looks like.** Switching projects mid-record leaves the recorded file with a ledger record in the project it belongs to, so a later prune of that project can reclaim it normally.
|
||||
|
||||
## Envelope overlay's tertiary-purple trace fails the 3:1 indicator contrast floor against the waveform
|
||||
|
||||
**Context (what shipped — Θ-W3-T2, staged-envelope-curves).** The overlay recolor moved every envelope's segment trace to `Role::AccentTertiary` (purple), specifically because it separates from the primary-green waveform underneath it more than the previously-used secondary blue did — `tests/test_theme.cpp`'s `testOverlayAccentSeparatesFromTheWaveformAccent` pins that the tertiary pairing measures a HIGHER contrast than the retired secondary one against the same waveform color.
|
||||
|
||||
**The wart.** Even the improved pairing measures **~1.37:1**, well below the **3:1** floor `theme.h` states as the indicator-class contrast floor everywhere else in the kit. The test pins the measured value as a KNOWN, HELD deviation rather than asserting the floor.
|
||||
|
||||
**Intended fix.** Not yet specified — awaiting Daniel's eye on a running build before choosing a new tertiary hue or a different palette approach.
|
||||
|
||||
**The constraint the fix MUST handle.** A separate palette branch is in flight (worktree `palette-darker-teal`) and may change the tertiary/secondary hues involved; a fix landed here could collide with or be superseded by that branch. Also: whatever fix lands must not reopen the loop-marker/envelope-overlay role collision this same track resolved by moving loop markers to `AccentSecondary` — a new tertiary hue must keep that separation too.
|
||||
|
||||
**Priority / risk.** Not yet triaged — pinned as a flagged deviation in `tests/test_theme.cpp`, open pending a visual pass.
|
||||
|
||||
**Done looks like.** The envelope overlay trace clears the 3:1 indicator floor against the primary-green waveform, `tests/test_theme.cpp`'s pinned deviation is replaced with a floor assertion matching every other indicator pairing, and the fix is reconciled with whatever the in-flight palette branch lands.
|
||||
|
||||
Reference in New Issue
Block a user