docs: close Phase Ξ — the popup abandoned by ruling, the bake window derives itself, and Γ's rung ladder shifts to v15

This commit is contained in:
2026-08-01 21:44:05 -04:00
parent 178cca3c25
commit 4bdbbe0517
2 changed files with 111 additions and 72 deletions
+59
View File
@@ -754,3 +754,62 @@ bank-format change under the current deserializer, which fails the whole bank bl
an out-of-range value — it wants its own decision); and `instrument_bake` copies the
interleaved render buffer into a `std::vector<double>` for the WAV build, roughly
doubling peak memory for a large bake.
### Ξ-W3-T1 — capture-signal-popup (spec abandoned by ruling; shipped as derived bake window)
Phase Ξ's final track, and Phase Ξ is now complete. What `docs/PLAN.md` specified was a
popup menu letting the user hand-program the capture signal: note length as a
musical-division picker (1/64 to 64/1, dotted and triplet), start/end offsets editable in
both ms and beats, velocity, and a preview trigger auditioning the programmed note, under
the acceptance criterion "preview and bake cannot diverge."
**What shipped instead, and why — the spec and the landed code diverge substantially and
deliberately, by Daniel's ruling, not by shortfall.** The popup was built (~1500 lines)
and then abandoned unmerged. Daniel, verbatim: *"I didn't realize you had already derived
a usable window. The manual stuff for baking a specific midi length was just an idea, if
we have a smarter, fewer-clicks way of doing it, that is ideal. I just don't want to lose
anything when we bake. We can abandon the whole parameterized bake window if we can safely
derive the window in gate and trigger modes."* An audit then established, with executable
tests (`tests/test_bake_window.cpp`), that the window derives losslessly everywhere except
one irreducible case. What actually shipped, in `src/core/instrument/bake/`:
- **The bake window derives itself.** Trigger derives from the play span; Gate *without*
an active sustain loop derives from source exhaustion + release; Gate *with* an active
loop takes one user value, because a loop sounds for as long as it is held and no
derivation can supply a duration.
- **One control: "Hold,"** a musical-division picker in the chrome row, visible and
settable only when Gate + an active sustain loop. `bakeWindowNeedsHold` is the predicate
and it reads the ENGINE's loop fold (`resolveLoop`) rather than the loop fields.
- **Velocity comes from the instance's persisted preview velocity**, not a hard-coded 100
— three velocity curves are live, so the velocity is a property of the sound being
printed.
- **No preview trigger, and no popup at all.** The chrome-row play button stays a pure
MIDI trigger; bake parameters are their own thing. Daniel's ruling: *"play button is
pure MIDI trigger, Bake parameters are their own thing."* So the plan's acceptance
criterion 2 ("preview and bake cannot diverge") and its preview-trigger behavior bullet
are **retired by ruling.**
- **Three truncation bugs that pre-existed on `dev` were found and fixed:** a drawn EG
plus a stale stored `%`-length lost up to the whole take; the Preserve pitch engine's
window closed on the exact frame the terminal declick ramp began, ending files on a
full-scale hard cut; and the Gate hold length quantized onto a musical ladder that
**saturated at 384 beats**, cutting any source past it mid-sound (at 120 BPM, anything
from 192 s up — a full-mix bounce).
- **An invariant was deliberately amended:** `note/CLAUDE.md`'s "note length stays
musical-division-only" is superseded — a note length now carries EITHER an exact
duration (every derived path) or a musical division (the Hold picker only).
Quantizing a derived length is what caused the saturation truncation.
- Two undefined-behaviour paths closed as fallout: a NaN `keyTrack` from a corrupt payload
reached a narrowing cast on the per-sample audio path, and a misaligned payload tail
could fabricate a value rather than degrade to absent.
- Payload rung **v14** consumed (the Hold division).
**Not verified in a live REAPER session** — worth carrying forward as owed: the "Bake
Hold" label fitting its 56 px cell, the Hold knob's duration-ordered travel, and the
control's appearance/disappearance on the 500 ms sync tick.
**Deferred, not done — logged to `docs/TODO.md`:** the loop intrinsic is folded twice
(the editor's `pickedMarkers` resolves it from the live bank blob first, the processor's
`reloadInstrument` resolves it from the instance ref via `resolveCapture`), so the two can
disagree whenever a bank blob's loop for a capture differs from the copy in the instance's
own refs table. Pre-existing — `bakeWindowNeedsHold` is only a new *consumer* of
`pickedMarkers`, not the origin of the divergence.
+52 -72
View File
@@ -71,15 +71,15 @@ in Γ-W4-T1 and changes no wave boundary.
### Flagged for awareness — not blocking, but decision-grade
1. **Item 15 has an unresolved cross-artifact seam, and it is the phase's largest
unknown.** The instrument is a read-only bank consumer by invariant
(`src/shell/instrument/CLAUDE.md`), and the one previous attempt at an
instrument→extension relay (S13) closed with a **DEGRADED** spike verdict and was
deferred (`docs/TODO.md`). Resample requires that crossing. Ξ-W2-T1 opens with the
architecture decision and this plan names the candidates rather than assuming the
deferred relay shape; see that track. Consequence to hold: if every candidate fails
verification, item 15's "one click from inside the VST" framing is what gives, not
the read-only invariant — the fallback is a bindable extension-side action.
1. **Item 15's cross-artifact seam is RESOLVED — this is no longer an unknown.** The
instrument is a read-only bank consumer by invariant (`src/shell/instrument/CLAUDE.md`),
and the one previous attempt at an instrument→extension relay (S13) closed with a
**DEGRADED** spike verdict and was deferred (`docs/TODO.md`). Resample required that
crossing. Ξ-W2-T1 ratified Decision 1 = (1b): the editor invokes the extension's bake
action directly over the VST-host bridge (`NamedCommandLookup`/`Main_OnCommandEx`), no
poller, no nonce — dissolving the S13 DEGRADED verdict rather than re-litigating it. The
read-only bank invariant held: the crossing is a bridge call, not a shell-side bank
write. See `docs/COMPLETED.md` for the full narrative.
2. **The "Γ before Ξ-W2" ordering is VIOLATED, it was never Daniel's choice, and Γ now owns
the correction.** Daniel, 2026-08-01: *"xi was started before I spun you up, we'll have to
@@ -396,6 +396,9 @@ and the loop points by name. Baking a processing chain that does not exist yet i
schedule preference; the feature is not expressible. Phase Θ landed as of Θ-W7-T1 (see
`docs/COMPLETED.md`); this gate is satisfied.
**All three waves have landed — Phase Ξ is complete.** W1 through W3 each carry their own
landed note below; see `docs/COMPLETED.md` for every track's full narrative.
---
### Ξ-W1 — Consolidated tracking, and the programmed-note model
@@ -427,8 +430,12 @@ peer of `engine/`/`map/`/`ui/` under `core/instrument/` — holding `musical_div
`StartOffset`/`EndOffset`, `NoteProgram`, `resolveNote`). **Both open questions below
are answered, for Ξ-W3-T1:** negative offsets are legal in both directions (sign
uniform, positive is later in time; only an inverted window is refused, reported via
`ResolvedNote::windowCollapsed`), and the denomination seam is confirmed — note length
stays musical-division-only, and an offset stores the denomination it was entered in.
`ResolvedNote::windowCollapsed`), and the denomination seam is confirmed as of this
landing — note length stays musical-division-only, and an offset stores the denomination
it was entered in. **Superseded by Ξ-W3-T1's landed work** (see `docs/COMPLETED.md`):
`note/CLAUDE.md`'s musical-division-only rule is amended — a note length now carries
EITHER an exact derived duration or a musical division, not division-only. The offset
denomination rule is unaffected.
---
@@ -470,63 +477,35 @@ Daniel's manual verification is still owed.
### Ξ-W3 — The capture-signal popup
**Depends on Ξ-W2 for:** the bake chain that the popup programs and its preview must not
diverge from. The acceptance criterion "preview and bake cannot diverge" requires one
render path, which W2 owns — building the preview against a second path is exactly the
defect the criterion exists to catch.
**Depends on Ξ-W2 for:** the bake chain this track was originally scoped to build a
preview against, under the acceptance criterion "preview and bake cannot diverge." **That
motivation is retired, not satisfied** — see below.
**One track.**
#### Ξ-W3-T1 — `capture-signal-popup`
**Goal.** Let the user program the capture signal — note length, offsets, velocity — and
hear it before committing the bake.
**Consolidates item 15 (the popup sub-feature).**
**Surface boundary — owns:** `core/instrument/ui/curve_popup`'s sibling — a new pure popup
geometry module for the capture-signal sheet (mirror the `curve_popup` precedent: centered
sheet, width/height clamps, title row, Close button rect, outside-sheet dismissal test) —
plus the editor's popup paint/input wiring in the deck or chrome band, and the preview
trigger's call into W2's render path. Reads Ξ-W1-T2's note-program record; does not
re-own it.
**Behavior.**
- **A popup menu programs the capture signal:** **note length**, **start and end offsets —
in ms AND in beats** — and **velocity**.
- **A preview trigger button** auditions the capture note exactly as currently programmed
— the user hears the bake before committing it — and the offline pass renders that same
programmed performance.
- **Note length is a musical-division picker** spanning 1/64th to 64/1 with dotted and
triplet multipliers.
- **Offsets are anchored** — start to note-on, end to note-off — and each is readable and
editable in **both** ms and beats, the two views of one stored value.
- **Beat-denominated values resolve against the project tempo under the cursor.** The
shell reads the tempo; the arithmetic is Ξ-W1-T2's.
- **The programmed velocity is the render velocity** — material because the velocity
transfer curves modulate amp, pitch, and filter at that velocity.
- House-consistent: drawn through the shared kit by palette role, geometry pure and
CTest-covered, no decorative animation.
**Acceptance criteria.**
- The popup exposes: note length as a musical-division picker spanning **1/64th to 64/1
with dotted and triplet multipliers**; start and end offsets, **each readable and
editable in both ms and beats**, anchored to note-on and note-off respectively; and
velocity.
- **Its preview trigger auditions the capture note exactly as programmed, and the bake
renders that same programmed performance — preview and bake cannot diverge.** Verify
structurally (one render path), not just by ear.
- **Beat-denominated values resolve against the project tempo under the cursor:** the same
programmed division yields a correspondingly different rendered duration when the tempo
at the cursor differs.
- The programmed signal persists with the instance and round-trips save/reload.
- The popup's geometry and dismissal test are pure and unit-tested; no hit-test math lands
in shell code.
**Open questions.** Both of Ξ-W1-T2's residuals (negative offsets; the note-length
denomination seam) surface here as UI consequences — if T2 answered them, this track
implements the answer; if T2 deferred either, this track is where it becomes visible and
must be closed.
**Ξ-W3-T1 has landed**`capture-signal-popup` — see `docs/COMPLETED.md` for the full
narrative, and it diverges substantially and deliberately from this section's spec. The
popup this track was scoped to build (a musical-division note-length picker, ms/beat-
editable offsets, velocity, and a preview trigger) was built (~1500 lines) and then
**abandoned unmerged** on Daniel's ruling: *"I didn't realize you had already derived a
usable window. The manual stuff for baking a specific midi length was just an idea, if we
have a smarter, fewer-clicks way of doing it, that is ideal. I just don't want to lose
anything when we bake. We can abandon the whole parameterized bake window if we can
safely derive the window in gate and trigger modes."* An audit, backed by executable
tests, established the window derives losslessly everywhere except Gate over an active
sustain loop, which has no intrinsic duration to derive. What shipped instead: the bake
window derives itself in Trigger and loop-less Gate; one control, a musical-division
**Hold** picker, covers the one irreducible case and is shown only for Gate-with-active-
loop (`bakeWindowNeedsHold`, reading the engine's own loop fold); bake velocity now reads
the instance's persisted preview velocity rather than a hard-coded value; and the
chrome-row play button stays a pure MIDI trigger — Daniel's ruling: *"play button is pure
MIDI trigger, Bake parameters are their own thing."* **This retires this section's
acceptance criterion 2 ("preview and bake cannot diverge") and its preview-trigger
behavior bullet by ruling, not by shortfall** — there is no popup and no preview trigger.
Three pre-existing truncation bugs were found and fixed along the way, and
`note/CLAUDE.md`'s musical-division-only invariant is amended (see Ξ-W1-T2 above): a note
length now carries either an exact derived duration or a musical division, not
division-only.
---
@@ -682,7 +661,7 @@ the automatable params reported."*
**The params-payload ladder — re-checked, and now stated RELATIVELY.** The old block named
v14 and v15 as absolutes. **That is no longer safe to assume**, because Ξ ran ahead of its
sequencing and this plan is not the record of what Ξ-W2-T1 actually took. On `dev` today
`kParamsPayloadVersion` is **13** (`map/component_state_io.h:154`) and Ξ-W2-T1 was specced to
`kParamsPayloadVersion` is **14** (`map/component_state_io.h:163`) and Ξ-W2-T1 was specced to
take no rung — but the plan's prediction is not evidence. So:
> **Γ owns the next three rungs above whatever `dev` carries when Γ-W1-T2 dispatches, and
@@ -692,9 +671,10 @@ take no rung — but the plan's prediction is not evidence. So:
> which the specification says it will not (`docs/product/parameter-automation.md` §6.1,
> §10). If unspent, that rung falls through to the next phase unclaimed.
>
> **On `dev` as of 2026-08-01 that resolves to v14 / v15 / v16-reserved.** If Ξ-W2-T1 landed
> a bump, every number shifts by one and **nothing else about the ownership changes** — which
> is the whole point of stating it relatively.
> **On `dev` as of 2026-08-01 that resolves to v15 / v16 / v17-reserved.** Ξ-W3-T1 landed and
> consumed a rung (v14, the bake Hold division) — not Ξ-W2-T1, which took none as specced — so
> every number shifted by one and **nothing else about the ownership changes** — which is the
> whole point of stating it relatively.
**Every other track in the phase owns no rung**: W1-T1 changes no persisted field (the payload
stores raw engine doubles, so both the taper and the new ceiling are persistence-neutral),
@@ -2769,7 +2749,7 @@ The check that nothing was dropped. Every row points at a track that exists abov
| 12 | Toolbar cleanup; full-width piano strip; tooltips | Θ-W2-T3 | `pth-w2-t3-toolbar-and-piano-strip` |
| 13 | Antialiased rendering audit for high-DPI | Θ-W6-T1 | `pth-w6-t1-legibility-and-antialiasing` |
| 14 | Trigger amp/filter fade → AHD consolidation | Θ-W3-T2 | `pth-w3-t2-staged-envelope-curves` |
| 15 | One-click in-sampler resample | Ξ-W1-T2 (note model) **+** Ξ-W2-T1 (bake chain) **+** Ξ-W3-T1 (popup) | `pxi-w1-t2-note-program-model`, `pxi-w2-t1-resample-bake-chain`, `pxi-w3-t1-capture-signal-popup` |
| 15 | One-click in-sampler resample | Ξ-W1-T2 (note model) **+** Ξ-W2-T1 (bake chain) **+** Ξ-W3-T1 (popup abandoned by ruling; window derives itself) | `pxi-w1-t2-note-program-model`, `pxi-w2-t1-resample-bake-chain`, `pxi-w3-t1-capture-signal-popup` |
| 16 | Retire the zone mapping system | Θ-W1-T1 | `pth-w1-t1-zone-retirement` |
| 17 | Consolidate provenance/usage tracking | Ξ-W1-T1 | `pxi-w1-t1-tracking-consolidation` |
@@ -2870,8 +2850,8 @@ Phase Ξ — The resample loop (W1 concurrency-safe with Θ from Θ-W
T2 note-program-model ...................... 15 (model)
W2 The bake chain [ran AHEAD of Γ; its reset list is corrected by Γ-W3-T2]
T1 resample-bake-chain ..................... 15 (chain)
W3 The capture-signal popup
T1 capture-signal-popup .................... 15 (popup)
W3 The capture-signal popup [popup abandoned by ruling; bake window derives itself]
T1 capture-signal-popup .................... 15 (popup abandoned; window derives)
Phase Γ — The instrument's control surface (none of the seventeen; ends with VST3 params)
W1 Foundations [5 tracks, disjoint by surface]