docs: close out Θ-W5, and record two spline-overlay UX deferrals

Collapse Θ-W5 to wave-level in PLAN.md, append its narrative to COMPLETED.md, fix a stale resolvePlay reference in core/instrument/CLAUDE.md, and file the drag-off-margin and AttackEnd/Origin shadow warts in TODO.md.
This commit is contained in:
2026-08-01 00:29:40 -04:00
parent 85cf34f858
commit 213ecfafe6
4 changed files with 103 additions and 107 deletions
+48
View File
@@ -504,3 +504,51 @@ over the amp gain curve and persist it. Fixed on both routes (the popup close no
cancels the drag; the mutable accessor refuses `kNone`). It has **no automated
regression pin** — `src/shell/instrument/` has no test target, and the bug is shell
state-machine coupling with no pure-layer equivalent.
### Θ-W5-T1 — spline-egs
Ships a free-drawn alternative to every staged envelope: the pitch, filter, and amp EGs
can each switch Staged → Spline and have their contour drawn directly on the waveform
overlay. The one shared monotone-spline implementation
(`core/instrument/engine/velocity_curve`) gained **hard points** as a per-segment rule —
a hard point does no curve smoothing on either adjacent segment, so the natural sharp
angle stands instead of a continuous derivative — and the enhancement flows to every
consumer, including the existing velocity→amp transfer curve, with no fork.
- **Dual state, save-but-inactive.** Both the Staged and Spline state persist
simultaneously; switching modes never converts or discards the inactive one, so
Staged↔Spline round-trips losslessly. Params payload reached **v13**; v12 projects
still load.
- **Gate unavailable in Spline mode.** A Spline EG's contour always covers the full
sample length as a pure time function (the Trigger/one-shot playback model), so Gate
is not selectable while it's active.
- **Point-editing grammar converged**: left-click adds a point, right-click deletes it,
control-click toggles hard/smooth — one grammar shared by both spline consumers (the
EG overlay and the velocity-curve popup), matching the popup's already-shipped
right-click delete.
- **Point-count ceiling: 128 — a musical bound, not a performance one.** Segment lookup
is an indexed binary search (≤7 steps at 128 points); the cap exists so long rhythmic
phrases (roughly two points per articulation event) aren't limited, not because the
evaluator is expensive.
- **Staged controls disabled while Spline is active** — that envelope's segment knobs
and their inner curve dials render disabled and reject edits; the dormant staged state
is edited only by switching back to Staged.
- The overlay's contour is normalized to the full sample length and drawn 1:1 with the
sample's time axis; a different-length capture rescales the stored contour
proportionally.
A follow-on change in the same track reworked deck cell width: `-1` in `cellIds` changed
meaning from "a blank cell holding geometry" to **one cell's width, reserved and
redistributed** — a Trigger face that drops Sustain and Release now gets wider cells
instead of 144 px of dead slots. Group widths, row packing, deck height, and Gate-mode
cell widths are unchanged.
**Deviations from spec / code review:**
- A pure `resolveWaveformClaim` predicate (`core/instrument/ui/spline_edit`) now resolves
competing waveform-band clicks — contour node, crossfade tab, marker column, staged
envelope node — by **smallest nominal target area among candidates that actually
contain the click**, replacing resolution by check order.
- The Gate-unavailable-while-drawn rule was consolidated into
`enforceGateUnavailableWhileDrawn` (`core/instrument/engine/play_params.h`), now the
single home of that rule, called by both `resolvePlay` and the editor's
`applyControl`.