docs: close out Θ-W6 and Phase Θ, and file two legibility-pass deferrals

This commit is contained in:
2026-08-01 10:36:22 -04:00
parent 7504eefb59
commit ae9019465e
3 changed files with 145 additions and 53 deletions
+61
View File
@@ -552,3 +552,64 @@ cell widths are unchanged.
`enforceGateUnavailableWhileDrawn` (`core/instrument/engine/play_params.h`), now the
single home of that rule, called by both `resolvePlay` and the editor's
`applyControl`.
### Θ-W6-T1 — legibility-and-antialiasing
Made the editor legible, then audited every drawn surface for high-DPI clean rendering
— sequenced sizing first, audit second, since the audit's disposition list needed a
surface that had stopped moving.
- **Sizing.** Knobs grew 28→40 px (inner curve dial 14→20), the deck cell 48×58→60×74,
and the label band 12→16 px, now drawn in `Font::Label` rather than `Font::Micro`.
Group captions and toggle segments deliberately stay `Font::Micro` — bumping them
would grow the per-group `captionWidth` reserves, and row 1 has only 14 px of
headroom at the floor width.
- **Editor default/minimum size 840×620 → 980×680**, because the deck cannot pack
three rows at the old floor with the wider cells. An existing saved instance's
window grows on open. The floor is validated by a derived test rather than
literals.
- **All 14 time-constant labels now read in ms**; internal representation untouched
(`formatEnvTimeMs` is display-only). `holdFraction` knobs and `Len %` stay `%`
they are fractions, not times. The bank panel's clip-length readout is a duration,
not a parameter time constant, and stayed out of scope.
- **Double-click reset, per ring.** Outer ring resets the value, inner dial resets
the exponent to 1.0, independently. The window class gained `CS_DBLCLKS`;
`WM_RBUTTONDBLCLK` was added as its peer so the spline right-click delete survives,
and both DBLCLK handlers fall through to the ordinary down handler. The chrome's
preview-velocity knob answers reset too, resolving against the drawn circle via a
shared `inKnobFace` rule now used by both the deck and the chrome.
- **Antialiasing pass.** Fixed: knob track/value arcs (widened to 3 px stacked-radius
AA arcs), knob needle (`LICE_ThickFLine`), inner dial arc and needle, staged
envelope slopes, spline contour, velocity-popup trace, waveform outline, preview
triangle. Already clean: node handles, curve knots, knob discs, buttons, piano
keys, loop markers, borders, gradients, text. The full disposition table is a
standing artifact in `docs/product/visual-design-language.md` §8.
- Three LICE facts the audit established: `LICE_Line` takes integer endpoints so
`aa=true` still quantizes; `LICE_FillTriangle` has no `aa` parameter at all; LICE
has no thick-arc call, so a wider ring is stacked 1 px arcs.
- **Measured cost:** the new AA waveform stroke adds ~0.41 ms per full-grid panel
repaint (0.070 → 0.48 ms over a 24-card × 2-band × 136-column grid), ~2.5% of a
60 Hz frame. Recorded in the §8 table and annotated as a one-off scratchpad
measurement, not a standing regression guard.
- **The piano-key open question is answered: not aliasing.** Every key is an
axis-aligned integer-width `LICE_FillRect`, so there was no sloped edge for
aliasing to act on; the defect was integer-division residue in the tiling, and
W2-T3's fix (remainder moved into symmetric end margins) is arithmetic. Above
client-pixel scaling it is unverified — nothing implements
`IPlugViewContentScaleSupport`.
**Two structural changes forced by review.** The waveform column's vertical
arithmetic moved into a pure, unit-tested `waveformColumnSpan` in
`core/ui/component_geometry` — the first pass had silently broken symmetry about
the midline in the shared `draw_kit` primitive that also feeds the docked bank
panel and browse thumbnails. And `PlaySeconds` plus its `AdsrSeconds`/
`AhdSeconds`/`PitchEnvSeconds`/`FilterSeconds` companions hoisted out of
`sample_map.h` into a header-only `play_seconds` INTERFACE target, so the new
`core/instrument/ui/deck_values` module stops transitively linking the bank model
and WAV codec. `deck_values` itself is an extraction of `controlValue`/
`applyControl`/`resetDeckParam`/the ms formatter out of the editor shell, making
reset semantics unit-testable; `editor_controls.cpp` dropped 469→293 lines.
All visual outcomes remain **pending Daniel's by-eye sign-off on `dev`** — sizes,
arc weight, and whether the waveform stroke improves or thickens the docked panel.
Not recorded as accepted.
+27 -53
View File
@@ -115,6 +115,9 @@ drag/drop defects that block getting captures into it.
**Consolidates items** 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16.
**All six waves have landed — Phase Θ is complete.** W1 through W6 each carry their own
landed note above; see `docs/COMPLETED.md` for every track's full narrative.
**The organizing constraint.** Three surfaces in the instrument are single-writer by
nature and dictate the wave shape:
@@ -253,60 +256,30 @@ as the boundary.
**One track.** Both items repaint essentially every surface in the editor; concurrent
tracks would collide everywhere.
#### Θ-W6-T1 — `legibility-and-antialiasing`
**Θ-W6-T1 has landed**`legibility-and-antialiasing` — see `docs/COMPLETED.md` for the
full narrative. It shipped both halves together: knobs grew 28→40 px (inner curve dial
14→20), the deck cell 48×58→60×74, and the label band 12→16 px, now drawn in
`Font::Label` rather than `Font::Micro` — group captions and toggle segments deliberately
stayed `Font::Micro`, since bumping them would outgrow row 1's headroom at the floor
width. The editor's default/minimum size grew 840×620→980×680 to fit the wider deck at
floor width; an existing saved instance's window grows on open, and the floor is
validated by a derived test rather than literals. All fourteen time-constant labels now
read in ms (display-only; internal representation untouched) — `holdFraction` knobs,
`Len %`, and the bank panel's duration readout stayed out of scope. Double-click resets
each ring independently — outer ring resets the value, inner dial resets the exponent to
1.0 — reaching the chrome's preview-velocity knob too via a shared `inKnobFace` rule.
**Goal.** Make the editor legible — bigger knobs and labels, time constants in ms,
double-click reset per ring — then audit every drawn surface for high-DPI clean rendering.
The antialiasing audit fixed knob arcs and needle, the inner dial arc and needle, staged
and spline envelope slopes, the spline contour, the velocity-popup trace, the waveform
outline, and the preview triangle; node handles, curve knots, knob discs, buttons, piano
keys, loop markers, borders, gradients, and text were already clean. The disposition
table is a standing artifact in `docs/product/visual-design-language.md` §8. The
piano-key open question is answered: not aliasing — every key is an axis-aligned
`LICE_FillRect`, so the earlier width defect was integer-division residue in the tiling,
not a sloped edge. High-DPI host scaling itself is unverified (deferred, see
`docs/TODO.md`).
**Consolidates items 10, 13.** Sequence internally: sizing first, audit second.
**Surface boundary — owns:** `core/instrument/ui/knob_deck` (sizing geometry),
`shell/panel/draw_kit` (the shared LICE draw shell — arcs, lines, slopes), and all three
Sample-face bands for repaint-level changes. **Note the blast radius:** `draw_kit` is
shared with the docked bank panel, so an antialiasing change there lands on the extension
UI too. That is desirable, but it puts the panel in this track's verification scope.
**Behavior — sizing and ergonomics (item 10).**
- **Radial knobs and their text labels grow** — both are currently too small. No target
size was given; this is a visual-judgment change accepted by eye.
- **Time-constant labels display in ms, not seconds.** A display-unit change; this makes
no claim about internal representation.
- **Double-click on any radial knob resets it to its default value.**
- **On dual-ring knobs, each ring is its own reset target**: double-click the outer ring
resets the time/level value; double-click the inner curve dial resets the exponent to
**1.0** (the settled linear neutral) — each independently, without touching the other.
**Behavior — antialiasing audit (item 13).**
- **A review pass, not a point fix.** Audit each class of drawn surface and confirm it
renders with antialiasing (or an equivalent) suitable for high-DPI, high-resolution
displays. Where one renders visibly aliased, bring it to the smooth standard.
- Daniel named the visibly pixely surfaces: **radial arcs, waveform lines, envelope
segment slopes.** By the time this runs, those include the inner dials (W3), the stereo
lanes (W2-T2), the staged *and* spline segment slopes (W3, W5), the loop markers
(W4-T1), and the piano key edges (W2-T3).
- The outcome is observable, not procedural.
**Acceptance criteria.**
- Knobs and labels are legibly larger; **Daniel signs off on the result by eye.**
- **Every** time-constant label reads in ms.
- Double-click resets any radial knob to its default; on dual-ring knobs, double-clicking
the inner dial resets **only** the exponent (to 1.0) and double-clicking the outer ring
resets **only** the value.
- Radial arcs (including the inner dials), waveform lines (including the stereo lanes),
and envelope segment slopes (staged **and** spline) render smooth — **no visible jaggies
at 100% scale or on a high-DPI display.**
- **The audit produces a short disposition list**: surfaces checked, which needed work,
which were already clean. A required deliverable, not a side effect — it is what makes
the audit re-runnable.
- Daniel signs off by eye on the named surfaces.
- The docked bank panel is re-verified for regression, since `draw_kit` is shared.
**Open questions.**
- **Target sizes [verify by eye].** No numeric target was given; item 10's own note is
that sizing is judged by eye. Bring a build, not a spec.
- **Whether the piano-key width defect was aliasing [verify].** W2-T3 fixes uniform width
regardless of cause; this audit confirms whether aliasing was the cause and whether the
fix survives DPI scaling.
All visual outcomes remain pending Daniel's by-eye sign-off on `dev`.
---
@@ -327,7 +300,8 @@ independent of the editor chain — which is what makes Ξ-W1 concurrency-safe w
bakes — "filtering, pitching, amp all set up nice" is the instrument items 1, 2, 3, and 14
build — and its settled reset scope enumerates the filter parameters, the spline contours,
and the loop points by name. Baking a processing chain that does not exist yet is not a
schedule preference; the feature is not expressible.
schedule preference; the feature is not expressible. Phase Θ landed as of Θ-W6-T1 (see
`docs/COMPLETED.md`); this gate is satisfied.
---
+57
View File
@@ -252,3 +252,60 @@ controls stacked internally (e.g. the filter's static control knobs above its en
knobs) per Daniel's design notes once they land. The VELOCITY↔VOICE adjacency criterion
is re-evaluated (or explicitly retired) against the new layout rather than the old
per-row one.
## The AA waveform stroke's cost on the docked bank panel's card thumbnails
**Context (what shipped — Θ-W6-T1, legibility-and-antialiasing).** The antialiasing
audit fixed the min/max waveform column plot by adding an AA `LICE_FLine` stroke across
each column's extremes, on top of the existing fill (`draw_kit.cpp` `drawWaveform`).
`drawWaveform` is shared by the editor's hero waveform lanes, the docked bank panel's
card thumbnails, and the browse cards — the stroke lands on all three.
**The wart.** Measured cost (Release, MSVC 14.44, real LICE, 24 stereo cards ×
136 columns = 6528 columns): fill alone 0.070 ms per full-grid repaint, fill+stroke
0.48 ms — the stroke adds ~0.41 ms, about 2.5% of a 60 Hz frame. At card-thumbnail
scale the added smoothness is far less visible than on the editor's hero lanes, so the
cost is paid on every repaint of every card for a benefit concentrated in one consumer.
**Intended fix.** The identified cheap lever: skip the stroke below a card-sized box
and keep it only on the editor's hero lanes.
**The constraint the fix MUST handle.** Not done, because it is a product call about
where the comb artifact — the min/max column plot's jagged outline — actually reads
badly enough to matter, not a performance-forced decision (2.5% of a frame on
hover/scroll/drag repaint, not a continuous cost, is not itself disqualifying).
**Priority / risk.** Low. The measurement is a one-off scratchpad number
(`docs/product/visual-design-language.md` §8), not a standing regression guard —
re-measure before relying on it again.
**Done looks like.** A size threshold (or explicit per-consumer flag) below which
`drawWaveform` skips the AA stroke, with the panel/browse cards confirmed still
readable and the editor's hero lanes unchanged.
## High-DPI host scaling is unverified (distinct from the antialiasing audit)
**Context (what shipped — Θ-W6-T1, legibility-and-antialiasing).** The antialiasing
audit (item 13) confirmed every drawn surface renders smooth at 100% scale — the
disposition table in `docs/product/visual-design-language.md` §8 is the record. That
audit is about rasterization quality at the pixel level the plugin already draws at;
it says nothing about what happens when a host scales the plugin window itself.
**The wart.** Nothing in the instrument implements `IPlugViewContentScaleSupport`. A
host that applies DPI scaling to the plugin window resamples the already-rasterized
output rather than asking the plugin to redraw at the target resolution — every AA
guarantee the audit just confirmed (and the piano-key uniform-width guarantee, §8.1)
holds only at the client-pixel level the plugin itself draws, not above it.
**Intended fix.** Not proposed. Implementing `IPlugViewContentScaleSupport` (or
confirming the host compositor's resampling is acceptable without it) is the shape of
a fix, not yet scoped.
**The constraint the fix MUST handle.** Not yet known — no design work has started.
**Priority / risk.** Not stated. Recorded as a gap, not a defect: no host behavior has
been observed to be wrong, only unverified.
**Done looks like.** Either `IPlugViewContentScaleSupport` is implemented and the
AA/uniform-width guarantees are re-verified at a scaled client size, or a decision is
recorded that host-side resampling of the rasterized output is an accepted tradeoff.