SSPACEAGEDOCUMENTATION/
Product and Design

Machine-Load-Sensitive Wall-Clock Tests

Updated Aug 29, 2026   |   394.1 KB   |   docs/Project_Gotchas_Checklist.md

2026-08-29 - Gotcha: Audio Tests Must Honor Prepared Block Size

  • Never pass processBlock more samples than the maximum block size supplied to prepareToPlay. Oversized callbacks can corrupt fixed DSP work storage and falsely implicate teardown or unrelated audio code.
  • To test changing callback sizes, prepare for the largest size first, then submit only callbacks at or below that maximum.

2026-08-24 - Gotcha: Performance Is Part Of Feature Correctness

  • Predict how new work scales with voices, Pads, lanes, channels, buses, effects, project length, and display size before implementation.
  • Define the disabled, silent, hidden, stationary, and unused cost. Dormant features must approach zero avoidable work.
  • Keep allocation, locks, file/device discovery, string work, and unbounded scans off the realtime audio thread.
  • Prepare constants and routing at the widest safe cadence: asset load, block, control slice, or UI refresh instead of per sample.
  • Measure Release builds with named workloads and repeat runs; reject theoretical optimizations that regress realistic timing.
  • Prove sound, tails, automation, finite output, routing, and wake/sleep behavior alongside CPU improvement.
  • Update Performance_Optimization_Rule_And_Impact_Report.md for meaningful new burdens, accepted optimizations, and rejected experiments.

2026-08-12 - Gotcha: Same-Sample Polyphony Has No Meaningful Age Order

  • Use an explicit start serial to break ties among notes created in one sample; floating age cannot identify the oldest note in a burst.
  • Never preserve delayed or never-rendered voices in a de-click bank. They emitted no signal and can exhaust transition capacity without preventing a click.
  • Measure victim importance after lane mute, solo, gain, pan, and strip processing. Pre-routing amplitude can protect an inaudible voice over audible music.
  • Resource-backed voices may need to surrender scarce render instances before their tails finish. A captured-sample retirement mode keeps the transition audible without starving the incoming note.
  • Retirement capacity must be fixed before playback. No allocation, parameter lookup, string construction, locking, or state sanitation belongs in note startup or voice stealing.
  • Panic, hard stop, project reset, and source reset must clear both the principal voice pool and all retirement tails.
  • A test that counts 96 active structures is insufficient: render the overlap, inspect both channels, enforce a peak bound, verify the exact fade deadline, and prove callback timing.

A moving synth control is not proof of working DSP

  • Parameter registration, attachment, visible recall, and Undo are necessary but do not prove audible influence.
  • Every engine family needs measured sound-difference coverage in addition to UI contracts.
  • Test Filter Off separately: Cutoff and Resonance must be genuinely inert while bypassed, then clearly effective in each active topology.

Chord-safe defaults and experimental range are different responsibilities

  • Initial unsaved Instruments must use integer/harmonic tuning, zero inharmonicity, and zero pitch spread unless the engine's customer-facing purpose explicitly says otherwise.
  • Preserve non-integer FM, X-Mod, spectral stretch, metallic spread, and grain detuning as advanced tools, but identify their tonal consequence and keep their defaults neutral.
  • A parameter that no longer appears in the UI and is not consumed by DSP is vestigial state. Remove it rather than carrying secret patch behavior.
  • Effects remain Mixer-owned. Native Instrument patches must not regain hidden local chorus, reverb, delay, or send values.

2026-08-11 - Redshift Control Range And Ownership

  • A parameter must have one audible DSP owner. Search both the engine renderer and shared post-engine path before adding saturation, filtering, gating, degradation, or effects.
  • A normalized amount must approach zero continuously. Do not hide a fixed offset behind value > epsilon; it creates a jump at the bottom of the knob.
  • Oscillator-ratio defaults and double-click resets must be chord-safe unless the control is explicitly an inharmonic feature. Redshift Oscillator 2 resets to 1.000x.
  • Match mouse travel to parameter role: precision for pitch/frequency/time, sweep for broad timbral amounts, and stepped whole-number display for integer controls.
  • Tooltips must state when a control is intentionally inert, contextual, tempo-synced, or mode-dependent.
  • Factory preset identity includes implicit defaults. Any deliberate default change requires a full 619-preset safety pass and synchronized runtime, inventory, hygiene, provenance, and packaging fingerprints.

Human review: Sweep every Redshift knob slowly and quickly, use Ctrl-modified precision, type exact values, double-click Oscillator 2 Ratio, and confirm no range feels dead, abrupt, or needlessly laborious.

2026-08-11 - Gotcha: Packaging Constants Must Follow Runtime Evidence

  • Treat the complete runtime inventory sweep as the authority for generated preset identity; do not let a stale documentation constant veto newer live evidence.
  • Update runtime expectation, machine-readable inventory, hygiene, provenance, and current release documentation as one transaction.
  • Preserve older dated receipts as history and add an explicit reconciliation note instead of silently rewriting them.
  • Canonical convergence must name customer-facing generator workflows such as Library Starting Points; an optional focused test is too easy to omit.

2026-08-10 - Gotcha: A Visible Parameter Is Not A Feature Until Audio Uses It

  • Every customer-visible synth control needs five linked pieces: registered parameter, UI attachment, audio/DSP consumer, persistence/Undo, and focused regression proof.
  • Patch parameter inventories must be tested against the actual registered parameter tree; a string in a save list cannot substitute for a real control.
  • Partial or older patch files must recall omitted known controls to declared defaults. Never inherit the previously loaded sound by accident.
  • Validate every supplied patch value before mutating any live parameter or source asset. One malformed value must leave the current sound and patch name untouched.
  • For envelope-shape controls, test audible energy in a bounded time window and voice retirement, not merely slider movement or serialization.

2026-08-08 - Gotcha: Undoing Automation Data Must Also Undo The Sound

  • Restoring serialized Automation without refreshing live controller state leaves the graph and playback disagreeing.
  • Capture affected Automation rows on both sides of a history restore; refresh their union so deleted rows reset/fall back and restored rows reapply.
  • Resolve Shared Pattern, Lane Local, and Clip Local precedence through one common path for internal instruments and hardware routes.
  • Preserve delete receipts after rebuilding the row list; a refresh must not erase proof of the action the user just took.
  • Keep operational labels, center cues, duplicate counts, and curve handles at the Automation readability floor.
  • Cap oversized callouts to the active display and expose honest scrollbars rather than placing controls beyond reach.

2026-08-08 - Gotcha: Deleting Automation Is Not Always A Neutral Reset

  • Automation playback precedence is Shared Pattern, then Lane Local, then Clip Local.
  • Removing Clip Local or Lane Local data must immediately reveal a surviving lower-priority value on the affected route.
  • Send a neutral controller reset only when no effective owner remains.
  • Apply the same surviving value to internal playback and external hardware; split behavior makes the editor lie about the audible state.
  • Keep focused regression proof for Clip Local fallback, Lane Local fallback, final-owner neutralization, channel remapping, and cross-lane isolation.

2026-08-01 - Gotcha: Async UI And Owner-Backed Workers Must Outlive Nothing

  • A dialog completion may run after its editor has closed; every asynchronous chooser, popup, confirmation, timer, and callout must resolve safe ownership before touching state.
  • A worker member that stores an owner reference is dangerous even when its destructor stops the thread: reverse member destruction may remove the owner's later-declared state first.
  • Stop external producers, then join owner-backed workers explicitly in the owner's destructor before ordinary member teardown begins.
  • Compilation plus focused timing/health gates are required; visual inspection cannot prove lifetime safety.

2026-07-30 - Gotcha: Background Archive Work Still Needs A Message-Thread Snapshot

  • Do not call live processor serialization from the archive worker.
  • Capture an immutable export plan and staged project state on the message thread, then give only files and plain metadata to the worker.
  • Import may inspect and extract on a worker, but the final project load and editor refresh belong on the message thread.
  • Keep the destination transactional: a failed or interrupted worker must not replace an existing valid archive.
  • Do not advertise a Cancel button until ZIP creation/extraction can stop cooperatively; chooser cancellation and mid-operation cancellation are different promises.

2026-07-30 - Gotcha: Asset Tests Must Not Depend On Shane's Private Library

  • Generate small first-principles fixtures for automated sampler tests whenever the format allows it.
  • A passing test tied to a personal SoundFont, sample drive, or cloud path is not reproducible release evidence.
  • Asset-backed proof must cover more than parsing: audible finite output, note release, project round trip, missing-asset reporting, relink, and audible recovery belong to one contract.
  • Folder relink is exact-match dependency repair, not a search-and-guess feature. Repair only when one filename/package match exists; leave duplicate names unresolved for the user.
  • A Quasar package can be discovered both by its directory and by its manifest.json. Deduplicate those paths before ambiguity counting or every valid package appears twice.
  • Treat a missing-asset chooser result as a receipt for one exact type/pad/slot/original-path/file-name tuple. Reject stale receipts after another repair changes the missing list.
  • Relink changes the live project dependency paths. Tell the user to save, and prove the repaired paths survive save/reopen.
  • A generated standards-valid SoundFont fixture now covers deterministic format and recovery behavior; keep large-library musical and performance certification open until human QA is recorded.

2026-08-04 - Gotcha: A One-Preset SoundFont Smoke Test Does Not Prove Workstation Behavior

  • SoundFont validation must cover preset enumeration, actual program-dependent audio, negative and positive out-of-range clamping, selected-preset persistence, and selected-preset recovery after relocation.
  • A control existing in the editor is not proof that it affects audio. Isolate cutoff, attack, decay, sustain, and release so each one changes an appropriate production-render metric while the other relevant variables remain fixed.
  • Recovery is not proven by merely hearing something after relink. Compare the repaired render with the saved pre-move render, including level windows and voice retirement.
  • Use a generated multi-preset fixture for reproducible software truth, but never relabel it as proof of multi-gigabyte vendor-bank performance or musical quality.

2026-07-30 - Gotcha: A Lane Instrument Change Must Not Rewrite The Score

  • For non-drum Arrangement playback, the lane owns the Instrument and Mixer route; the clip/pattern owns the musical data.
  • Do not clone a shared pattern or rewrite note/chord source-slot metadata merely because a lane changes Instrument.
  • Playback already substitutes the lane Instrument for Arrangement notes and Chord Engine events.
  • Keep explicit pattern retargeting limited to standalone contexts where there is no owning Arrangement lane.
  • This protects shared composition, avoids pattern-pool churn, and makes orchestration changes reversible without hidden MIDI mutations.

2026-07-30 - Gotcha: Representative Persistence Needs Explicit-Length Coverage

  • Saving notes is not enough to preserve a clip whose intentional pattern boundary extends beyond its final note.
  • Serialize explicit pattern lengths and do not shrink an existing longer boundary when replacing note or chord arrays.
  • Persist release velocity and Arrangement loop state alongside the more obvious note/clip fields.
  • Keep the representative fixture musically broad enough to cover Section form, lane Mixer state, and detailed Chord Engine strum/arp settings.
  • Project restore must not create user-facing Undo history.

2026-07-30 - Gotcha: A Performance Result Is Meaningless Without Build And Voice Cadence

  • Measure product performance in Release, never from an unoptimized or unknown CMake build type.
  • Preallocate benchmark buffers, warm up the engine, use multiple runs, and report the median.
  • State pad/voice count, trigger cadence, buffer size, sample rate, and enabled effects with every result.
  • Rapid retriggering can accumulate many decaying voices; do not describe that as a fixed voice-count test.
  • Keep realistic real-time gates separate from deliberate voice-flood stress tests and retain both as optimization evidence.
  • I03-A is the realistic musical gate: eight pitched voices on one private lane-owned Instrument at 48 kHz / 512 samples, five measured runs, median timing, finite audio, voice retirement, and a 70% real-time ceiling.

2026-07-30 - Gotcha: A Solo Instrument Benchmark Does Not Certify An Arrangement

  • Always keep a whole-song performance gate beside isolated engine benchmarks. SpaceAge's I03-B scene includes synthesized drums, five lane-owned tonal Instruments, independent Mixer channels, automation, shared returns, loop timing, finite output, and voice retirement.
  • Explicitly set every effect enable state in performance fixtures. Factory defaults can make a test labeled dry process an unnoticed Reverb or Delay return and corrupt the diagnosis.
  • Sweep lane count when a whole-song test fails. The jump from one to two tonal lanes isolated Liftoff; adding the remaining engines barely changed the failed result.
  • Do not synthesize a wavetable by summing dozens of harmonics inside every realtime sample. Prepare band-limited mip levels in prepareToPlay() or asset import, then use bounded interpolation in the audio callback.
  • Keep private Instrument source slots and Mixer channels distinct in tests. Engine parameters target the private source; gain, pan, automation destinations, and effect sends target the lane's assigned Mixer channel.
  • A shared return with per-channel history must not scan and fully process every possible Mixer channel for every sample. Build a bounded active-channel list once per block from nonzero sends, live tails, and special inputs, while preserving enough wake time for the longest supported delay tap.
  • A benchmark threshold is a regression boundary, not a promise about every computer. Preserve the sample rate, block size, musical workload, median-run policy, and machine context with every reported percentage.
  • A test that sends pitched notes through the visible Drum Pad MIDI map is not an Instrument benchmark. Trigger the explicit private source slot or every engine case may silently measure the same Drum source.
  • Private Instrument sources and Mixer channels are separate domains. Do not fail an Instrument benchmark because a private source intentionally lacks Pad/Mixer-owned controls such as channel gain.

2026-07-30 - Gotcha: An Enabled Return Can Still Be Silent

  • Do not certify a shared effect merely because its enable flag is true or a wet Arrangement remains audible. The dry path can conceal a disconnected return.
  • Test Halostar, Reverb, and EchoRay independently against the same deterministic dry source. Require finite output, a measurable wet difference, a post-note tail, and a response to the effect's defining controls.
  • Delay-line activity lifetime must cover the longest supported synchronized tap. EchoRay previously decayed its wake marker per sample so quickly that a short note could put the delay to sleep before a quarter-note or longer repeat emerged.
  • Keep the realistic whole-song CPU gate beside the isolated return gate. Correct tails consume real processing time, and the latest full wet workload is close enough to its ceiling that target-machine profiling remains mandatory.

2026-07-30 - Gotcha: Exact UI Copy Must Not Masquerade As Runtime Health

  • Safety and readiness tests should assert typed state: locks, confirmations, non-sending actions, receipt persistence, routes, and launch boundaries.
  • Longer prose and button explanations may change as the workflow improves; test those separately as editorial contracts.
  • A harmless wording update must not mark hardware routing, MIDI readiness, or export behavior broken.

2026-07-28 - Gotcha: Last Recorded Proof Must Preserve Source Identity

  • Carry MidiInputSourceSession through asynchronous note, drum-step, and expression recording queues.
  • Do not reconstruct the source later from global state; the device slot/generation can change between live receipt and queued commit.
  • Last-recorded proof should distinguish host/plugin MIDI from physical direct input slots so two-controller and DIN-versus-USB problems can be debugged from the Health panel.
  • Keep this as diagnostic metadata unless deliberately changing routing semantics.

2026-07-28 - Gotcha: Recording Proof Must Stay Above The Fold

  • MIDI Health has a fixed visible summary budget. If recording trust rows drift too low, the model/report can be correct while the musician still cannot see whether the take was captured correctly.
  • Keep LIVE INPUT, REC OWNER, DEST PROOF, REC PROOF, LAST REC, REC DATA, REC BOUNDARY, REC TRUST, TAKE TRUST, TAKE ACTION, and TAKE PROOF near the top of the Health popup.
  • If new Health rows are added above them, verify that the post-take trust block remains visible before release.

2026-07-28 - Gotcha: Last Recorded Proof Must Not Mix Stale Event Types

  • When recording starts, clear last-recorded note, drum, and expression fields together.
  • When a note commits, clear stale drum/expression fields; when a drum hit commits, clear stale note/expression fields; when expression commits, clear stale note/drum fields.
  • MIDI Health liveMidiLastRecordedEventProofLabel should describe the latest captured event type only.
  • Keep this diagnostic separate from recording timing math: the proof label reports what landed, but the timing code remains the source of truth for placement.

2026-07-28 - Gotcha: Never Let A MIDI Percentage Become Launch Permission

  • Keep MidiProjectHealthSummary::midiOneCompletionBoundarySummary() alive anywhere MIDI readiness is shown or copied.
  • The boundary must continue to separate software/backend closeout, human hardware/launch proof, and future roadmap exclusions such as MPE/MIDI 2.0.
  • Do not describe MIDI 1.0 as public-launch complete merely because a backend percentage looks high; saved PASS receipts and hardware proof still matter.
  • Any future marketing/support copy helper should consume this model-owned boundary instead of improvising readiness language in the UI.

2026-07-28 - Gotcha: Recording Take Fix-Next Needs A Fallback Route

  • Keep MIDI Health FIX NEXT/RETAKE navigation robust even if action-surface metadata drifts.
  • The backend should serialize doorwayId=midiExpression, but the UI should also map source=recordingTake to the Automation/Retake doorway.
  • This fallback must remain navigation-only: opening the surface does not send MIDI, change routing, change project data, or repair the take by itself.

2026-07-28 - Gotcha: Dropped Recording Events Must Own The Cockpit

  • If runtimeNoteRecordingDroppedNoteCount or runtimeExpressionRecordingDroppedEventCount is non-zero, MIDI Health cockpit severity should be RECORDING.
  • The next action button should be RETAKE, and the action surface should identify recordingTake as its source.
  • Do not allow latency, launch proof, hardware proof, or generic closeout wording to visually outrank a take that dropped events.
  • The action surface should carry recordingTakeReviewCopyBlock() so the UI, report, and copied proof text share one model-owned truth.

2026-07-28 - Gotcha: Recording Trust Must Travel In Existing Reports

  • Keep the full recordingTakeReviewCopyBlock() included in MIDI Health next steps and the full MIDI Health report.
  • COPY STEPS and COPY REPORT should carry recording trust before a dedicated button is considered.
  • If a future COPY TAKE button is added, it should consume the same model-owned block rather than duplicating the wording.

2026-07-28 - Gotcha: Recording Takes Need Explicit Trust Boundaries

  • Keep recording ownership, capture policy, target boundary, queue trust, take reliability, next action, evidence, and live-timing boundary bundled in recordingTakeReviewCopyBlock().
  • Do not let UI panels invent separate "take clean" language from partial counters.
  • Dropped note/expression events must mark the pass as untrusted until retaken or repaired.
  • Timing-suspect takes are different from dropped-event takes: they may contain all events, but the user should fix buffer/queue/routing ambiguity before judging groove.
  • Future Arrangement/Piano Roll record surfaces should consume this model-owned block or a derivative card instead of creating their own recording trust rules.

2026-07-28 - Gotcha: Preserve The COPY CLAIM Door

  • Keep MIDI Health COPY CLAIM wired to hardwareProofReviewCopyBlock().
  • Do not replace it with the full COPY REPORT; the claim boundary needs a focused copy doorway for support, tester, and marketing review.
  • The button must remain a cold clipboard action: no MIDI send, no routing change, no project mutation, no external hardware change.

2026-07-28 - Gotcha: Public MIDI Claims Need Real Hardware/File/Host Proof

  • Do not collapse hardwareProofReviewCopyBlock() into launch proof.
  • Launch proof can pass while public wording remains limited by missing real-device/file/host receipts.
  • Any button or report that summarizes "MIDI 1.0 ready" must include safe public claim status, MIDI 1.0 ship state, hardware proof checklist, first needed hardware proof, run order, and the no-send safety boundary.
  • If future marketing/export/support surfaces quote MIDI readiness, route them through the tested model-owned copy block instead of improvising wording in the UI.

2026-07-28 - Gotcha: UI Panels Should Not Compose Proof Copy

  • Keep next-proof copy text owned by MidiProjectHealthSummary::midiLaunchValidationNextProofReviewCardCopyBlock().
  • UI buttons such as COPY PROOF should consume the model-owned block rather than assembling receipt, queue, and safety text themselves.
  • If a future panel needs a similar proof handoff, add a tested model method first and keep the UI as a display/copy doorway.

2026-07-28 - Gotcha: Launch Proof Needs A Small Copy Door Too

  • Keep COPY PROOF separate from COPY LAUNCH.
  • COPY LAUNCH is the whole run order; COPY PROOF is the next proof card only.
  • Both buttons are clipboard-only safety surfaces and must continue to send no MIDI, change no routing, change no project data, and touch no external hardware.

2026-07-28 - Gotcha: QA Action Surfaces Need Human Cards

  • Do not rely only on raw summary() key/value strings for launch proof or QA receipt actions.
  • Keep reviewCardText available on MidiProtocolQaReceiptActionState::toVar() so future UI/copy paths can show button, state, receipt key, proof bucket, proof artifact, PASS evidence, script, backend gate, and safety boundary in readable form.
  • When a health/cockpit/action surface manually copies receipt action fields, copy reviewCardText too; otherwise tests may pass at the receipt layer while the user-facing MIDI Health doorway loses the readable card.
  • Keep MIDI Health next steps and full reports printing the labeled review-card block; copied support text should not require digging through serialized properties.
  • If a future UI uses the raw summary for diagnostics, pair it with the review card for humans.

2026-07-28 - Gotcha: Launch Proof Needs A Focused Copy Door

  • Do not make COPY REPORT the only way to retrieve launch-validation proof instructions.
  • Keep the MIDI Health COPY LAUNCH doorway wired to midiLaunchValidationRunOrderCopyBlock().
  • If MIDI Health title-row buttons are redesigned, preserve one focused copy action for the launch-proof ritual instead of burying it inside the full report.

2026-07-28 - Gotcha: Launch Run Order Needs A Copy Block

  • Do not make testers assemble MIDI launch proof from separate summary, matrix, queue, and next-receipt fields.
  • Keep midiLaunchValidationRunOrderCopyBlock and cockpit runOrderCopyBlock wired anywhere MIDI Health is exported, copied, or inspected.
  • If launch proof buckets change, the copy block must still include ordered rows, receipt keys, proof requirements, PASS evidence, script cues, remaining queue, action queue, and next receipt summary.

2026-07-28 - Gotcha: Next Steps Must Not Hide The Launch Run Order

  • The MIDI Health next-steps report should lead with a practical run order, not just the remaining receipt queue.
  • Keep tests requiring MIDI launch validation run order, the first proof bucket, receipt key, and Test script: in next-steps output.
  • The remaining queue can stay as supporting detail, but it should not be the only proof-navigation contract.

2026-07-28 - Gotcha: Launch Proof Needs A Run Order, Not Just Rows

  • Do not reduce launch validation back to an unordered receipt table.
  • Keep midiLaunchValidationRunOrderSummary and midiLaunchValidationRunOrderLines wired to protocol coverage, MIDI Health, cockpit cards, copied reports, and visible health UI.
  • If proof buckets are added, make sure their order, proof artifact, needs summary, minimum PASS evidence, and script cue still read like a practical test session.

2026-07-28 - Gotcha: The Receipt Dialog Must Teach PASS Standards

  • The evidence-entry dialog is the moment where weak QA can accidentally become a saved PASS.
  • Keep launch-proof instructions tied to MidiProtocolQaReceiptActionState::fromReceipt() so proof artifact, proof needs, script cue, and minimum PASS evidence stay model-owned.
  • Do not let future receipt dialogs invent separate proof wording that can drift from MIDI Health or copied reports.

2026-07-28 - Gotcha: Visible Proof Cards Need The Same Script As Reports

  • Do not let MIDI Health show a proof card that is less actionable than the copied report.
  • Keep the launch proofScriptCue visible in the PROOF CARD row and reachable from the FIX NEXT tooltip path.
  • If the script gets too long for a compact row, shorten the displayed form but preserve the full cue in tooltip/copy/report surfaces.

2026-07-28 - Gotcha: The Next Action Summary Must Carry The Script

  • Do not let the compact MIDI launch next-action summary drift behind the deeper receipt/proof-card objects.
  • Keep proofScriptCue visible in midiLaunchValidationNextActionSummary so copied health reports remain actionable without requiring the tester to dig into nested data.
  • If a future launch-proof UI summarizes proof buckets, include the exact test script or an equivalent single-click test plan.

2026-07-28 - Gotcha: Launch Receipts Need Test Script Cues

  • A launch proof bucket should name both the proof type and the practical test script.
  • Keep proofScriptCue attached to matrix rows, remaining checklist rows, action queues, next-receipt objects, cockpit proof cards, and copied/plain-text reports.
  • Do not let a future UI show only abstract proof artifacts; testers need an action they can perform and document.

2026-07-28 - Gotcha: Do Not Shorten Lane Local To Lane In Owner Copy

  • Lane Local is an ownership model, not just a lane row.
  • Active Automation UI, health reports, QA receipts, and closeout copy should name all three owners as Shared PTN, Clip Local, and Lane Local.
  • A plain Lane label is acceptable only where the UI is talking about arranging tracks/lanes generally, not Automation ownership.

2026-07-28 - Gotcha: Launch Receipts Must Say What Kind Of Proof They Need

  • Do not show only requiresHardware, requiresRealFiles, and requiresLivePerformance booleans in launch-proof surfaces.
  • Keep the human-readable proofNeedsSummary attached to matrix rows, action queues, cockpit cards, reports, and any future launch-readiness UI.
  • Software-only proof is still proof; label it as software/customer review so absence of hardware requirements does not look like missing data.

2026-07-28 - Gotcha: Automation Owner Names Must Match The UI

  • Active product/report wording should use Shared PTN, Clip Local, and Lane Local.
  • Do not reintroduce stale developer-facing labels such as Shared PTN Data, Clip Data, or Lane Data in visible MIDI Health, Automation, shortcut, or closeout reports.
  • If a new Automation surface needs shorter labels, keep the short label visibly mapped to these same three owner concepts.

2026-07-28 - Gotcha: Run The Fresh MIDI Test Binary

  • tools/codex-build-selftest.ps1 builds SampleSquadAudioTest into outputs/build-local-midi-closeout, not the older project-local build-codex tree.
  • Before trusting a focused MIDI gate, resolve the newest SampleSquadAudioTest.exe from the build wrapper's output directory and check its timestamp.
  • A stale test binary can mimic a product regression: missing checkpoint text, old helper wording, unexpected timeouts, and false failures.

2026-07-26 - Gotcha: Automation Deletes Must Reset Hardware

  • Internal reset is not enough once a lane can route to external MIDI hardware.
  • When removing an automation lane/event/range, send the neutral MIDI value to only the affected hardware lanes: shared pattern owners, clip-local owners, or lane-local owners.
  • Keep neutral reset rules centralized so pan, volume, expression, sustain, pitch bend, pressure, and generic CC resets cannot drift apart between internal synths and hardware output.

2026-07-26 - Gotcha: Primary Doors Should Not Be Too Clever

  • AUTO is acceptable for tiny clip/lane evidence badges, but a primary toolbar button should say AUTOMATION unless horizontal space truly forbids it.
  • When a popup is launched from Arrangement, anchor it to the visible Arrangement control, not to a hidden or unrelated Piano Roll/Settings component.
  • Keep compact labels and full feature names intentionally separated: evidence can abbreviate; discovery should teach.

2026-07-26 - Gotcha: Automation Needs Breadcrumbs, Not Just Doorways

  • A working Automation editor is still hard to trust if existing rows are not visible from the musical context.
  • Keep Arrangement clip/lane badges, toolbar labels, selected readouts, and tooltips synchronized with the same target-resolution logic.
  • Do not add a second automation-target resolver just to paint labels; that creates the exact "looks selected but edits elsewhere" bug class we are trying to avoid.

2026-07-26 - Gotcha: Settings Panels Should Not Grow Only Downward

  • Once Settings contains MIDI, sync, SysEx, automation, tooltips, metronome, and display controls, a single-column popup will eventually clip or crowd on smaller screens.
  • Prefer columns, cards, or subpages before adding more rows.
  • Keep MIDI safety copy short in the visible panel; tooltips/manual text can hold the full legalistic explanation.

2026-07-26 - Gotcha: Dense Safety Copy Can Create UI Bugs

  • Ownership warnings are necessary in MIDI automation, but long visible copy can create clipping, overlap, and hesitation.
  • Keep the popup's main guidance short; move extended explanations into docs/tooltips/manual material.
  • When tightening text, also check the geometry constants. A visually cleaner popup can still be wrong if the old click/hit-test regions remain.

2026-07-26 - Gotcha: Automation Popup Geometry Must Share Constants

  • Do not tune automation row drawing, mouse hit-testing, and visible-row calculations independently.
  • Keep row height, header height, and reserved action-panel space synchronized through shared constants/helpers where practical.
  • A UI can look fixed while clicks still land on the old geometry; that mismatch is exactly the kind of small betrayal that makes automation feel unreliable.

2026-07-26 - Gotcha: Stale Direct Device Generations Must Be Silent

  • Direct MIDI queue entries carry a device-slot generation. If the slot is invalidated or reused before the audio block drains the queue, those queued entries must be dropped.
  • Do not let stale-generation note-on/note-off messages create live voices, write Arrangement recording notes, close another device's note, or silently retarget to a replacement handle.
  • Keep the regression that proves stale direct messages increment the readiness dropped-message counter while leaving the armed clip and live voice table untouched.

2026-07-26 - Gotcha: Test Gates Share A Build Directory

  • The focused MIDI gates currently build into the same local CMake output unless SPACEAGE_BUILD_DIR is overridden.
  • Parallel builds can collide on .obj files and report misleading "Permission denied" compiler failures.
  • tools/run_tests.ps1 now uses a named build mutex around the CMake build phase. Keep that guard unless/until every gate gets an isolated build directory.

2026-07-25 - Gotcha: Transport Start Has One Authority

  • Every user-facing Play path must resolve playback start through the same helper.
  • Arrangement loop selection wins first, then explicit "start from arrangement start", then selected Arrangement playhead, then current position.
  • Do not add a new Play/spacebar/start button path that calls setSequencerRunning() directly unless it intentionally bypasses Arrangement context.

2026-07-25 - Gotcha: Automation Drawing Must Audition Live

  • Storing MIDI expression events is not enough for a working automation editor.
  • When the user draws, nudges, drags, or adds an editable expression point, immediately apply the corresponding MIDI message to internal state so pan/volume/expression/etc. respond while playback continues.
  • Scheduled playback still owns timeline-accurate automation; live auditioning is the responsiveness layer on top.

2026-07-25 - Gotcha: Lane-Owned Synth Editors Must Lock To Lane Instrument Slots

  • The Synth Editor is shared by visible Drum Pads and private lane-owned Instruments.
  • If a source lane is present, re-resolve the lane's current instrument slot before attaching controls.
  • Never let selectedPad or a stale drawer target decide where Redshift/engine rotary edits go when the drawer was opened from an Instrument lane.

2026-07-25 - Gotcha: Resource Cleanup Must Follow The Active Owner

  • Shared native preset loaders must not call clearSample(pad), clearBonusShot(pad, shot), or clearSoundFontForPad(pad) directly when their target comes from getActiveSynthEditPad().
  • Use clearSampleForActiveTarget(), clearBonusShotForActiveTarget(), and clearSoundFontForActiveTarget() so visible Drum Pads stay Pad-owned while private non-drum lane Instruments stay Instrument-owned.
  • Leave true pad-bank generators alone unless they become shared editor paths; those routines are intentionally Pad workflows.

2026-07-25 - Gotcha: Shared Preset Loaders Need Active-Target Helpers

  • If a Synth Editor function starts with const int pad = getActiveSynthEditPad();, assume it might be editing a lane-owned Instrument, not just a Drum Pad.
  • Prefer owner-aware helpers such as useBuiltInSynthForActiveTarget() and setActiveSynthEditLabel() for shared paths.
  • Pad-only calls are acceptable in Pad-page controls, Drum Composer controls, and batch Pad kit setup, but not in shared engine/preset/source-loader paths.

2026-07-25 - Gotcha: Clear/Load Buttons Can Reintroduce Pad Thinking

  • Shared Synth Editor buttons are dangerous because they can operate on visible Pads or private lane-owned Instruments.
  • If an action clears, loads, resets, imports, or swaps a resource, first ask whether the active target is a visible Pad or a lane-owned Instrument.
  • Use Instrument-slot wrappers for private lane Instruments whenever available. Direct Pad helpers are only acceptable for proven Pad-only flows.

2026-07-25 - Gotcha: Private Instrument Labels Must Not Fall Back To Pads

  • Do not restore getInstrumentSlotLabel() fallback to getPadLabel() for private slots.
  • That fallback makes old projects look nicer, but it also lets a visible Pad name become a non-drum lane Instrument name, which revives the deprecated Pad-proxy workflow.
  • If a private Instrument has no explicit Instrument label, use an Instrument/engine fallback instead.

2026-07-25 - Gotcha: Menus Teach Architecture

  • Do not let transitional storage leak through menu language. A label like AVAILABLE LANE INSTRUMENT may be technically accurate, but it turns lane ownership into a confusing object class.
  • Arrangement lane choices should say Instrument while the surrounding lane badge/menu context teaches ownership.
  • Keep visible Pad labels and private Instrument labels separate. Writing a lane-owned Instrument name into padLabels can silently resurrect the deprecated pad-proxy workflow.
  • Keep Instrument Bay reserved for the future browser/page where Instruments are chosen, not for the low-level storage object underneath a lane.

2026-07-25 - Gotcha: Preset Loading Is Also Naming

  • Do not treat preset application as only DSP parameter mutation. It also updates the visible identity of the thing being edited.
  • Shared Synth Editor preset paths should use setActiveSynthEditLabel() unless they are proven Pad-only.
  • Patch display fallback should prefer Instrument-slot labels for private/lane-owned Instruments before consulting Pad labels. Otherwise a lane can play the right synth while displaying the wrong old Pad-era identity.

2026-07-25 - Gotcha: Saved Patch Names Are Not Always Pad Labels

  • Do not call setPadLabel() directly from shared Synth Editor save/load paths unless the path is proven Pad-only.
  • Use setActiveSynthEditLabel() for active Synth Editor targets. It writes visible Pad labels for Drum Pad contexts and private Instrument labels for non-drum lane contexts.
  • Closeout now checks that saved patch name refresh on a lane-owned Instrument does not move visible Pad selection or leak Pad wording.

2026-07-25 - Gotcha: Name Helpers Can Leak Old Architecture

  • Do not use getPadLabel() as the first fallback for private non-drum Instrument slots.
  • Patch save names, status messages, and drawer labels should resolve through the owning Arrangement lane first. If no lane owns the slot, use Instrument-slot naming or an engine-based Instrument fallback.
  • Closeout now expands laneOwnedDrawer to check active Synth Editor base names for Pad/private-slot/Bay wording leaks.

2026-07-25 - Gotcha: Copying Must Not De-Contextualize The Synth Editor

  • Shared Synth Editor controls are reused by visible Drum Pads and lane-owned Instruments.
  • After a copy/paste action, always reapply refreshSynthDrawerContextText() instead of hard-coding generic labels. Otherwise a lane-owned Instrument drawer can drift back into Pad-era copy.
  • Closeout now checks that lane-owned drawers keep COPY INSTRUMENT / PASTE INSTRUMENT and Pad-free tooltips before and after copy.

2026-07-25 - Gotcha: Variants Are Instrument Copies, Not Mixer Copies

  • VARIANT on a non-drum lane should create an independent Instrument identity without rewriting its notes/chords; Arrangement playback resolves those through the lane owner.
  • Do not copy mixer-owned values such as gain, pan, sends, or strip processing when making a variant. Those belong to the lane's mixer channel, not the patch identity.
  • Closeout now covers this with laneInstrumentVariantWorkflow=1.

2026-07-25 - Gotcha: Fresh Instrument Replacement Must Not Replace The Lane

  • NEW INSTRUMENT on a non-drum Arrangement lane means replace the engine/preset living in that lane's private Instrument slot.
  • It must not allocate a visible Pad, change the lane's mixer channel, move visible Pad selection, or add a starter clip when the lane already has real clips.
  • Closeout now covers this with freshLaneInstrumentReplacement=1; keep this guard alive until Instrument Bay storage is fully independent.

2026-07-25 - Gotcha: Same Note Does Not Mean Same Controller

  • Do not key live-note ownership only by MIDI channel and note number when direct physical inputs are involved.
  • Two controllers can legitimately play the same pitch at the same time; a note-off from one source must not release the other source's held note.
  • Timing coverage now checks this for live monitoring as well as recording/sustain/channel-mode behavior.

2026-07-25 - Gotcha: MIDI Recording Already Monitors The Voice

  • Do not let recorded note events fall through into the ordinary live-monitor path after handleMidiRecordingEvent() handles them.
  • The recording helper already starts the monitored voice and owns the matching note-off/release behavior. Falling through creates duplicate live voices and breaks direct-device disconnect/reuse cleanup.
  • Focused timing coverage now checks that recording an armed Arrangement lane produces exactly one live monitored voice.

2026-07-25 - Gotcha: Import Review Text Teaches The Lane Model

  • MIDI import review is often a user's first contact with existing songs and hardware data.
  • In visible import hints and tooltips, say Instrument, Instrument destination, and MIDI Channel; avoid lane-owned, private, slot, or Lane Instrument unless documenting internals.
  • Do not run build-producing test gates in parallel on Windows when they may relink the same executable. Use parallel no-build gates or run rebuild gates solo.

2026-07-25 - Gotcha: Available Means Musical, Not Transitional

  • Do not use transitional labels like LANE-READY INSTRUMENT in visible chooser rows.
  • Use AVAILABLE INSTRUMENT ## for unused choices and NO EMPTY INSTRUMENTS AVAILABLE for exhaustion states.
  • Keep user-facing copy centered on the musical object. Internal bridge-phase slots can stay internal.

2026-07-25 - Gotcha: Lane-Owned Editor Titles Must Not Say Pad

  • Non-drum Arrangement lane Instruments may still use bridge-phase slot indexes internally, but customer-facing Synth Editor titles must identify the lane Instrument, not a Pad.
  • Keep regression coverage for this. The lane-owned drawer test now rejects Pad in the title while requiring lane name and mixer context.
  • If future shared editor code needs a target label, prefer formatSynthDrawerTargetLabel() or another owner-aware formatter over hand-built Pad NN strings.

2026-07-25 - Gotcha: Shared Synth Editor Loaders Should Use setActiveSynthEditLabel()

  • When Synth Editor code loads a patch, SoundFont, Quasar package, or mapped preset into the active target, do not call setPadLabel() directly unless the code is proven Pad-only.
  • Use setActiveSynthEditLabel() so visible Drum Pads keep Pad labels while private lane Instruments use InstrumentSlot labels.
  • This helper is part of the bridge-phase contract until the final Instrument Bay registry replaces pad-indexed storage.

2026-07-25 - Gotcha: Shared Engines Need Target-Aware Labels

  • Engines that can load into both Drum Pads and lane Instruments must choose the correct identity path at load time.
  • For visible Drum Pads, use Pad labels. For private lane Instruments, use setInstrumentSlotLabel().
  • Quasar loading now follows this rule; future loaders should not blindly call setPadLabel() from Synth Editor code.

2026-07-25 - Gotcha: Tests Must Follow Renamed User-Facing Controls

  • When visible controls move from Pad wording to Instrument wording, update editor regressions at the same time.
  • Stale tests that still search for COPY PAD SETTINGS can make a correct UI look broken during full-test runs.
  • Current visible Synth Editor copy/paste controls are COPY INSTRUMENT SETTINGS and PASTE INSTRUMENT SETTINGS, with shorter lane-owned variants where context allows.

2026-07-25 - Gotcha: Piano Roll Source Checks Must Use InstrumentSlot Wrappers

  • Do not call getSampleName(selectedPianoInstrumentSlot) from Piano Roll non-drum readiness or preset paths.
  • Use getInstrumentSlotSourceName() so visible Drum Pads remain invisible to private lane-Instrument workflows.
  • Closeout coverage now checks both halves: visible Pad source names are blocked, private lane-Instrument source names are readable.

2026-07-25 - Gotcha: Fresh Lane Setup Must Use InstrumentSlot Guards

  • Fresh non-drum lane initialization must not call raw Pad source-clearing APIs.
  • Use clearSampleForInstrumentSlot(), clearSoundFontForInstrumentSlot(), and clearQuasarForInstrumentSlot() together so the initializer cannot damage visible Drum Pads if a bad slot leaks in.
  • This path is especially important because Add Lane is a high-frequency workflow and should teach the Instrument-lane model by behaving predictably.

2026-07-25 - Gotcha: Piano Roll Presets Must Not Clear Visible Pads Through Slot APIs

  • Piano Roll preset application targets private lane-Instrument slots, not visible Drum Pads.
  • Use clearSampleForInstrumentSlot(), clearSoundFontForInstrumentSlot(), setInstrumentSlotLabel(), and useBuiltInSynthForInstrumentSlot() for non-drum Piano Roll preset paths.
  • If a future helper needs to clear Quasar/sample/source data for a lane Instrument, add a guarded InstrumentSlot wrapper rather than calling Pad APIs directly from UI code.
  • MIDI closeout now guards sample clearing: visible Pad sample payload survives the InstrumentSlot clear API; private Instrument sample payload is cleared.

2026-07-25 - Gotcha: Copy Menus Should Name The Source, Not The Browser

  • COPY SETTINGS FROM LANE should remain a source-lane Instrument action. Do not route this path through customer-facing Bay-slot language.
  • Internal identifiers should also avoid InstrumentBayChoice when the operation is specifically copying another Arrangement lane's Instrument.
  • This keeps future linked-instrument or shared-instance features from being accidentally implied by a copy command.

2026-07-25 - Gotcha: Empty Lane Instrument Slots Need One Definition

  • Do not duplicate private lane-instrument availability checks in editor/UI code. The processor owns isArrangementInstrumentSlotAvailableForNewLane().
  • A bare Internal engine source label is not payload. A custom label, real source, SoundFont, Quasar, step data, piano notes, or chord clips are payload.
  • Visible Pad slots are never available for new non-drum lanes, even if they appear empty. Drum Pads and lane Instruments remain separate workflows.

2026-07-25 - Gotcha: MIDI Import Should Create Lane Instruments, Not Bay Presets

  • The MIDI import wizard may source choices from the Instrument Bay, but melodic import hints should describe the result as a lane Instrument.
  • Avoid wording such as Instrument Bay preset in import candidate text because it makes the Bay sound like the thing assigned to the lane.
  • Protocol coverage now guards the melodic lane import hint.

2026-07-25 - Gotcha: Helper Names Can Reintroduce Deprecated Workflows

  • Avoid naming shared editor helpers after transitional storage concepts such as Instrument Bay slots when the user-facing workflow is lane-owned Instruments.
  • Use helper names that describe the UI contract: lane choices, lane labels, mixer routes, MIDI routes, or patch names.
  • Regression coverage should continue guarding visible labels, but source names matter too because they shape the next developer's assumptions.

2026-07-25 - Gotcha: Available Lane Instruments Are Not A User-Facing Pool

  • Avoid customer-facing pool, slot, private slot, or Instrument Bay slot language when a user is simply trying to add or choose a lane instrument.
  • Use plain musical labels such as AVAILABLE INSTRUMENT ## and plain failure text such as NO EMPTY INSTRUMENTS AVAILABLE.
  • MIDI closeout now guards this wording so the transitional storage model does not leak back into the lane workflow.

2026-07-25 - Gotcha: Lane SoundFont UI Must Not Call Pad-Facing Paths

  • SoundFont-backed Instrument lanes should query preset names, counts, indices, and labels through instrument-slot wrappers.
  • Do not call getSoundFontPreset*ForPad() from Arrangement lane UI code for non-drum lanes. It works mechanically but reintroduces the deprecated Pad mental model.
  • Generic editor code that can receive either a visible Drum Pad or a private lane Instrument should use slot-aware editor helpers, not raw Pad calls.
  • Regression coverage now proves visible Pad indices are rejected by the SoundFont instrument-slot wrapper surface.

2026-07-25 - Gotcha: Copy Settings From Lane Must Copy, Not Share

  • The lane instrument menu can offer another lane's sound as a source, but selecting it must copy the source settings into the destination lane's own instrument instance.
  • Preserve the destination lane's slot, instrument identity, mixer channel, and current Pad selection. Only the patch/instrument settings should change.
  • Regression coverage now proves the editor workflow preserves lane ownership while copying the source patch label and parameter value.

2026-07-25 - Gotcha: Lane-Owned Instruments Must Not Share Backing Slots

  • During the Instrument Bay migration, private backing slots are still an implementation detail, but non-drum lanes must behave like independent instruments.
  • If a reassignment targets another lane's slot, copy the source instrument into the destination lane's slot. Do not let both lanes point at one mutable patch.
  • Repair duplicated slots after project restore has loaded pad/instrument payloads, otherwise the repair can preserve identity but lose the actual patch data.
  • Regression coverage now proves live reassignment and restored duplicate slot data both split into unique lane-owned slots.

2026-07-25 - Gotcha: Lane-Owned Instruments Need Unique IDs

  • Non-drum Arrangement lanes can temporarily share hidden backing-slot mechanics, but they must never share the same instrumentId.
  • Duplicate instrument IDs can happen through copied lane state or damaged/restored project data. Repair them at the lane-normalization boundary instead of letting two lanes appear independent while pointing at one identity.
  • Regression coverage now proves duplicate repair during setArrangementLane() and project restore through the MIDI closeout gate.

2026-07-25 - Gotcha: Instrument Lane Creation Must Not Behave Like Pad Selection

  • Creating a non-drum Instrument lane should insert it at the top of the Arrangement stack, create a starter non-drum clip, and preserve the user's current Drum Pad selection.
  • Do not let Add Instrument Lane drift into "select a pad, then make a lane from it" behavior. That reintroduces the deprecated half-way-house workflow.
  • Regression coverage now proves the top insertion, starter clip, 16-step pattern setup, non-drum type, and Pad selection preservation.

2026-07-25 - Gotcha: Lane-Owned Synth Editing Must Not Select Pads

  • During the Instrument Bay migration, a non-drum lane may still use a private backing slot, but opening its Synth Editor must not call the normal Pad-selection path.
  • Preserve the currently selected Pad and Pad bank while routing the active edit slot to the lane instrument. The title should say INSTRUMENT, not imply the user is editing a Drum Pad.
  • Regression coverage now opens a lane-owned synth editor and proves selected Pad state is unchanged while the drawer edits the lane's instrument slot.

2026-07-25 - Gotcha: Explicit Record Targets Need A Tiny Left-Edge Tolerance

  • Direct MIDI callbacks can arrive just before the audio block that renders the first beat. For an explicit target clip, a small pre-roll position near the clip's left edge should clamp to the clip start, not drop the note.
  • Do not widen this into general wrong-target forgiveness. Events far before the explicit target clip still indicate the wrong record location and must be dropped/flagged.
  • Regression coverage now proves both behaviors: tiny pre-roll records at step 0; a playhead far outside the explicit target writes no bogus note and marks the take untrusted.

2026-07-25 - Gotcha: Live MIDI Playback And Recording Have Different Timing Truths

  • Direct physical MIDI should trigger live sound as soon as SpaceAge receives the event, but recorded notes should retain the received timestamp so timing diagnostics and groove placement remain honest.
  • Do not "fix" felt DIN/interface lag by delaying or smearing live playback. First inspect MIDI Health: audio buffer, direct input queue age, hardware output queue age, and SoundFont live voice refusal.
  • Regression coverage now checks both sides: immediate direct playback stays immediate, and stale direct queue input appears as a timing-suspect Health condition.

2026-07-25 - Gotcha: Explicit Arrangement Recording Targets Must Not Fall Back

  • When the editor starts Arrangement recording with a target clip id, the processor must map incoming notes and expression events into that exact clip. If mapping fails, drop/flag the event instead of recording at absolute Arrangement coordinates or clamping into step 0.
  • Keep raw setMidiRecording() processor tests available for lower-level pattern timing, but the user-facing REC path should always be clip-targeted.
  • Regression coverage must prove both sides: long valid clips record deep into the timeline, and explicit-target out-of-clip events create no bogus notes while MIDI Health reports dropped recording events.

2026-07-24 - Gotcha: Backing Slot Is Not Instrument Identity

  • A non-drum lane may still use an internal backing slot during the migration, but UI labels should describe the lane, mixer route, MIDI channel, engine, and patch. Avoid exposing hidden pad numbers for lane-owned instruments.

2026-07-24 - Gotcha: Overview Fit And Compact Visuals Are Different States

  • Do not couple whole-song fitting directly to compact lane drawing. A user can need vertical fitting without wanting the lane badges, MIDI/M/S controls, and normal lane readouts to disappear.
  • Use compact lane visuals only when fitted lane height gets genuinely small; otherwise keep normal lane chrome while fitting the whole song into the viewport.

2026-07-24 - Gotcha: Clip Interior Previews Must Sort By Musical Time

  • Arrangement clip miniature note/chord drawings should never trust vector insertion order. Recorded notes, variants, imports, and copied clips can leave pattern data in an order that does not match the musical timeline.
  • Preview builders must collect events overlapping the clip's source range, sort by start time, and then sample across the full clip span. Otherwise long clips can look like only half the musical material exists.

2026-07-24 - Gotcha: Private Instrument Slots Can Contain Non-Drum Payloads

  • While Instrument Bay is still backed by private engine slots, an "empty" slot must mean more than no sample/SoundFont/Quasar/drum steps. Piano Roll notes and Chord Engine markers can still reference that slot. Do not allocate a fresh lane Instrument into a slot that owns melodic or chord payloads, or a new lane can silently change old musical material.

2026-07-24 - Gotcha: Arrangement Record Starts And Cleanup Note-Offs Are Different

  • Arrangement MIDI note starts should clamp at the beginning of the target when latency compensation would otherwise push them before step 0. Do not wrap first-measure Arrangement takes to the end of the clip.
  • Synthetic cleanup note-offs created by direct-device disconnect/reuse/channel-mode cleanup should close notes at the current musical boundary and should not receive human input latency compensation. Otherwise stale-source cleanup can create tiny 0.05-step notes.

2026-07-22 - Late Direct MIDI Live Drain Plays Immediately

  • Gotcha: direct MIDI input has two timing truths. Live monitoring should usually favor immediate response on the late drain, while recorded MIDI should retain the timestamped position so performances do not smear rhythmically.
  • Rule: do not use the same sample offset blindly for both audible live triggering and recording placement when messages arrive after the early block drain.

2026-07-22 - Playback Snapshot Note-Step Indexing

  • Gotcha: long Piano Roll clips can quietly punish the audio thread if each active Arrangement clip scans every note on every step boundary.
  • Rule: published playback snapshots should carry lookup/index structures for step-triggered data, and the audio thread should prefer O(voices-at-this-step) work over O(all-notes-in-pattern) scans.

2026-07-19 - JUCE var Array Lifetime

  • Do not call someFunctionReturningVar().getArray() and keep/use the returned pointer. The temporary juce::var can be destroyed before the pointer is safely consumed.
  • Store the juce::var in a local first, then call getArray() on the local.
  • This surfaced while adding MIDI Health live-performance row counts: the row report was correct, but the count helpers returned zero because the array pointer came from a temporary.

2026-07-16 - MIDI export proof is layered

  • MIDI export readiness, file write success, and external/opened-file verification are different evidence layers. Do not call an export launch-complete because a plan exists or a file was written. The export plan should carry evidenceBoundarySummary, and the validation checklist should remain visible until the file/package has been opened or re-imported.

Machine-Load-Sensitive Wall-Clock Tests

  • Performance stress 200 blocks currently measures one monolithic wall-clock interval against a fixed 8-second threshold and allocates an AudioBuffer during each block.
  • It can fail under unrelated machine load even when all MIDI, audio, project, mixer, synth, and render assertions pass. Recent unchanged-code results ranged from roughly 5.3 to 10.3 seconds.
  • Do not hide the signal by casually raising the limit. A future hardening pass should isolate or warm up the test, reuse buffers, record machine/load context, and use a robust statistic across multiple samples.

2026-07-15 - Gotcha: One Owner Cannot Certify AUTO LANES

AUTO LANES exposes Shared PTN, Clip Local, and Lane Local ownership, so a product-closing PASS must prove all three. Do not let a valid single-owner diagnostic receipt close the category. Require the canonical session/key pair, all required doorways, all owners, exact Pattern/Clip/Lane coordinates, edit/undo/playback/export proof, and ALL_OWNERS_VERIFIED. Keep individual-owner proof available for diagnosis without promoting it to whole-feature evidence.

2026-07-15 - Gotcha: A Detailed QA Note Is Not Structured Proof

Do not let a prose-only PASS receipt close AUTO LANES. A valid PASS must identify the doorway, owner, exact target, edit/undo/playback/export result, and matching ownership outcome. Shared PTN requires linked-scope proof; Clip Local and Lane Local require isolation proof. FAIL/BLOCKED may remain incomplete because the failed step itself can prevent later checks. Preserve this distinction in UI, serialization, and tests.

2026-07-15 - Gotcha: A Timing Note Is Not A Device Identity

Two controllers can send the same calibration note on the same MIDI channel. Never accept a timing return based only on note and channel. Capture the Hardware Passport input device when the session begins, reject all other physical sources, report how many were ignored, and retain the direct-input queue-age offset when calculating round-trip timing. Regression coverage must include an unrelated controller arriving before the expected device.

2026-07-15 - Gotcha: Generic SysEx ACK Is Not Restore Proof

Never classify an arbitrary F0 7E device ACK packet F7 message as proof for the first pending restore. SDS handshakes must match an outgoing SDS data packet, device id, packet number, selected input source, and exactly one active attempt. Vendor-specific dumps need declared passport dialect/checksum rules or explicit manual verification. Wrong-device, wrong-packet, non-SDS, unrelated, and ambiguous responses must consume nothing and must not create product-ready evidence.

2026-07-14 - Physical MIDI ingress gotchas

  • A saved MIDI device ID is configuration intent, not an open hardware handle. Readiness must report available, open, and active separately.
  • Do not rebuild every direct MIDI input on inventory refresh. Differentially retain unchanged handles or a performer can lose notes during a routine UI/settings update.
  • juce::AbstractFifo is not a multi-producer queue. Multiple controller callbacks require producer serialization or a real MPSC replacement.
  • Preserve callback/driver timestamps before JUCE flattens events into a processor MidiBuffer; otherwise accurate direct-device recording placement cannot be reconstructed.
  • Increment a device-slot generation whenever a slot is invalidated or reused, and reject queued events whose generation is stale.
  • MIDI clock/start/stop/continue must pass through the same Host/Direct/Auto source policy as musical events.
  • An exact zero-duration note is not a negative wraparound duration. Apply the minimum-note policy to zero; reserve pattern wrapping for truly negative elapsed time.

2026-07-14 - Gotcha: Automation Owner Must Stay Explicit

Shared PTN, Clip Local, and Lane Local automation are implemented, serialized, restored, routed into effective playback/export, and exposed by the AUTO LANES owner selector. Never collapse these identities into an unlabeled generic automation state. Shared PTN keeps the linked-clip guard; Clip Local and Lane Local must remain visibly scoped overlays. Regression proof must include owner precedence, variants, project round trips, export, and deletion cleanup.

2026-07-14 - Gotcha: Local Automation Must Survive Project Round Trip

Clip-local and lane-local MIDI expression are real project data: they are serialized, restored, routed into effective playback/export, and clip-local payloads are deleted with their owning clip. Keep regression coverage for all three scopes so future refactors do not silently undo that guarantee.

2026-07-14 - Gotcha: Effective Automation Helpers Must Not Be Stubs

AUTO LANES visibility depends on buildEffectiveMidiExpressionEventsForArrangementClip() and related helpers returning the effective source of truth: shared PTN expression plus lane-local and clip-local expression. If these helpers return empty data, the UI can show no automation even though the pattern contains CC, pitch bend, pressure, or sustain. Any future automation ownership refactor must test the direct helper, clip-aware summaries, and Arrangement/Piano Roll indicators together.

2026-07-14 01:20 - Gotcha: Live Monitoring Must Not Flatten Host Offsets

Recording timing and live monitoring timing are separate code paths. If host/plugin MIDI arrives with JUCE sample offsets, internal monitoring must pass those offsets through to voice start/release. Direct physical MIDI should still use the earliest drain-block path for live notes because delaying it to a calculated in-block position can make real controllers feel worse.

2026-07-14 00:45 - Gotcha: Direct MIDI Latency vs Host Sample Offsets

Direct physical MIDI should stay as close to immediate as the audio block allows once the callback queue is drained. Host/plugin MIDI should preserve JUCE event sample offsets when forwarded to plugin/hardware outputs. Do not use one timing rule for both paths: it either harms direct live feel or destroys host sample accuracy.

2026-07-14 00:45 - Gotcha: Verify The Fresh Test Binary

The self-test build script writes to outputs/build-local-midi-closeout, not the older diagnostic build folder. When a focused gate appears to hang or contradict fresh compilation, verify the executable path before chasing a false runtime failure.

2026-07-14 00:17 - Gotcha: Device Discovery Must Be Deliberate

Do not call physical MIDI device enumeration from quick readouts, constructors, prepare paths, reset paths, or ordinary status refresh. getAvailableDevices() can stall on some Windows MIDI setups, especially with hubs/interfaces. Use known/open/referenced inventory for passive status; reserve full discovery for explicit hardware panels or user-requested refresh actions.

MIDI Runtime/Reporting Gotchas

  • MIDI report builders that can run during timing tests, record workflows, health polling, or UI refresh must not call physical device enumeration. juce::MidiInput::getAvailableDevices() and juce::MidiOutput::getAvailableDevices() belong behind explicit user hardware refresh/discovery actions. Hot reports should use cached/project-known inventory from open direct inputs, open outputs, lane routes, and Hardware Passport references.

SpaceAge Project Gotchas Checklist

2026-08-18 - Human Evidence Review Is Not Ledger Authority

  • Bind every human-evidence result to the exact candidate ID and source commit.
  • A PASS is not eligible for closure without tester details, environment, notes, at least one existing relative evidence artifact, explicit reviewer approval, reviewer name, and review timestamp.
  • Reject absolute paths and .. traversal in evidence references; release reports must remain portable and must not expose private machine paths.
  • Hash reviewed evidence artifacts into the report so later replacement is visible.
  • The evidence reviewer must never edit docs/Release_Readiness_Checklist.md. Ledger closure remains an explicit release decision after report review.

This file captures project-specific traps, invariants, and special cases that should be checked before larger refactors or commercial-readiness passes. It exists so important lessons are not trapped only in chat history.

MIDI Import Gotchas

  • MIDI SETUP checklist row clicks are safe doors only. They may open MIDI Input, MIDI Out, Hardware Passport, Timing, or Health surfaces, but they must not arm lanes, apply setup drafts, queue test notes, send SysEx, recall programs, change routing, or perform any other hidden hardware-affecting action.
  • MIDI stem-package import receipts must not blur planning with mutation. A model/planning receipt may list prepared lane-stem actions, but only the processor path that actually imports lane stems and reaches the package checkpoint should report projectMutated and undoCheckpointCreated as true.
  • Live MIDI input can arrive through the host/plugin stream and SpaceAge's direct physical-input router at the same time. Do not silently accept both by default for a lane-selected physical device; honor the MIDI Input source policy and keep suppression counts visible so doubled notes are diagnosed as routing, not synth instability.
  • MIDI Input proof-test panels must refresh live evidence without clearing it. RESET OBSERVED is a destructive diagnostic reset; CHECK INPUT/copy-report refreshes should re-read host/direct counts, observed channels, visible devices, and repair cards while preserving the evidence the user just created by playing a controller.
  • MIDI source-policy warnings belong in buildMidiDeviceRepairRecommendations, not in ad hoc editor copy. Duplicate host/direct acceptance and policy-suppressed host/direct traffic should produce model-owned Health repair cards that open MIDI Input setup.
  • Long GM channel 10 drum imports use a PianoNote-backed long-form drum payload inside drum lanes, not the classic 64-step Drum Composer grid. Do not imply that imported long drum files have become editable Step Bank grids unless a separate conversion workflow has been chosen.
  • Selected-pattern MIDI import and first-pass channel-to-lane import now both go through applyMidiFileImport. Setup-only channels, hardware conversion, and raw SysEx sending remain guarded; note-bearing melodic/instrument rows and GM drum rows are safe commit paths.
  • MIDI expression ownership is explicit: Shared PTN, Clip Local, or Lane Local. Never imply a different owner than the selector currently names.
  • MIDI expression transforms must report matched, changed, removed, and untouched source-event counts from the model layer. Do not let UI imply a selected-lane transform changed the whole pattern when unrelated MIDI events were deliberately preserved.
  • MIDI expression editor UI must use MidiExpressionLaneSelector or equivalent model-owned selector semantics when matching events back to a lane. Do not hand-roll controller matching in PluginEditor: poly aftertouch lanes use note number as the lane key, while CC-style lanes use controller number, and duplicating that logic invites silent duplicate events.
  • MIDI expression lane lists must not stop at a static +N more footer. Imported files and hardware-heavy performances can create more lanes than fit in the panel; every reported lane needs a reachable selection path through scrolling, paging, or search.
  • MIDI expression graph display and editing must share the same visible tick window. Once the selected-lane scope is zoomable, drawing and erasing against full-pattern ticks while displaying a zoomed range would create silent edits in the wrong place.
  • MIDI expression selected-scope editing must stay based on full visible expression events, not capped previewPoints. Row sparklines may stay summarized, but editable point handles need the viewport-aware event/hit-target model that preserves lane identity, tick, value, and duplicate/stack context.
  • MIDI expression point deletion must not be implemented by tick/lane range deletion. Same-tick stacked events can be legitimate, so point delete/cut/move/paste paths must keep using validated processor helpers that recheck lane identity, tick, value/raw message identity, shared-pattern guards, undo, sorting, and snapshot publishing.
  • Focused MIDI workbench reports belong in the MIDI model layer, not in PluginEditor. MIDI Input, MIDI Output, MIDI Sync, MIDI Timing, MIDI Expression, MIDI Health, Hardware, SysEx, and future setup/repair panels should copy model-owned receipts so tests, support bundles, and UI wording stay aligned.
  • MIDI hardware hookup instructions belong in MidiHardwareSetupDeviceTemplate::hookupChecklist. Do not duplicate MiniNova/QY/GM/EWI/MIDI guitar/MPC cabling, channel, test-note, SysEx Vault, or timing-calibration steps in PluginEditor; setup UI and docs should quote the model-owned checklist so the future wizard has one source of truth.
  • Device-specific hardware editors must obey MidiHardwareSetupDeviceTemplate editor-readiness fields. If deepHardwareEditorReady is false, Yamaha XG, Roland GS, GM module, CBX, QY, MU, Sound Canvas, or similar pages may show locked guidance and manual/Data List checklists, but they must not queue device-specific SysEx or NRPN parameter sends.
  • Hardware test-note UI must be driven by buildMidiHardwareSetupTestNotePlan() and the processor's lane-aware confirmed queue bridge. The plan must be ready, show the output/profile route, require confirmation, and still report Sends MIDI now: no until the user clicks the separate send action. The confirmed action may queue only note-on/note-off. Do not combine template apply, Program Change, Bank Select, SysEx, clock, transport, reset, or recorded-timing alignment into the test-note gesture.
  • Outgoing MIDI Clock, note playback, pitch bend, aftertouch, CC playback, and other performance gestures must stay on realtime/live queues and must not discover devices, take profile locks, wait on planned delays, or share the SysEx/program-recall queue from the audio callback.
  • SysEx dumps, program recall, setup cards, and device identity/configuration messages belong in the guarded setup/recall path, where confirmation, wire-time estimates, and planned delays are useful rather than harmful.
  • MIDI PATCH copy must distinguish review-only setup data from queue-safe setup cards. RPN and passport-defined queue-safe NRPN cards may be queued through guarded actions; unknown/device-specific NRPN, Bank-only, and ambiguous setup rows should stay preserved/reviewed until a Hardware Passport or explicit user action makes them safe.
  • Hardware Passport pitch-bend range is structured data, not note text. RPN 0,0 Pitch Bend Range setup-card receipts can populate pitchBendRangeSemitones and pitchBendRangeCents, but future UI/export logic should read those fields rather than parsing receipt notes.
  • Runtime/internal pitch-bend range and Hardware Passport destination pitch-bend range are different truths. MIDI AUTO can report how SpaceAge is currently scaling internal pitch bend; readiness/export reports must still warn if a hardware-routed lane or clip contains pitch-bend expression without a passport-declared destination range.
  • MIDI AUTO visibility and MIDI AUTO ownership are different truths. Doorways may summarize effective automation, but the editor must name whether edits target Shared PTN, Clip Local, or Lane Local data.
  • AUTO LANES user instructions should come from the MIDI model, not hand-written panel copy. The workflow is: select/create real musical material, open AUTO LANES, choose/create a row, edit with guarded tools, use VARIANT before diverging linked PTN clips, and audition/export/test hardware when the destination matters. If a future UI panel needs different wording, update the model helper first.
  • Protocol coverage should keep pitch-bend editor polish and hardware bend trust separate. Drawing or smoothing a 14-bit pitch-bend curve is a MIDI AUTO/editor job; proving that an external synth will bend by the intended semitones is a Hardware Passport bend-policy and bend-test job.
  • Hardware pitch-bend test plans are not permission to send MIDI. The model can describe the test phrase, readiness, and safety copy, but the actual bend-test send must stay behind explicit user confirmation and must not bundle Program Change, Bank Select, SysEx, MIDI Clock, transport, or reset traffic.
  • Hardware pitch-bend test receipts belong in the processor, not in editor code. A visible button should call the confirmed receipt path, not hand-roll MIDI messages, because the processor path rechecks the Hardware Passport, logs each attempted message, refuses unconfirmed sends, and preserves the no-patch/no-SysEx/no-sync/no-reset boundary.
  • MIDI PATCH TEST BEND / BEND INFO is a Hardware Passport action, not a generic lane note audition. Keep it attached to the selected Arranger lane's passport and let incomplete setup open the plan report instead of silently doing nothing.
  • SysEx snapshot summaries should report transfer class, DIN wire-time estimates, and paced completion estimates from the MIDI model. Do not let future vault UI imply old-hardware dumps are instant sends.
  • SysEx identity evidence is not restore verification. A dump's manufacturer/device id can help compare it against an attached Hardware Passport, but a match only says the snapshot appears to belong to the expected hardware family. Success still requires manual verification, device ACK, or another restore verification receipt.
  • SysEx Vault reports should include whole-vault totals, not just per-snapshot details. The user needs to know total bytes, total pacing time, transfer-class mix, confirmation count, archive count, warning count, and the largest dump before restoring a hardware rig.
  • SysEx live capture must be an explicitly armed listen/store workflow. Captured bytes need F0/F7 validation, transfer/timing classification, naming, archive policy, and Hardware Passport attachment before they become restore candidates. A capture receipt is not consent to send anything.
  • SysEx restore progress has two truths: SpaceAge can estimate byte pacing and completion time, but success requires a restore verification receipt. Manual verification is a user-confirmed receipt, not device acceptance. Device ACK/NAK is hardware-confirmed. WAIT/CANCEL/timeout/no-verification are separate states. Any UI progress meter must label estimated progress separately from manual verification, device ACK, device NAK, WAIT/CANCEL, and timeout.
  • SysEx restore response classification must stay conservative. Generic MIDI Sample Dump-style ACK/NAK/CANCEL/WAIT parsing exists, but valid unrecognized SysEx is not restore success. Device-specific response dialects should be added as Hardware Passport parsers, not as broad "any SysEx after send means OK" logic.
  • SysEx status text must match the real UI. One-dump capture, timeout/countdown, source guidance, save, copy, Vault storage, visible restore verification receipts, and a generic ACK/NAK/CANCEL/WAIT classifier exist; remaining product gaps are live batch capture/save UI, device-specific acknowledgement scripts, checksum/device-id helpers, deeper restore-history filtering/export polish, and clearer restore progress UI. Do not write copy that implies those gaps are already solved.
  • SysEx identity/checksum displays should always answer "what should the user do next?" Hex evidence alone is not enough for normal musicians; reports should say attach Passport, review mismatch, do not restore, manually verify, or proceed only after explicit confirmation.
  • SysEx batch capture currently has a read-only preflight plan, not a live capture workflow. Do not wire UI that appends multiple dumps until per-dump receipts, per-dump timeout reset, duplicate/out-of-order warnings, save/skip decisions, and no-hidden-send copy are all model-owned.
  • SysEx batch-capture helper signatures are easy to misread because expected dumps, captured dumps, stored evidence, live evidence, duplicate evidence, and timeout are all integers. Use explicit argument ordering and test stored/live/timeout fields together so timeout never masquerades as evidence.
  • Incoming SysEx should bypass normal note/channel recording and expression mapping. It is device-level data, not a lane performance gesture. Route it only through the armed capture inbox or explicit import/vault workflows.
  • Runtime MIDI Learn mappings must not call setValueNotifyingHost() directly from incoming MIDI handling in processBlock. Queue the gesture and apply it off the audio path; keep MIDI Learn capture itself explicit and guarded.
  • Legacy performance controls such as CC/pitch-wheel-to-perfcutoff should not notify APVTS parameters from the audio callback. The current live path writes realtime performance cutoff atomics; if host-visible automation is ever needed for those gestures, coalesce it onto a non-audio context.
  • MIDI Health owns the runtime MIDI control-mapping dropped-message counter. If controller assignments ever feel like they miss dense CC moves, check that report before chasing unrelated audio or hardware bugs.
  • Compact MIDI Health/Input/Output cards should reveal when the full copied report contains more warnings or recommendations than the visible card can show. A small +N more cue is better than hiding important diagnostics behind an apparently clean summary.
  • Host/plugin live MIDI notes are sample-positioned from incoming JUCE event offsets inside the current audio block before voice rendering. Direct physical MIDI has a separate callback bridge: keep sync/transport on the calculated timing sample, but keep live notes/controllers on the earliest drain-block sample so the device-identity path does not add avoidable monitoring lag. If the user still feels latency, check audio buffer, driver, interface, controller path, Bluetooth, SoundFont/instrument startup, and heavy processing before adding compensation.
  • Live MIDI lag reports need to separate SpaceAge scheduling from the rest of the chain. A high audio buffer, direct-input queue age, SoundFont voice startup, driver/interface latency, Bluetooth paths, or external-hardware response can all feel like MIDI lag even when incoming notes are already entering the monitor path at the earliest possible audio-block position.
  • Emergency live MIDI stop messages must keep the same sample-position discipline as normal note-offs. All Sound Off is allowed to hard-stop voices, but it should schedule that stop through the renderer/retirement path instead of wiping voice state directly from incoming MIDI handling.
  • Generated MIDI expression from clip playback must be sample-positioned for internal synth state too, not only for plugin/hardware output. If queueMidiExpressionEventsForStep() creates a delayed pitch bend, sustain, pressure, CC, volume, pan, or expression event, it should flow through the per-sample expression queue before rendering so it does not affect earlier samples in the same block.
  • Panic should clear stale hardware-router live/recall queues before queuing new reset traffic. Otherwise a delayed external note/program/setup message can still leave SpaceAge after the user hits the emergency button.
  • SoundFont voices now use an atomically published prepared TSF voice rack on the live note-on path. MIDI Health reports loaded SoundFont instrument count, prepared/active rack slots, and refused live voice starts as diagnostic clues. Keep validating pool exhaustion: if the prepared rack is exhausted or stale for the current sample rate, SpaceAge should refuse the unsafe voice start and report the miss rather than cloning TinySoundFont state on the realtime path.
  • Classic sample/one-shot voice start now reads immutable per-pad voice-start cache data instead of taking sampleMutex in startVoice(). Any code that changes sample layers, bonus one-shots, or layer settings must republish the pad's sample voice-start cache at the mutation boundary; do not move sampleMutex back into the live MIDI note-start path.
  • MIDI import tempo/meter/key rows must be ordered by musical tick before any UI says "first row." MIDI files can store conductor data in a separate track, and traversal order is not musical order.
  • Sequencer playback must acquire one SequencerPlaybackSnapshot at the audio-block boundary and use that generation for Chain mapping, lane mute/solo, routing, automation ownership, and clip traversal. Pattern snapshots remain independently published by design. If a path edits drum steps, Piano Roll notes, Chord Engine markers, MIDI expression, or pattern length, it must publish the affected pattern snapshot after the edit. Do not add vector copies or snapshot publishing directly inside live MIDI callbacks; queue those edits and publish on the worker side, as MIDI recording and Step Input now do.
  • MIDI recording sessions must pin their target context at record start. At minimum, keep the target length stable through count-in, live recording, and commit; do not rely only on repeated scans of mutable Arrangement clip state from the audio path. Future punch-in/automation recording should also pin target clip id and source-start.

Core Product Rules

  • SpaceAge should remain a focused groovebox / microDAW hybrid, not a full clone of every large DAW feature.
  • Prefer simple visible workflows first, then hide advanced power in panels, Settings, or clear secondary actions.
  • One instrument per Arranger lane is the default mental model. It keeps composition, mixer routing, MIDI export, stems, and future VST/hardware support predictable.
  • Pads are still excellent for drums and hybrid sample/synth work, but melodic lanes must be treated as lane-owned instruments and MIDI clips, not visible pad assignments.
  • Clips should not silently move into foreign lane types. If conversion is needed later, ask explicitly.
  • Linked clips and unique/variant clips must remain clearly different: linked edits propagate; variants become independent.
  • Keep Clip Instance and Pattern Payload separate in thinking and code. The clip lives on the timeline; the payload is the musical data. Bugs appear when resizing, cloning, or pasting silently confuses the two.
  • The Arrangement Canvas owns song time. Drum Composer and Piano Roll are editors for material; the Arranger decides when lanes play together.

Arranger Canvas Gotchas

  • Deleting a clip should preserve timeline space unless the user explicitly chooses a future ripple-delete action.
  • Pasting at the playhead must shift the whole selected time block consistently across affected lanes, not only the lanes represented in the copied material.
  • Silent gaps are timeline objects when they help paste/replace workflows, but should not remain visibly stacked behind real clips.
  • Section markers are composer-map objects. Normal resizing should be non-destructive to musical clips.
  • Section marker duplication should insert immediately after the selected marker/group, not after the next unrelated marker.
  • Lane names and instrument badges must remain pinned and stable during horizontal scroll.
  • Lane instrument badges are the only Arranger-level place to inspect or change lane instruments. Do not recreate a top-row instrument inspector/dropdown; it was confusing, redundant, and deprecated. Keep badges readable enough for SoundFont and preset names; shrinking them too aggressively makes lane ownership ambiguous.
  • Lane badges should teach two facts at a glance: the lane's channel-strip route and the lane's current Instrument/preset. If screen space gets tight, improve the badge layout before adding another inspector control.
  • Patch identity is part of the Instrument, not only the synth editor. Loading/saving a patch, changing a SoundFont preset, loading a Quasar package, or choosing a factory preset should update the synth patch readout and the Arranger lane badge. Use an asterisk for unsaved edits, but hydrate a clean baseline after loading a project, recovery, archive, or blank project so reopened work does not look falsely dirty.
  • Dirty patch detection must not depend on the synth drawer being visible. Arrangement lane badges should catch changed instrument parameters while the user is composing on the Arrangement Canvas.
  • Patch-name save/read code must use the effective loaded engine for a backing slot, not just the raw engine parameter. SoundFont and Quasar can override the old engine parameter, so helper paths should go through the current/effective instrument-engine accessor.
  • Lane badge route text should explicitly show both the mixer strip and MIDI channel, e.g. MIXER 20 / MIDI CHANNEL 06. Favor full wording over abbreviations unless space becomes a real problem; this keeps hardware routing and mix routing visible without reintroducing a top inspector.
  • Lane instrument menus should keep adding Instrument Bay actions, not raw top-row selection. Important nomenclature: the Instrument Bay is the chooser/management page; Bay Slots are temporary internal/source slots; the chosen sound generator on a lane is an Instrument. User-facing lane actions should say Instrument, not Bay Slot: edit this Instrument, load a SoundFont into this Instrument, assign a fresh Instrument to this lane, variant this lane Instrument, create a new lane with an Instrument copy, and warn before changing the instrument on a lane that already contains clips.
  • Do not expose a raw list of hidden backing slots as "Assign Existing Instrument" in lane menus. That recreates the deprecated pad-proxy workflow. Until the full Instrument Bay browser exists, lane menus should offer explicit safe actions: edit this lane Instrument, load a SoundFont into it, assign a fresh Instrument, variant/copy it, or create a new lane with an Instrument copy.
  • Do not implement "assign fresh Instrument" by blindly calling resetPad() on the backing slot. resetPad() is intentionally heavy: it clears samples, SoundFonts, Quasar data, mod routes, global audio tails/buffers, and mutes the pad. The Instrument Bay needs a lightweight lane-instrument initializer that creates a clean chord-friendly Instrument without disturbing unrelated audio state.
  • + ADD LANE must use the same lightweight fresh-instrument initializer as Assign Fresh Instrument To This Lane. Otherwise a newly created lane can inherit stale sample, SoundFont, Quasar, send, mute/solo, or synth state from a recycled hidden backing slot. Copy/variant lane creation is the exception: it should preserve the copied Instrument exactly.
  • Fresh non-drum lane Instruments must only allocate from empty backing slots. A slot is not empty if it is already owned by a lane, has loaded sample/one-shot/SoundFont/Quasar content, has a non-default pad label, or has drum steps anywhere in the project. Failing loudly is safer than silently overwriting a user drum pad while the Instrument Bay still uses temporary backing slots.
  • Any UI path that changes a populated lane's instrument must go through the same confirmation guard.
  • The highlighted Arranger lane is the Add Clip target. Empty lanes should receive new clips at measure 1; populated lanes should receive new clips at the end when the playhead is still at measure 1; a deliberate playhead position should insert there. Do not reintroduce a separate top-row instrument/pad target that competes with this lane-first behavior.
  • All new Arranger clip paths should use the same preferred insertion rule: selected silent gap wins, empty lane starts at measure 1, populated lane with playhead beyond measure 1 inserts at the playhead, otherwise append after existing lane material. Keep auto-created starter clips, ADD CLIP, and + ADD DRUM CLIP on that shared rule instead of reimplementing placement locally.
  • When a Piano Roll clip is opened from the Arrangement Canvas, the Arranger lane owns the instrument. The Piano Roll instrument selector may show the active lane instrument for context, but it must not become a second competing place to reroute that lane.
  • Live MIDI input has its own target and must be predictable: if an Arranger lane is MIDI-armed, that lane's Instrument owns incoming controller notes until disarmed. Clip selection and Piano Roll refresh may change the edit/preview Instrument, but must not steal the live MIDI target while a lane is armed. With no lane armed, live MIDI follows the current Piano Roll/edit Instrument.
  • The armed MIDI lane should be visibly labeled, not merely recolored. A tiny color change is easy to miss while composing; use explicit text such as MIDI IN on the lane MIDI button and LIVE MIDI INPUT on the lane badge so users can predict where their controller will play.
  • A saved lane hardware profile is identity, not consent. Project load, lane selection, or route normalization must not send bank/program changes or SysEx automatically. External hardware recall needs a visible user action, confirmation where appropriate, progress/throttling, and a send log.
  • Hardware recall summaries are previews, not send commands. The UI should render buildHardwareRecallPlanSummary() so the user can see Bank MSB/LSB, Program Change, SysEx byte counts, delays, and warnings before choosing an explicit recall action.
  • The Settings MIDI Hardware viewer is intentionally observational. It may show hardware profiles, recall-plan previews, and queue logs, but must not become an unconfirmed Bank/Program/SysEx send button.
  • Hardware transport summaries are previews, not clock/transport sends. The UI should render buildHardwareTransportPlanSummary() for Song Position Pointer, Start/Continue/Stop, and MIDI Clock intent; actual sync behavior still needs explicit user-controlled policy.
  • Hardware profile program numbers need careful UI wording. MIDI Program Change stores 0-127, but many humans and hardware manuals say 1-128. Do not "fix" the stored byte by adding one unless the UI is explicitly converting for display.
  • Removing a hardware profile must clear lane route references to that profile. Removing a SysEx snapshot must remove that snapshot id from any profile that linked it. Dangling device references make hardware workflows feel haunted and unsafe.
  • Creating a hardware profile from a lane should copy known lane facts only: channel, devices, clock intent, and a friendly name. Do not guess bank/program values or manufacturer data; let the user fill those in deliberately.
  • Do not expose a top-row Piano Roll instrument/preset chooser as a normal workflow. Standalone Piano Roll can keep a hidden fallback until the full Instrument Bay page exists, but visible instrument assignment belongs on Arrangement lane badges.
  • Preset browsing must become metadata-driven. Do not keep expanding raw preset dropdown strings forever. Future preset records need ENGINE, CATEGORY, PRESET, and INFO fields, with INFO carrying authorship, description, source, harmonic-safety notes, version, and license/source notes where relevant. Preset records must be able to launch their engine as a new Instrument in a new lane through the Instrument Bay.
  • Non-drum Arrangement clips need persistent clipId identity. The visible clip label should be CLIP #n or a user name, not a pattern number. Pattern payloads are an internal implementation detail until the clip/payload registry is fully separated.
  • The selected lane is the destination for ADD CLIP and + ADD DRUM CLIP. The lane header/badge must visibly light up when selected and explicitly say ADD CLIP TARGET. Placement rule: empty selected lane inserts at measure 1; populated selected lane with playhead at measure 1 appends after existing clips; populated selected lane with playhead later inserts at the playhead. Do not infer the destination lane from stale clip selection or a hidden top inspector. Clip selection may update the Add Clip target to that clip's lane, but it must not change the separately armed live MIDI input lane.
  • The hidden Arranger lane ComboBox is compatibility plumbing only. It may mirror the selected lane for old inspector code, but the editor-side selected destination lane and visible lane badge are the source of truth for Add Clip, Add Drum Clip, Rename Lane, and lane instrument actions.
  • Fresh non-drum ADD CLIP actions must allocate a fresh unused internal payload. Do not reuse the globally selected Drum Composer / Piano Roll pattern for melodic lanes; that was the old pad/pattern coupling path and caused surprising edits across lanes.
  • Any path that auto-creates a starter lane clip must wipe the newly assigned payload first (clearPattern, clearPianoNotes, clearChordClips) before setting clip length. Fresh starter clips should never inherit stale notes, chords, or drum steps from a recycled internal payload.
  • The Arranger inspector's PATTERN selector is drum-clip-only. For non-drum lane clips it should become a disabled CLIP identity readout, because retargeting melodic clips to arbitrary PTNs recreates the deprecated global-pattern workflow.
  • Non-drum Arrangement lanes need persistent instrumentId identity. The current audio engine still uses a backing slot internally, but user-facing lane workflows must not expose pad identity for melodic/harmonic/bass lanes. Playback, ghost notes, and routing should go through Instrument Bay accessors so the future independent Bay can replace backing slots cleanly.
  • The visible Arranger workflow is lane-head first: click the lane itself to choose the Add Clip target and change instruments from the lane badge. Do not reintroduce a top-level lane/instrument inspector dropdown as the primary user-facing path.
  • When UI or export code needs the channel-strip route for a non-drum lane, use the lane mixer-channel accessor rather than deriving it from the hidden backing slot. The current implementation maps them together, but that coupling is temporary scaffolding for the future independent Instrument Bay.
  • Arranger lane badges and status messages need the lane index, not merely an ArrangementLane snapshot, when formatting the user-facing Instrument route. A lane snapshot can still reveal the temporary backing slot, but only the lane-index path can report the intended mixer-channel route as the Instrument Bay becomes independent.
  • Avoid reintroducing helper overloads that format lane Instruments from only an ArrangementLane value. If a label needs mixer route, MIDI channel, or future Instrument Bay identity, pass the lane index and use the processor lane accessors.
  • Synth Editor titles and tooltips must not expose hidden pad-slot backing when opened from an Arrangement lane. Until the independent Instrument Bay is complete, format lane-opened editor labels through the lane index and say Instrument/lane/mixer/MIDI, not Pad. Pad wording is valid only on the Pads page, Drum Composer, and explicitly MPC-style workflows.
  • Lane badge status words such as TARGET and MIDI IN should augment, not replace, the actual route text. The user should still be able to see the Mixer and MIDI channel route on the active lane.
  • Arrangement Overview Mode is a map, not a cramped full editor. It may hide per-lane controls so the user can see the whole song stack; double-clicking a collapsed lane label or toggling Overview should restore full lane view.
  • O and Ctrl+O currently converge on the same Arranger overview: frame the whole existing song in both axes at the largest practical scale. Do not include empty work-tail space.
  • Drag behavior should feel magnetic and smooth: object follows cursor, snap preview is clear, final drop is deterministic.
  • Snap state must be visibly obvious (SNAP ON / SNAP OFF) and should use consistent snapping semantics for clips, loop ranges, playhead, and section markers.
  • Clip length and pattern/payload length are not automatically the same thing. If the user changes one, the app must make the result clear through command behavior, UI labels, or a future dialog.
  • Pasting a multi-lane block inside existing material should shift the whole time range consistently, not only lanes that happened to be copied.

Sequencer And Piano Roll Gotchas

  • Drum Composer and Piano Roll click behavior should follow familiar piano-roll expectations where possible: left-click adds/auditions, right-click deletes, modifiers select or edit.
  • Ctrl+A, Delete/Backspace, copy/paste, duplicate, slice, and fit/zoom shortcuts should be consistent across note, chord marker, and clip contexts unless there is a strong reason.
  • Shortcut handlers must prove an edit can mutate eligible objects before checkpointing. Empty selections and boundary-clamped transforms must not clear Redo.
  • Plain X may dismiss a popup, but modified Ctrl/Cmd+X is always Cut. Never use an unrestricted character check before clipboard commands.
  • Clearing Arranger selection must clear both visible clip selection and any legacy chain-slot target. A following Delete must be a no-op, never a stale-target removal.
  • Copy with no selection must preserve the existing internal clipboard. It should report the no-op instead of silently destroying a valid copy.
  • Chord Markers should behave like notes where possible: drag, resize both edges, slice, duplicate, copy/paste, delete, and shift-drag clone.
  • Chord Markers can have playback modes (Chord + Arp, Arp Only, Reference Only) so harmonic memory can remain without doubling playback.
  • Typed Honeycomb chords currently map typed roots/qualities into the existing ChordClip model. Slash chords are only faithful when the bass note is one of the chord voices and can be represented as an inversion. A future true slash-bass feature needs a dedicated bass-note override field; do not fake arbitrary slash bass with root offsets.
  • Piano Roll ghost notes must be read-only and visually distinct. Do not let ghost data become editable by accident.
  • Scale/key spelling should respect real music theory conventions, including flats/sharps appropriate to the chosen key.
  • Fit/zoom should center selected material when selection exists, not throw the user to an unrelated register or timeline zone.

Instrument And Sound Engine Gotchas

  • Synth Engine tab highlighting is intentional: active/used tabs are shown in dark blue (#00202f). Keep contrast readable.
  • Non-drum Arrangement lanes own their instruments. The lane header/menu is the source of truth for engine, preset, channel strip, and MIDI channel. Do not reintroduce a top-level instrument inspector for non-drum lanes; it recreates the confusing pad-proxy workflow we intentionally retired.
  • Lane-owned instruments must not silently share Bay Slots. Internally, a lane may still point at a Bay Slot until the full Instrument Bay refactor is complete, but user-facing lane instruments should behave like independent instances. Creating a new lane from an existing lane should copy the instrument into a fresh slot; assigning an existing slot already owned by another lane should be blocked or made explicitly intentional in a future linked-instrument feature.
  • Long-term Instrument Bay refactor target: introduce persistent lane instrumentId records, keep instrumentPad only as legacy migration/backing data, store mixer routing explicitly instead of deriving it from pad + 1, and make Arrangement playback resolve Instruments from lane identity rather than raw pad slots. Current tests still encode some pad-backed expectations, so update tests in the same pass that changes the resolver.

UI Layout Gotchas

  • Avoid fixed-width header chains that exceed the editor's minimum width. The Synth Editor header, native synth patch rows, Drum Lab patch rows, and effect card headers must either use flexible/truncated regions or split into intentional rows. Do not solve horizontal crowding by making knobs too small.
  • Dense device pages should use compact typography before shrinking controls. Liftoff previously clipped because its layout hard-coded too few rows for the number of controls; future dense grids should compute rows from control count instead of assuming a fixed row count.
  • Effect detail popups and compact Effects rack cards should share a named knob style. Do not accidentally reuse Redshift-specific dial component IDs in other device editors unless the visual match is deliberate.
  • Complex synth/effect devices should become paged devices before their dials become tiny. Propulsion 1, Liftoff, Moonshadow, Lunacy, Glass Moon, and future advanced effects should follow the Redshift principle: fewer controls per page, larger controls, compact labels, and predictable breathing room.
  • Synth drawer actions must target the instrument currently being edited, not whichever pad was selected last. Save/load patch, engine changes, SoundFont loading, and source import should all refresh lane labels and the Arrangement Canvas immediately.
  • Clear Changed Values must affect only the active tab/engine area, not the entire synth editor.
  • Redshift patch save/load must include new parameters as they are added, or patches will silently lose sound-design intent.
  • New Redshift tone controls must be represented in four places: APVTS parameters, raw parameter pointers, the Redshift editor tab, and .sspoly patch save/load. Generated/factory Redshift presets should also set sane default values so new controls are musically exercised.
  • SoundFont projects need path repair when .sf2 locations change.
  • Large SoundFonts can work, but note preview/dragging must avoid expensive or unsafe voice churn.
  • Physical Model pitched presets should obey A440 equal temperament when used melodically.
  • Factory melodic presets must be honest about harmonic behavior. Use Chord-Safe only for audited sounds that survive normal triads without hidden fifth stacks, fixed-frequency operators, and strongly inharmonic ratios. Until another engine is intentionally verified for chord work, reserve this label for Redshift-style harmonic presets and tag the rest as Lead, Pad, Bass, Percussive, Inharmonic, or FX.
  • Never repeat the jazz horror oscillator mistake: factory presets meant for chords must not hide semitone offsets, fixed-frequency operators, dissonant ratio stacks, or unadvertised non-A440 components. Test ordinary major/minor triads before calling anything chord-safe.
  • Propulsion 1, Liftoff, and Glass Moon currently expose ten conservative factory starters each. The retired adventurous banks are archived outside the live app; do not reintroduce them as factory defaults unless they are retuned, relabeled, and tested against ordinary chords.
  • CPU-heavy instrument designs need a real-time audit before becoming factory defaults. Red flags include per-sample harmonic loops, high unison counts, repeated sin/pow/exp inside nested voice/oscillator loops, long release tails with no voice culling, and preview behavior that retriggers large SoundFont voices during note dragging.
  • Spectral instruments are especially vulnerable to accidental "jazz horror" because partials can imply hidden chords. Moonshadow-style presets should keep the fundamental A440/equal-tempered, make inharmonic content controllable, and audit ordinary triads before any preset is treated as chord-friendly.
  • Kick Lab and other purpose-built engines should remain low-CPU and musically named, not raw DSP parameter dumps.
  • VIB SYNC currently syncs vibrato rate only. Depth remains a normal depth amount.
  • SoundFont parameters are patch-dependent. If a control has subtle/no audible effect on one SoundFont patch, do not assume the control or engine is broken without testing another patch.
  • Lane-owned SoundFonts need stable state: file path, repaired path, bank/program/preset index, readable preset name, transpose/tuning, and patch controls.
  • Liftoff and Lunacy user-source import currently stores audio through sample layer slot 1 internally, but the user-facing concept is not "a normal layer." Keep source import labels, project archive behavior, and tab highlighting clear so users do not think the Layers tab is secretly playing a separate one-shot.
  • Factory presets for source-import synths should clear or intentionally ignore user-loaded sources. If a preset clears a source internally, refresh the visible source label immediately so the UI does not imply a stale file is still active.
  • Source-root controls should display musical note names plus MIDI numbers. Raw MIDI numbers alone are accurate but too opaque for a composer trying to tune imported C/A/root samples quickly.
  • Native .ssynth patches for Liftoff and Lunacy store source-file paths and, when possible, copy the source audio beside the patch in a *_Sources folder. Load should try the original path first, then the relative bundled source, and report missing sources clearly if neither exists.
  • Liftoff/Lunacy source-file chooser callbacks must capture the target pad at launch time and use a SafePointer. Otherwise a user can open the file chooser, click another pad/lane, and accidentally load the source into the wrong instrument.
  • Propulsion 1 oscillator pan uses a lightweight side-signal plus weighted voice pan offset. This gives audible oscillator spread without rewriting the shared mono-per-voice renderer. True independent stereo oscillators still require a deeper stereo voice-renderer refactor.
  • Quasar .ssquasar instruments are folder packages, not single files. Archive/export/import, missing-asset repair, and future Instrument Bay browsing must preserve the package folder, manifest.json, and relative sample paths under Samples/; do not flatten these into ordinary archive assets.
  • Quasar sample filenames must remain ASCII-only and deterministic. Do not pass raw C strings through %s/platform formatting for exported filenames; Windows/JUCE can reinterpret byte pairs as wide text and produce corrupted Unicode filenames.
  • Quasar manifest version 4 stores source-engine identity and source patch parameters. Keep this metadata when rebuilding package tools so future Instrument Bay browsing, rebuild, and audit features can explain where a multisample came from.
  • Quasar package loading should fail if any declared zone sample is missing or unreadable. A partial multisample is more dangerous than a clear load failure because it creates invisible dead notes.
  • Quasar live note start must read from the immutable per-pad zone cache and the realtime round-robin cursor array. Loading, clearing, pasting, project restore, and package repair should publish/reset that cache at the mutation boundary; do not put quasarMutex back into startVoice() for zone choice.
  • Quasar builds can be long offline jobs when the user chooses wide ranges, multiple velocity layers, and long captures. Always show progress, allow cancellation, and only refresh/load the resulting instrument after the worker completes.
  • Quasar build/load must force a clean post-load UI refresh. If the user has to change presets to "clear" the machine before loading again, the editor is probably showing stale engine/preset state.
  • Quasar root spacing is a quality/package-size choice. Whole-tone roots make smoother packages but increase build time and disk use; fourths and octaves are useful draft sizes. Keep the selected spacing in the manifest so future browsers and repair tools can explain the package.
  • Quasar round robins multiply capture time and package size. Playback must rotate only among equally suitable zones for the same note/range/velocity decision, and project load/copy/reset paths must clear cursor state so the first repeated notes are predictable.
  • Live MIDI pressure is now stored as shared runtime state, but synth engines should not invent private pressure behavior. Add pressure modulation through the same destination/amount patterns used for mod wheel, velocity, and future Motion Blocks so hardware controllers, automation, and imported MIDI remain coherent.
  • Do not let MIDI setup/channel-mode controllers fall through into musical performance paths. RPN/NRPN/Data Entry/Local Control/Omni/Mono/Poly messages can be preserved, inspected, or intentionally routed later, but they should not trigger pads or legacy performance cutoff behavior by accident.
  • Six Sines is a promising permissive reference for a future clean digital FM/sine engine, but do not import its plugin architecture or presets without a dependency-level audit. If used, favor a clean SpaceAge-native implementation with our lane-owned Instrument Bay workflow, our patch format, and explicit attribution only for code/preset material we actually copy.

Mixer And Effects Gotchas

  • Mixer bank changes must never reset sends, gain, output, mute/solo, EQ, or hidden-panel values.
  • Effect sends already exist per pad/channel. Exposing them in more than one page is okay only if all controls bind to the same parameter.
  • Halostar and Reverb must remain separately routable. Do not let Halostar silently piggyback on the classic REVERB send again; it makes both reverbs feel identical and confuses A/B testing. The mixer send panel should expose Halostar, Reverb, and EchoRay as distinct sends.
  • The Effects page selected-pad send bank and the Mixer channel-strip send panel must stay in lockstep: Halostar, Reverb, EchoRay, modulation sends, and octave send should all bind to the actual selected pad/channel parameters rather than any page-local state.
  • Arrangement lanes do not currently own independent effect-send fields. Do not describe effects as per-lane until a lane-send model exists; for now lanes reach shared returns through the instrument/pad/mixer channel path.
  • EchoRay is not per-pattern, but it is also not a single shared delay memory: its global character/return controls are shared while delay histories are per pad/channel. Keep that distinction intact when debugging delay behavior.
  • Loop FX also need separate Halostar, Reverb, and EchoRay intent. Keep old project compatibility by loading old reverbSend values as classic Reverb only, while new loop Halostar routing uses halostarSend.
  • Shared effects should be bypassed or cheap when unused. Turning on an effect return may have some baseline cost; the larger audible cost happens when channels actually send signal into it.
  • Channel-strip hidden panels should preserve their open state across page changes.
  • Keep destructive/drastic controls visually distinct: Panic, 86/Remove, Clear.
  • Future master/return effect rates should share one tempo-sync division language instead of one-off sync systems.
  • Delay sync, vibrato sync, LFO sync, arp rate, chop subdivisions, and future Motion Blocks should eventually use one shared timing/division model.

MIDI And Hardware Gotchas

  • MIDI now has a central protocol foundation in Source/SpaceAgeMidi.*. New MIDI features should prefer that timing/event/route vocabulary instead of adding more one-off state to processBlock.

  • MIDI Health / Dashboard data is a read-only summary. buildMidiProjectHealthSummary() may gather readiness, mappings, hardware profiles, SysEx summaries, and expression counts, but it must never become permission to send SysEx, bank/program changes, clock, transport, or raw thru data.

  • MidiProjectHealthSummary::toPlainTextNextSteps() is the compact musician-facing MIDI checklist. Do not duplicate its wording in PluginEditor or future setup panels; add new recommendation/fallback facts to the model report instead.

  • Focused MIDI Input/Output preflight copy should compose model-owned readiness/runtime/inventory reports plus midiDeviceRepairRecommendationsToPlainTextReport(). Do not fork support/report wording in the editor.

  • MIDI SETUP guide copy should come from MidiHardwareSetupAssistantPlan::toPlainTextReport() and midiHardwareSetupAssistantPlansToPlainTextReport(). The popup is only a renderer/navigation surface; it should not maintain a separate version of hardware setup instructions.

  • MIDI Timing must distinguish readiness from measurement. MidiTimingCalibrationSummary can recommend recorded-timing alignment and report saved compensation, but UI must not claim SpaceAge measured real hardware round-trip delay until an actual loopback/manual pass has produced that data.

  • MIDI Timing is for recorded-event alignment and compensation, not a live-monitoring latency cure. If a controller feels late, first check audio buffer, driver/interface, controller route, duplicate host/direct routing, SoundFont/instrument startup, and external monitoring path; only then apply recorded-timing alignment to align recorded hardware lanes.

  • Hardware-profile timing records are device/profile knowledge, not automatic project mutation. Loading a profile with MidiTimingCalibrationRecord must not silently change global record-latency compensation; the user must explicitly apply or rerun recorded-timing alignment.

  • Saved hardware-profile recorded timing alignment is only trustworthy for a similar audio buffer/device path. If the current audio-block latency differs significantly from the calibration buffer, profile summaries should warn rather than silently implying the saved compensation is still perfect.

  • MidiEvent::fromVar() must rebuild an actual juce::MidiMessage, not only metadata fields. Persisted CC, pitch bend, aftertouch, and SysEx are useless if they cannot render back into a MIDI buffer.

  • 960 PPQ is the canonical MIDI tick base. One SpaceAge 16th-step is currently 240 ticks. Do not introduce another hidden timing unit for MIDI clips, controller automation, or MIDI export.

  • Do not embed full MIDI event arrays directly in ArrangementClip. Use a separate payload store keyed by clip/payload ID, because Arrangement clips are copied, resized, cloned, variant-ed, and serialized frequently.

  • CLONE vs VARIANT must apply to MIDI payloads too: CLONE shares the payload, VARIANT duplicates it.

  • Live MIDI input arming is now processor/project state. Keep future monitor/record/route controls tied to CinematicDrumsAudioProcessor::armedMidiInputLane; do not reintroduce editor-only MIDI routing truth.

  • Live MIDI note input preserves JUCE sample positions and starts voices with delayedStartSamples. If a controller feels 30-50 ms late, first check audio device buffer size, driver mode, interface latency, hardware monitor path, and OS MIDI device path before assuming SpaceAge is adding a full-block scheduling delay.

  • MIDI recording active-note tracking is now channel-aware, which prevents same-pitch notes on different channels from stealing each other's note-off. Piano Roll notes also persist/export their source channel, and safe expression payloads are preserved explicitly. Future work should keep note/expression timing, copy/variant behavior, and export/playback policy aligned.

  • MIDI file import must consume matched note-offs one time per MIDI track. A naive "scan forward to first same-channel/same-note off" breaks overlapping repeated notes by assigning the same off event, length, and release velocity to multiple note-ons.

  • MIDI expression events now live in pattern-level midiExpressionEvents / saved midiExpressionPayloads, not in Arrangement clips. Any operation that clears, duplicates, variants, imports, or replaces a pattern payload must intentionally clear or copy this event list along with steps, Piano Roll notes, and Chord Markers.

  • AUTO LANES is one owner-scoped editor. Quantize/thin/delete must use the selected Shared PTN, Clip Local, or Lane Local identity and never fall back silently.

  • Expression-lane UI must edit through processor helpers (addMidiExpressionEvent, replaceMidiExpressionLane, removeMidiExpressionLane). Raw vector edits would bypass the shared timeline-expression filter, stable sorting, and pattern length policy.

  • Expression-lane scope visuals must use the same timeline mapping as expression authoring. If a grid/playhead/curve preview is drawn, do not let it imply a different snap unit, visible range, or clip-local ownership than the MIDI model actually uses.

  • Step-local expression cleanup must stay scoped to the selected editable expression lane and a half-open tick range. It should not clear review-only setup rows, adjacent steps, all controllers on the channel, or every poly-aftertouch note.

  • Poly-aftertouch lane matching is note-specific. If a user edits aftertouch for C4, do not accidentally delete or replace aftertouch for D4 on the same channel.

  • Keep the timeline-expression storage boundary centralized through spaceage::midi::isTimelineExpressionMessage(), but do not confuse storage/review with playback/export/reporting. Normal playback, MIDI export, and export/readiness expression counts must use isEditableTimelineExpressionMessage() so Bank Select, Program Change, RPN/NRPN, Data Entry, and other setup payload remain review-only until a confirmed Hardware Passport / MIDI PATCH / recall workflow sends them.

  • Raw pattern MIDI export is intentionally pattern-local: drum steps use General MIDI channel 10, Chord Engine markers use channel 1, and Piano Roll notes preserve stored source channels. Arrangement clip/song export is lane-local and should continue to follow lane playback/export channels.

  • MIDI export currently writes the current SpaceAge BPM and fixed 4/4 meter metadata. Do not imply full tempo/meter conductor-map export until SpaceAge has a real project timeline-map model.

  • MIDI export now also writes the current project key signature, but it is still a single song-level metadata snapshot: current BPM, fixed 4/4, current key. It is not a full tempo/meter/key conductor map.

  • Hardware workflows need plain-language guidance: input device, output device, MIDI channel, program/bank, clock, panic, and test note should all be obvious.

  • Hardware profile creation belongs to the lane MIDI routing workflow. Avoid adding a second top-level hardware profile selector that can disagree with lane state.

  • Lane hardware test notes are explicit diagnostics. They may queue a note-on/note-off pair to the lane's chosen hardware route, but they must not mutate project data, arm recording, alter bank/program state, or imply SysEx/profile recall.

  • Lane hardware test note queue attempts should be visible in the hardware log. If the user cannot hear the device, the log should still show whether SpaceAge queued the note-on and note-off messages.

  • Arrangement lanes now own a MIDI output/export channel. Keep this visible in the lane instrument workflow, and avoid adding separate hidden MIDI-channel controls that disagree with lane state.

  • Full Arrangement MIDI export should follow Arrangement Canvas clips and lane channels when clips exist. Pattern-only export can remain pattern-local, but whole-song collaboration/hardware export must respect lanes.

  • The plugin target is MIDI-output-capable for host/hardware routing, but processBlock intentionally clears incoming MIDI after consuming it. Do not add raw MIDI thru unless the user explicitly enables it; accidental MIDI echo would be dangerous with hardware.

  • Live Arrangement playback now emits generated MIDI for drum clips, Piano Roll notes, and Chord Markers on their lane MIDI channels. Chord Marker output shares the internal chord trigger path so arp, strum, playback mode, velocity shaping, and humanize stay aligned with audio playback.

  • SysEx must be treated as a librarian/snapshot feature with explicit confirmation, throttled sending, and a visible log.

  • Hardware profiles and SysEx snapshots now persist as project data. Do not repurpose pattern MIDI expression payloads for device dumps; profile/snapshot storage is the safe home, and actual sending must remain an explicit user action.

  • Attaching a SysEx snapshot to a hardware profile is only recall-plan bookkeeping. It must never send bytes, trigger recall, or imply that project load/lane selection should configure external hardware.

  • Live MIDI note-offs need the same sample-position discipline as note-ons. If note-offs are handled immediately while note-ons are delayed to their buffer sample position, very short notes and same-buffer note pairs can feel clipped, early, or inconsistent.

  • Count-in recording can activate midway through an audio block. When that happens, pre-activation MIDI events in the same buffer must not be recorded, and post-activation events must calculate step position from the activation sample, not from stale block-start sequencer state.

  • MIDI recording handlers should only consume note-off events that match an actively recorded note. If an early note-on is monitored before the count-in capture window but its note-off lands after recording opens, the note-off still needs to fall through to live playback release.

  • Incoming live MIDI expression should not be applied once at block start if it has a later sample position. Keep pitch bend, mod wheel, sustain, channel volume/expression, pan, and reset-all-controllers moving through the incoming expression queue so live performance gestures land inside the block.

  • Bank Select and Program Change are allowed timeline MIDI expression events, but they must not silently mutate SpaceAge internal presets. Import assistants may offer to convert them into hardware profile defaults later, but that must be a visible user decision.

  • The MIDI Patch Settings panel is a selected-pattern inspector for Bank Select and Program Change rows. Its hardware-profile conversion button may save reviewed rows as recall recipes, but it must never send hardware data, change internal presets, or attach a profile to a lane without a later explicit user action.

  • Hardware profiles created from MIDI PATCH rows are recall recipes, not proof that a hardware device is configured. They may intentionally lack an output device until the user assigns one in the lane/hardware routing workflow.

  • MIDI Clock, Start/Stop/Continue, Song Position Pointer, Song Select, MTC, and MMC are sync/status/transport messages, not clip expression data. Do not auto-record or auto-play them through piano-roll expression payloads.

  • MIDI Song Select is not Song Position Pointer. Observe and report F3 nn as external-device song-selection status, but do not let it chase the SpaceAge timeline, switch a project, become timeline payload, or trigger Arrangement mutation without a future explicit hardware workflow.

  • MIDI Sync monitor counters are diagnostics only. Refreshing or resetting them must not alter playback state, sync policy, hardware profile routing, or queued MIDI output.

  • MIDI Sync authority must stay split into three readable questions: who controls tempo, who controls timeline position, and who controls play/stop transport? Do not collapse Host Sync, MIDI Clock, SPP, MTC, and MMC into a single vague "sync enabled" flag.

  • MIDI transport chase must remain strictly gated. A profile that says chaseIncomingTransport is not enough. Clock-family Start/Continue/Stop chase requires receive-clock mode, a real input device, and clock response. Song Position Pointer chase additionally requires explicit SPP capability.

  • MIDI transport edge chase is sample-position sensitive. Do not make incoming Start/Continue/Stop/MMC Play/Pause/Stop mutate the whole audio block from sample 0 when JUCE supplied a later event sample; preserve the in-block boundary so external transport feels tight.

  • Incoming MTC chase is a separate gate from MIDI Clock and MMC. MTC full-frame and complete 0-7 quarter-frame packets may set transport position only when a profile has an input device, chaseIncomingTransport, and respondsToMtc. MTC does not authorize tempo-following or start/stop by itself; it is timecode position, not musical pulse.

  • Incoming MMC chase is a separate gate from MIDI Clock chase. MMC Play, Deferred Play, Stop, and Pause can act only when a profile has an input device, chaseIncomingTransport, and respondsToMmc. Do not require Receive MIDI Clock for MMC-only chase, and do not let unsupported MMC commands such as Fast Forward, Rewind, or Record mutate transport until a deliberate design exists.

  • Incoming MIDI Clock tempo-following is smoothed and strictly gated. It requires a receive-clock-ready hardware profile and Host Sync off; it should never let a random MIDI Clock stream yank project BPM merely because pulses are observed. Start/Continue/Stop/SPP chase, MTC chase, MMC chase, and clock-tempo follow are separate features and should remain separately reported.

  • Hardware sync capability fields describe what a device can understand; they do not authorize transmission. Keep "can follow Clock/SPP/MTC/MMC" separate from "send this now" in every future UI.

  • Hardware profile sync-policy editing must remain a profile mutation only. Changing Clock/SPP/MTC/MMC capability fields, send-transport, or chase-transport should refresh readiness and reports, but must not send MIDI Clock, Start/Stop/Continue, SPP, MTC, MMC, bank/program recall, SysEx, or test notes.

  • If a hardware profile is saved as Send MIDI Clock, allowClockSend may become true because that is the explicit sender role. Other clock modes should not keep stale clock-send authorization alive.

  • MIDI transport plans are preview objects. buildTransportPlanForProfile() can tell the UI what would be sent, but it must not become a hidden project-load, lane-selection, or playback-start side effect.

  • Use buildHardwareTransportPlan() for customer-facing transport previews so profile lookup, effective tempo, sample rate, and validation warnings stay centralized.

  • Hardware profile sendPanicOnStop belongs in the stop transport plan, not in a separate stop-code branch. Preview and queue behavior must remain identical.

  • Hardware-output sample offsets are only musically meaningful if they are anchored to a shared audio-block time origin. Do not turn per-event sampleOffset into "schedule this message a couple milliseconds after the router thread wakes"; that converts sample timing into thread jitter.

  • Incoming transport chase messages must honor the MIDI event's sample position inside the audio block. Start/Continue, Stop/MMC Stop/Pause, and first-pass running SPP/MTC position jumps now use in-block sample boundaries. Do not collapse this back into whole-buffer mutation; remaining work is real-hardware validation, jitter/offset diagnostics, and fuller user-facing Sync reporting.

  • Count-in recording activation must stay before same-block incoming MIDI note recording is filtered. If activation is discovered only after incoming MIDI has already been handled, the downbeat note can be monitored but not recorded.

  • Live armed input for an externally routed or internal-and-external lane should forward monitor notes and performance expression to hardware as deliberately as sequenced playback does. A lane route that says external but only monitors internal voices will feel broken even when recording itself works.

  • MIDI imports need an inspection/preflight pass before conversion. Use processor-facing planMidiFileImport() for files whenever building UI, and keep inspectMidiFileForImport() / shared helpers such as inspectMidiFile(), inspectMidiMessageSequence(), and inspectMidiEventList() for lower-level diagnostics. The import plan protects against silently importing SysEx, bank/program changes, transport/sync messages, unknown messages, and multi-channel data into the wrong place.

  • MIDI import timing metadata is not permission to mutate the project. PPQ, SMPTE/timecode timing, tempo rows, meter rows, and key rows should be shown in review first; adopting a source tempo map or key/meter map must remain a visible user decision.

  • MIDI Import Wizard first-value adoption is real only for first source tempo and first source key. Source meter and full tempo/key maps are still review-only until SpaceAge has an undo-safe project timeline map. Any future full-map adoption must update MidiImportCommitRequest, MidiImportApplyResult, receipt copy, tests, and undo/project-tempo behavior together.

  • First source tempo/key means earliest-by-tick across the MIDI file, not first track/file order. Keep using extractFirstSongMetadataValues() for this; do not reintroduce one-off nested-loop scans that can pick a later track's metadata by accident.

  • MIDI file import must not convert PPQ note timing through seconds when the source tempo map is only being reviewed. Positive-PPQ files should derive SpaceAge step positions from source ticks-per-quarter; SMPTE/timecode files can use JUCE seconds conversion. This keeps variable-tempo source maps visible in receipts without letting review-only conductor data distort imported note positions.

  • Selected-pattern Piano Roll MIDI import and Drum Composer MIDI import are not the same timeline. Piano Roll import must preserve long-form material up to maxPatternSteps; Drum Composer import should stay constrained to the visible 64-step bank unless the Drum Composer UI itself grows a longer bank model.

  • MIDI Import Wizard lane rows should render MidiImportLaneCandidate data from the import plan. Do not re-guess channel roles in the editor; channel 10 drum assumptions, expressive melodic hints, controller-only review, and hardware setup review belong in the shared MIDI layer.

  • MIDI Export Preview rows should render MidiExportReadinessSummary data from the processor. Do not re-count Arrangement clips, pattern payloads, lane channels, Chord Engine markers, or expression events in PluginEditor.

  • MIDI Export action rows should come from spaceage::midi::buildMidiExportJobPlan(). Do not let PluginEditor invent filenames, warning state, or lane-track rows independently of the MIDI backend.

  • MIDI stem package import/repair UI should call spaceage::midi::inspectMidiStemPackage(), then spaceage::midi::buildMidiStemPackageImportPreview(), then spaceage::midi::buildMidiStemPackageImportPlan() when the preview is clean enough. Do not parse manifest.json directly in the editor, and do not treat a successful inspection as permission to mutate the current project without an explicit user action. The preview action rows are the future wizard safety contract; the import plan is the "what would be created?" contract; the commit request is the user-intent contract; the apply result is the receipt/audit contract.

  • Library CHECK / IMPORT MIDI PACKAGE is inspection-first. It remains read-only until a clean import plan enables the explicit confirmed IMPORT STEMS action; do not hide import side effects behind inspection, reveal, or copy-report buttons.

  • Processor-level MIDI package import is now the mutation boundary. UI should call CinematicDrumsAudioProcessor::applyMidiStemPackageImport() only after the user has reviewed the plan and confirmed import. The helper deliberately re-inspects and replans before mutation; do not bypass that safety by trying to import from cached manifest details.

  • Fresh processors may already contain default Arrangement/drum-chain clips. Tests for MIDI package import should assert clip/lane deltas rather than assuming a zero-clip starting project.

  • The package importer intentionally routes each lane stem through the existing single-file MIDI importer, but package import owns the undo checkpoint. Keep that contract intact: one confirmed package import should be reversed by one Undo.

  • MIDI Import Wizard mutation should be represented by a MidiImportCommitRequest, ideally from buildRecommendedMidiImportCommitRequest() or an edited equivalent, and followed by a MidiImportApplyResult receipt. Do not let the wizard mutate the project directly from visible checkboxes without creating a request object that can be logged, tested, and reviewed.

  • MIDI Import Wizard checkboxes must be backend truth, not decoration. If the user disables editable expression, device setup, or SysEx storage, selected-pattern import and split-channel import must both pass those choices through MidiImportCommitRequest and filter the imported payload accordingly.

  • MIDI Import Wizard paged channel rows must preserve row state across pages. Split-channel commit requests should be built from every checked importable channel row, not only the rows currently visible in the compact panel.

  • MIDI Import Wizard destination labels must be derived from current controls, not stale row strings. If the user changes an instrument slot or MIDI output channel in the wizard, the row's displayed destination should update before commit.

  • Every MIDI Import Wizard path that calls applyMidiFileImport() should end in the shared MidiImportApplyResult receipt surface. Selected-pattern import, split-channel import, and inspect-only review should all leave the same kind of visible audit trail.

  • MIDI Import Wizard recipe buttons are safe setup helpers only. They may toggle/import-option controls, but they must not import, create lanes, send hardware data, or mutate project state until the user clicks an explicit commit action.

  • Use buildMidiImportApplyPreview() for the final MIDI Import Wizard checklist. It is a preview only; if a future code path creates lanes or sends hardware data while building the preview, that is a serious safety bug.

  • importPatternMidi() imports safe timeline expression into the destination pattern's midiExpressionEvents. Keep that path tied to isTimelineExpressionMessage() so recording, import, playback, and export do not disagree about what counts as musical expression.

  • MIDI import stores SysEx as confirmation-required SysExSnapshot records and must not add those bytes to clip playback/expression payloads or send them during import.

  • SysEx descriptors are derived from raw snapshot bytes at report time. Do not persist parser conclusions as authoritative state; future parser improvements should reclassify existing snapshots automatically.

  • SysEx Vault clipboard/support reports belong in the MIDI model layer. Do not recreate snapshot byte counts, safety labels, confirmation policy, checksum lines, or preview text in PluginEditor; use SysExSnapshotSummary::toPlainTextReport() and sysExSnapshotSummariesToPlainTextReport().

  • SysEx Librarian readiness belongs in SysExLibrarianReadinessPlan. Future UI should use its no-autosend safety label, next action, product gap, and checklist instead of treating a first-pass vault report as a finished capture/restore cockpit.

  • Armed SysEx live capture should stay on the bounded runtime queue. Do not rebuild review receipts or allocate large MemoryBlock objects directly from the incoming live MIDI handler; future batch capture/progress UI should observe the queue/receipt state instead of bypassing it.

  • Armed SysEx live capture has a 60-second listening window. If it times out, it should disarm and report that state; do not leave capture armed indefinitely or accept late dumps as if the user had just requested them.

  • SysEx capture source guidance belongs in the MIDI model/readiness report, not in one-off editor strings. Deeper device-specific guidance should extend the Hardware Passport/device-template layer later.

  • Hardware recall planning is not hardware recall sending. buildHardwareRecallPlan() can assemble bank/program/SysEx messages for display and validation, but any actual send must live in a separate confirmed, throttled, logged workflow.

  • Hardware profile validation belongs in validateHardwareMidiProfile(). Keep future UI warnings, archive preflight, and hardware recall readiness based on that shared helper so the app does not disagree with itself.

  • queueConfirmedHardwareRecall() is the only backend path that should send saved hardware recall plans. It must remain explicit, confirmed, validated, and separate from project load, lane selection, route normalization, and Arrangement playback.

  • HardwareRecallQueueProgress reports SpaceAge-side queue state only. Its phase/trust/next-action labels are useful for UI and support, but they must not be reworded as external hardware acknowledgement, transfer success, or device acceptance.

  • The hardware recall log is for explicit profile recall attempts and lane hardware test-note attempts only. Do not fill it with generated note playback or raw MIDI thru events, or the future hardware troubleshooting UI will become noise instead of signal.

  • MIDI guitar and wind controllers need special profiles because they generate expressive data differently from keyboards.

  • MIDI 2.0 should remain on the radar, but MIDI 1.0 compatibility and clean CC/channel workflows come first.

  • MIDI Protocol Coverage's productReadinessPercent() is intentionally the current MIDI 1.0 finish-line readiness score. Do not include deferred MPE/MIDI 2.0 roadmap families in that score unless they become an explicit release target with their own readiness model.

  • MIDI Protocol Coverage's backendFamilyPercent() is not the high-90s backend-foundation estimate. It is a tracked dashboard-family score that includes polish/roadmap rows in its denominator, so label it as family/backend-row readiness rather than raw backend foundation.

  • Do not draw productReadinessPercent() and currentFinishLineReadinessPercent() as separate progress meters unless they actually represent separate promises. Right now, product readiness is the current MIDI 1.0 finish line, so the UI should show one readiness meter plus concrete blocker/deferred counts.

  • Broad MIDI Protocol Coverage families should split once a working core and unfinished launch polish both exist. Do not let rows such as SysEx or MIDI export imply "all missing" or "all finished" when the true state is core-safe but still waiting on device-specific librarian polish, package repair, progress bars, or broader validation.

  • MIDI Protocol Coverage row clicks are navigation only. They may open MIDI AUTO, MIDI PATCH, SysEx Vault, MIDI Setup, MIDI Maps, Hardware Passport, or show a status hint for Import/Export/Roadmap, but they must not send hardware MIDI, import files, export files, or mutate project data.

  • MPE must be treated as a named lane/device policy, not inferred from random multi-channel expression. Do not call SpaceAge "MPE-ready" until zones, master/member channels, per-note pitch/timbre/pressure ownership, bend ranges, import/export validation, and synth response are all implemented and tested.

  • MPE-like import detection is an inspection/review feature, not true MPE interpretation. It should preserve multi-channel bend/pressure/CC74 as MIDI 1.0 expression, mark the Expression import section as review-required before expression-preserving import, and explain the limitation instead of silently flattening the data or pretending true MPE zones exist.

  • MIDI Health's MidiMpeReadinessPolicy is the single report-owned truth for MPE readiness. Future UI badges, support reports, and release notes should read that object instead of rewording MPE support from scattered expression counters. Keep its trueMpeRemainingWork checklist concrete until true zones, channel ownership, bend ranges, synth response, and validation tests exist.

  • Pitch bend and mod wheel are channel-scoped performance state. Do not treat them as pad-global or selected-clip-global data; future automation clips should write into the same expression layer so live controllers and drawn automation agree.

  • RPN/NRPN and Data Entry messages are setup/controller-parameter traffic, not normal learnable knobs. Preserve and order them, but keep selector/channel-mode messages out of MIDI Learn unless a future explicit hardware setup editor is active.

  • Channel-mode CCs such as Local Control, Omni, Mono, and Poly can change external hardware behavior. Treat them like device setup data, not ordinary expression.

  • SysEx paced restore progress is not device acknowledgement. Generic MIDI Sample Dump-style ACK/NAK/WAIT/CANCEL parsing now exists, but until SpaceAge implements response parsing for a given hardware profile, reports and structured Health payloads must say estimated-only progress and require manual verification before claiming that specific device accepted the dump.

  • SysEx Vault UI must keep the no-autosend / estimated-restore-trust warning visible near capture and snapshot actions. Do not bury the hardware-ACK boundary only in MIDI Health or support reports.

  • SysEx selected-snapshot closeout is read-only/copy-only evidence. It may show manual verification, device ACK/NAK, timeout, archive state, confirmation policy, and Hardware Passport identity warnings, but it must not call recall/send paths or imply that estimated timing proves hardware acceptance.

  • Default internal pitch-bend response is currently +/-2 semitones, but MIDI AUTO stores imported/drawn pitch bend as raw 14-bit data centered at 8192. The MIDI AUTO graph now shows SpaceAge's internal default or RPN-learned range; still expose per-lane/per-instrument/per-hardware bend policy before promising hardware-perfect expression, MIDI guitar, wind-controller, or MPE workflows.

  • Mod wheel currently performs a conservative musical vibrato gesture on tonal engines. Future CC routing should let CC1 be reassigned without breaking that default behavior.

  • MIDI expression editor wording belongs in MidiExpressionEditorPlan. Do not hardcode separate pitch-bend, sustain, pressure, CC value, preview-density, or truncation language in PluginEditor, or copied reports, MIDI AUTO scope drawing, and future Piano Roll expression lanes will drift apart.

  • MIDI expression lane ownership/density wording belongs in MidiExpressionLaneSummary. If future UI needs to know whether a lane is clip-local, review-only, dense, sparse, stacked, truncated, bipolar, or switch-style, ask the model instead of reclassifying raw controller events in PluginEditor.

  • MIDI Health expression counts should be derived from summariseMidiExpressionEvents(). Do not create a second classifier for drawable/review-only/switch/bipolar/pitch/pressure lanes, or MIDI Health, MIDI AUTO, import reviews, and future expression lanes will disagree.

  • MIDI Health expression copy should come from MidiExpressionProjectSummary::toPlainTextReport(). Do not let COPY STEPS, COPY REPORT, MIDI AUTO, and future Piano Roll expression lanes each invent separate advice for drawable, review-only, pitch, pressure, switch, dense, or shared-pattern expression data.

  • A first-pass MIDI expression editor surface is not the same as a product-finished expression editor. Keep hasFirstPassEditorSurface, productReadyEditorSurface, editorSurfaceLabel, and productGapLabel honest so protocol reports, copied expression receipts, and future Piano Roll-grade expression lanes do not overclaim what exists.

  • Expression Editor release gaps should roll up through MidiExpressionProjectSummary::expressionEditorCloseoutChecklist. Future UI should add or consume checklist items through MidiExpressionEditorPlan instead of scattering "still needs handles/lasso/clip-local ownership" wording across panels.

  • Recorded MIDI expression commits should stay on MidiExpressionRecordingRuntimeQueue. Do not append/sort midiExpressionEvents directly from the incoming live MIDI handler; future controller automation capture should reuse the same off-callback commit pattern.

  • Completed MIDI note commits should stay on MidiNoteRecordingRuntimeQueue. The live path may track note starts and monitor voices, but durable PianoNote insertion, pattern-length updates, and last-recorded-note feedback should not return to direct live-handler vector mutation.

  • MIDI Health is the user-facing place for runtime MIDI queue pressure. If a new live-safe queue is added, give it a dropped/blocked counter and surface it in MidiProjectHealthSummary instead of letting overflow become silent "MIDI weirdness."

  • MIDI Input source policies are persisted and reported as string ids (autoPreferDirect, hostOnly, directOnly, hostAndDirect), not enum integers. Repair recommendations, reports, and UI helpers must compare those ids as strings or the customer-facing guidance will fall back to generic "source policy is filtering input" text even though the routing engine did the right thing.

  • Do not run APVTS repair or host-notifying parameter cleanup from startVoice() or any incoming live MIDI note path. Parameter sanitization belongs on load/reset/paste/edit boundaries, not at the instant a musician presses a key.

  • SoundFont note-on should borrow from the prepared rack and avoid soundFontMutex. Per-note TinySoundFont copying must not return to the live note-start path; if MIDI Health shows refused live voice starts during ordinary playing, tune/prewarm the rack before treating the latency issue as external hardware or driver delay.

  • Live MIDI monitoring should treat the armed lane as the performance destination and selected clips as editing context. If the armed lane is routed External or Internal+External, note and performance-expression input should be mirrored to that lane's output at the incoming sample position. Do not reintroduce a parallel live audition path that ignores lane route target, forced output channel, or external-only monitoring.

  • MIDI Input readiness copy must distinguish deterministic armed-lane routing from fallback audition behavior. "Selected clip/lane" is editing context, not a guaranteed live performance destination. MIDI Learn currently captures messages after live input filtering, and saved physical input-device names are visibility/identity hints until a source-aware input manager can enforce per-event device identity.

  • MIDI Input panels need a direct live-routing verdict, not only counters. If a user asks "why is my controller silent?", the first answer should be host/plugin MIDI active, direct physical MIDI active, both routes active, missing selected device, or no armed lane. Do not force users to infer that from host/direct counts and policy ids.

  • Live MIDI recording must keep armed-lane performance destination and shared PTN edit target visibly aligned or explicitly explained. If a user can hear/play one lane but durable notes land in another selected pattern, the UI will feel broken even when the backend is doing exactly what it was told.

  • Direct physical MIDI sync/chase must be source-scoped to a ready Hardware Passport. Global "some device is ready" counts are fine for reports, but a MIDI Clock, SPP, MTC, MMC, Start, Continue, or Stop packet from one physical input must not move SpaceAge just because a different Hardware Passport is configured for sync.

  • Protected channel-mode/reset messages should be review-persistable but never ordinary performance expression. Local Control, Omni, Mono/Poly, All Notes Off, All Sound Off, and Reset All Controllers may belong in Hardware Passport/MIDI PATCH review records, but they must stay out of live playback/export unless a future explicit confirmed hardware action sends them.

  • Hardware recall, hardware test notes, and other delayed setup traffic must never sleep or block the live MIDI output router. Delayed recall traffic belongs in the delayed recall queue, immediate recall/transport traffic belongs in the immediate recall queue, and live note/clock output must stay responsive.

  • Live external MIDI monitoring must stay on the fast live-thru route. Do not send live-played notes, bend, pressure, mod wheel, sustain, or expression back through the generic pending-output scheduler unless plugin MIDI output specifically needs a buffered copy. Sequenced playback and recall traffic can be scheduled; live fingers should not feel scheduled.

  • Hardware MIDI output queue-age diagnostics are there to catch SpaceAge-side router pressure, not to justify adding musical compensation. If output queue age spikes, simplify the hardware route, check driver/device stalls, and avoid dense recall/setup traffic while live-playing before touching recorded-timing alignment.

  • Hardware lane TEST NOTE actions go through the same confirmed plan/receipt path as MIDI SETUP test notes. Do not add a new direct button path that queues hardware notes without showing destination, confirmation state, blocked reasons, and the "not sent" safety families.

  • A not-yet-due delayed recall item should make the router yield briefly, not busy-spin. If delayed hardware traffic raises idle CPU, check MidiHardwareOutputRouter::drainQueue() / run() before blaming synth DSP.

  • Timing-calibration pulse trains need absolute due-time scheduling, not repeated relative note-on/note-off delays. If every pulse says "note off after duration" instead of "note off at pulse start + duration," later calibration pulses can collapse together and produce useless loopback measurements.

  • Panic must clear both SpaceAge-owned hardware output FIFOs and JUCE MidiOutput scheduled messages before it sends reset traffic. It should target every open hardware output device, not only devices currently routed from lanes, because setup/test/calibration ports can be open without being active lane destinations.

  • Queued hardware messages must carry the output-slot generation that existed when they were queued. If a device slot has been closed, reopened, or replaced before the message drains, refuse the stale message instead of sending it to whatever hardware happens to occupy that slot now.

  • Mixer sends are mix/routing state, not synth patch state. Native patch save/load, factory preset loading, and Clear Changed Values should not write halostarsend, roomsend, delaysend, chorussend, flangersend, phasersend, tremolosend, or octavesend unless the command is explicitly a mixer/channel-strip command.

  • Instrument pages may expose internal character effects only when they are part of the voice. Shared-return sends belong in the Mixer/Lane channel strip, or users will not know whether a preset change or a mixer move changed the ambience.

  • AUTO LANES can target Shared PTN, Clip Local, or Lane Local data. UI, reports, and support wording must always name the selected owner.

  • Pitch-bend trust is destination-owned. A raw 14-bit bend curve is not enough to promise correct playback on external hardware; the lane route and Hardware Passport bend-range policy must be visible and trusted.

  • SysEx Vault readiness must keep the no-autosend and estimated-restore boundary visible near the Vault actions. Do not bury that warning only in copied reports or MIDI Health.

  • SysEx restore evidence has tiers. Stored bytes, queued sends, device ACK/NAK/WAIT/CANCEL responses, and manual verification each prove different things; do not collapse them into a vague "restored" claim.

  • MIDI Output Preflight next-action guidance is read-only. Do not turn that row into an implicit auto-repair, output-open, route-change, or hardware-send path; explicit buttons and confirmations must continue to own state changes.

  • MIDI Output CHECK OUTPUT is a scan/refresh only. It may rebuild readiness, runtime, inventory, repair recommendations, and trust labels, but it must not send MIDI, open a hardware port, attach a Hardware Passport, or rewrite a lane route.

  • MIDI Health action buttons must be rebuilt whenever the health snapshot changes. Do not let a timer refresh update the warning text while old repair buttons still point at stale recommendations.

  • MIDI Health CHECK HEALTH is a scan/refresh only. It may rebuild health, trust labels, counters, and repair buttons, but it must not send MIDI, open ports, change routing, import/export files, or mutate project state.

  • MIDI Health FIX NEXT is navigation only. It may close the Health panel and open the current MIDI closeout surface from protocol coverage, but it must not send MIDI, open hardware ports by itself, change routing, import/export files, or mutate project data.

  • AUTO LANES ROW VIEW is layout-only. It gives the selected expression row more graph space and must never change the selected owner. Shared PTN still uses Variant/Ack Shared when linked.

  • MIDI SETUP direct-door buttons are navigation only, including the new HEALTH door. They may close the guide and open MIDI Input, MIDI Out, Hardware, Timing, Health, MIDI PATCH, SysEx Vault, or MIDI AUTO, but they must not arm lanes, rewrite routes, open hardware ports by themselves, send test notes, recall programs, transmit SysEx, or mutate project data.

  • MIDI AUTO OPEN MIDI PATCH is also navigation only. It is visible for review-only setup rows so protected bank/program/RPN/NRPN data has an obvious home, but it must not transmit setup data, queue recall, change lane routes, or rewrite the pattern.

  • Hardware Passport COPY PLAN is review-only. It may copy program-only and program-plus-SysEx recall plans, warnings, transfer estimates, and message lists, but it must not call queueConfirmedHardwareRecall(), open hardware ports, or transmit MIDI.

  • MIDI package export has a real cancellable ThreadWithProgressWindow. MIDI package import is intentionally a message-thread stem-by-stem flow: each timer step imports one lane stem, updates the panel receipt, and can stop before the next stem. Do not move package import onto a worker thread unless lane/clip creation, checkpoints, UI refresh, and undo receipt handling are explicitly staged through a safe transaction boundary.

  • MIDI package RECHECK is scan-only. It may rebuild package inspection, preview, plan, report text, repair-step availability, reveal availability, and import-button readiness, but it must not copy files, alter the manifest, import stems, or mutate the current project.

  • MIDI package import is a temporary guarded transaction. While it is running, block undo/redo, project/archive/recovery loads, file drops, selected MIDI import, and new package inspections. Do not let a JUCE callout outside-click or a second confirmation path create a half-imported state that the user can accidentally mutate around.

  • Protocol Coverage action verbs must stay honest. Use Open only for a real guarded surface. Use Review, Show, or status text for import/export/roadmap rows that do not open a dedicated panel yet.

  • MIDI AUTO copied reports should mirror on-screen pitch-bend trust cues. Do not let copied support text imply a bend curve is safe for external hardware unless the selected pattern resolves to one destination lane and that hardware route has a Hardware Passport bend-range policy.

  • Arrangement AUTO badges must require real clip/pattern context. Do not let an empty lane open MIDI AUTO by falling back to the globally selected PTN, because that makes automation ownership feel random and can teach the user the wrong lane/pattern relationship.

  • Arrangement AUTO discoverability should be clip-contextual, not selection-hidden. A lane that contains real clips may show the stable AUTO doorway even before automation exists; empty lanes should not show the door. Tooltips and the opened editor should explain whether the click creates the first row or opens existing rows.

  • Arrangement AUTO badges summarize effective clip context. They are not the owner selector; ownership must be chosen and shown inside AUTO LANES.

  • Arrangement AUTO badge counts should deduplicate repeated clips that share the same PTN on one lane. Count the automation source once per lane/pattern, not once per visible clip clone, or the badge will exaggerate how much automation really exists.

  • Direct physical MIDI queue-age warnings must use a musician-feel threshold, not a permissive engineering threshold. Peaks around 12 ms or higher should be visible in MIDI Health/Input diagnostics because a 30-50 ms monitoring delay is absolutely user-facing even if the app is still technically passing messages.

  • Arrangement MIDI recording placement must be lane-owned. Do not use total arrangement clip count to decide whether a new recording clip should start at the playhead or lane end; a populated bass lane and an empty melody lane are different musical situations even though the arrangement has clips somewhere.

  • Arrangement lane MIDI recording state must be visible on the lane itself. Top transport status is not enough; the armed lane button should distinguish armed live input, count-in, and active recording so a performer can trust where incoming notes will land.

  • MIDI Health/status copy must describe the same lane-owned truth as the runtime path. Armed Drums lanes monitor/record through pad mapping; armed melodic lanes monitor/record through the lane Instrument; selected clips and selected lanes are editing context unless the recording resolver deliberately chooses or creates a clip on the armed lane.

Save / Archive / Release Gotchas

  • Project archives should package referenced samples, loops, SoundFonts when appropriate, and enough manifest data to repair paths.
  • Recovery snapshots must be visible and understandable without becoming the main save workflow.
  • Final release builds should embed curated splash art and not expose Shane's personal folders or Google Drive paths.
  • GPL code is off-limits for product code unless we intentionally change the licensing strategy. Prefer MIT/BSD/permissive or original implementation.
  • Keep GitHub pushed after meaningful code checkpoints.

Test Pass Checklist Before Big Releases

  • Open an existing project and verify Arranger opens fitted/centered.
  • Switch mixer banks while drums and melodic lanes play; verify no channel goes silent and sends remain unchanged.
  • Save/load Redshift and SoundFont patches; verify new parameters recall.
  • Load a large SoundFont; drag notes in Piano Roll; verify no crash or stuck notes.
  • Copy/paste mixed lane clip selections at playhead and into silent gaps.
  • Delete clips and confirm timeline space behaves as expected.
  • Use Chord Engine suggestions, arp, render/reference modes, and MIDI export.
  • Clear a pad, load a preset, and verify no stale samples/effects/tabs remain.
  • Export audio, MIDI, stems, and project archive from a non-trivial arrangement.

Hardware Editor Profiles Need Real Evidence

  • Do not expose Yamaha XG, Roland GS, or other vintage hardware editor controls from vibes, similar-device memory, or product names alone.

  • A control that sends CC, RPN, NRPN, SysEx parameter changes, resets, or dumps must be backed by the exact device manual/Data List/MIDI Implementation Chart whenever possible.

  • QY-70/QY-100, CBX-K1XG, MU-family, Sound Canvas, and GS-family devices may share broad conventions, but device-specific addresses, model IDs, checksums, and effect tables can differ.

  • Hardware editor pages should remain read-only or disabled until their Hardware Passport/profile says the relevant message family is verified.

  • Any setup-changing send must show a receipt and stay separate from normal live performance forwarding.

  • Direct physical MIDI must have a default-open path in standalone. If the router only opens lane-referenced devices, a user with no lane-specific input assignment can get no live notes, no chord readout, no MIDI Learn, and no useful feedback. Lane-specific device selection should narrow input; it should not be the prerequisite for any controller input at all.

2026-07-12 17:20 - MIDI input source policy gotcha

Never use direct MIDI device availability as proof that direct MIDI traffic is functioning. Auto mode may only prefer direct after real direct messages have arrived; otherwise host/plugin MIDI can be accidentally silenced.

2026-07-12 18:59 - Live MIDI duplicate-source lag gotcha

In standalone Auto mode, do not let host/plugin MIDI and SpaceAge-opened direct MIDI both monitor the same controller unless the user explicitly chooses that behavior. The direct queue can arrive a block later and feel like input lag or a doubled/flam note.

2026-07-12 19:16 - MIDI two-controller Auto policy refinement

  • Changed MIDI Auto source arbitration so host/direct duplicate suppression is per note event instead of global. Direct MIDI is no longer suppressed merely because any host MIDI has appeared, which should allow two simultaneous controllers to coexist.
  • Added a short host-note timestamp window to suppress likely duplicate host/direct echoes of the same note/channel/kind within 25 ms when no direct lane device has been intentionally selected.
  • Built test package: SPACEAGE_MIDI_TWO_CONTROLLERS_20260712-191625.zip.

2026-07-12 19:24 - MIDI multi-controller source routing fix

  • Fixed a two-controller regression by making MIDI Auto source arbitration message-specific instead of stream-global. Host/direct duplicate suppression now compares recent host and direct note timestamps in both directions, rather than muting all host or all direct input after one source appears.
  • Direct physical MIDI discovery now opens all visible input devices. Lane-specific input-device choices are still enforced later in the live-routing gate, but discovery no longer makes a second connected controller disappear because another lane named a controller.
  • Built test package: SPACEAGE_MIDI_MULTI_CONTROLLER_FIX_20260712-192341.zip.

2026-07-12 - Gotcha: Pattern Time Is Not Arrangement Time

MIDI recording inside the Arrangement Canvas must translate incoming sample positions through the Arrangement timeline first, then into the owning clip's pattern-local source range. Using only currentStep/pattern-local timing causes notes played later in the song to collapse into the first measure of the pattern. Any future record, overdub, punch-in, or capture feature must carry both blockStartStep and blockStartArrangementStep through the recording path.

  • Direct physical MIDI late-drain eligibility must include harmless status chatter such as MIDI Clock and Active Sense. Otherwise a status byte at the front of the FIFO can block same-block live notes/controllers behind it, creating intermittent felt lag even though the main drain path is correct.

2026-07-12 23:58:05 - MIDI AUTO source policy must not eat a second controller

AUTO duplicate suppression is only safe when SpaceAge knows the selected lane's direct physical input device. Note/channel/time alone is not enough identity: two different controllers can play the same note/channel within the duplicate window. If no lane-specific direct input is pinned, pass both streams and warn rather than suppressing one.

2026-07-13 00:08:45 - Direct MIDI record timing sign matters

For physical MIDI callback messages, record timing and queue-age timing are opposite questions. Record placement asks how far after the current block start the callback arrived; queue age asks how long it waited before the audio block drained it. Reversing those signs silently clamps fresh late-drain notes to sample 0 and makes recorded takes feel wrong.

2026-07-13 00:17:36 - MIDI diagnostic text density

  • Gotcha: MIDI Health/Input/Hardware panels accumulate long safety strings quickly. Any future MIDI closeout row should be tested against small app heights and long device names, not just the ideal desktop layout.

MIDI Arrangement Recording Playhead Start

  • setSequencerRunning(true) intentionally resets playback to the beginning or active loop start. Arrangement recording/playback tests that need to honor a user-placed playhead must call setSequencerAbsoluteStep(...) followed by setSequencerRunningFromCurrent(true).
  • Regression coverage should include real processBlock() recording, not only testHandleIncomingMidiMessageFromSource(...), because the helper path can prove clip-local math while missing transport-start reset behavior.

MIDI AUTO Source Policy and Multiple Controllers

  • In AUTO mode, lane-selected direct input should be treated as authoritative once seen. Do not allow source-agnostic host/plugin musical messages to leak into that armed lane while the selected direct input is active.
  • Duplicate-note suppression alone is not enough: a second host-routed controller may send different notes and still create unwanted recording unless host spill is suppressed.

2026-07-13 - MIDI Recording Length Must Not Depend Only On Playback Snapshots

When recording into an Arrangement clip immediately after editing clips/lanes, the playback snapshot may briefly trail the source-of-truth arrangement data. MIDI recording length should inspect current Arrangement clips first, then use the playback snapshot as fallback. Otherwise notes can land correctly while the pattern/clip length reports an older shorter value.

2026-07-13: Pattern-Only Automation Previews Are Misleading

If a Piano Roll editor is opened from an Arrangement clip, never feed its automation preview from selectedPattern alone. That hides lane-local and clip-local automation and makes the UI contradict playback. Use getMidiExpressionLaneSummariesForArrangementClip(selectedArrangementClip) whenever hasCurrentArrangementClip is true.

2026-07-13: Focused MIDI Timing Test Had One Transient Length Mismatch

After the Arrangement AUTO badge pass, the first focused MIDI timing run reported the Arrangement processBlock record case at patternLength=48 instead of 64, while the note start/length were correct. Two immediate reruns passed all six focused checks. Treat this as a watch item: if it appears again, inspect stale recording target snapshots, pattern length mutation order, and any test isolation assumptions before calling the recording backend fully stable.

  • Lane-level AUTO LANES targeting follows intent order: selected real clip in the lane, clip under the Arrangement playhead, then first real clip in the lane as a fallback. Do not reopen AUTO LANES against an arbitrary first clip if the user has a clearer selected/playhead context.

2026-07-13 - Direct MIDI Queue Chatter Can Feel Like Lag

When diagnosing live MIDI lag, do not only inspect recorded note timing. A physical MIDI input can feel late if status/controller/setup chatter sits ahead of a note in the direct-input queue and misses the final before-render drain. Keep the late drain bounded, preserve normal handling for non-performance messages, and test both USB and DIN controllers because interface buffering can differ substantially.

  • MIDI Health and Hardware settings pages can regress visually even when backend readiness is correct. Keep tall diagnostic panels, compact row typography, and focused SPACEAGE_MIDI_*_ONLY test doors in the closeout loop before calling MIDI Settings customer-ready.

2026-07-13 - MIDI Direct Queue Chatter Gotcha

  • A live hardware controller can send CC, pitch bend, active sensing, clock, or other status traffic immediately before notes. The direct-input queue must drain bounded batches, not one message at a time, or a real note can feel one audio block late.
  • Keep regression coverage around physical-queue behavior, not only handleIncomingMidiMessageFromSource() bypass tests.

2026-07-13 - MIDI Multi-Controller Gotcha

  • Two controllers can legitimately arrive at SpaceAge at once, especially when one is USB and another is routed through a DIN interface such as a mioXL.
  • Lane recording must honor the lane's pinned input device before treating matching MIDI channels as valid. MIDI channel alone is not enough to separate controllers.
  • Keep direct-input regressions around same-channel, same-block controller collisions so future routing work does not reintroduce cross-controller recording surprises.

2026-07-13 - Expression Must Obey Physical Input Pinning

When multiple controllers are connected, note events are not the only danger. CC, pitch bend, aftertouch, and other expression events can be just as destructive if they land on the wrong lane. Any future live-MIDI recording path must filter expression by the same physical input route and channel used for notes.

2026-07-13 - MIDI Recording Must Preserve Non-Editable Setup

Do not gate live recording only through editable automation checks. Bank Select, Program Change, RPN/NRPN selectors, and Data Entry messages are not normal draw-able curves, but they are still MIDI timeline payload and must be preserved during recording/import/export workflows where hardware or GM/XG-style setup matters.

  • Piano Roll and Arrangement AUTO summaries may show effective Shared PTN, Lane Local, and Clip Local automation. Editing copy must distinguish effective visibility from the currently selected write owner.

2026-07-13 - Count-In Note-Offs Must Not Invent Notes

  • If a performer presses a key before the count-in recording window opens and releases it after the window opens, the release should clean up live monitoring only. It must not create a tiny phantom note at the activation boundary.

Long Drum Recording Must Not Write Past The 64-Step Grid

The Drum Composer step grid is physically 64 steps wide. For Arrangement drum clips longer than 64 steps, do not clamp or modulo recorded hits into the grid. Store them as note-backed drum events with the originating pad preserved, because playback already supports that path.

2026-07-14 05:09:57 - MIDI timing gotcha

  • A direct MIDI input can feel late even when SpaceAge records the right musical tick if the hardware path adds delay before JUCE receives the message. DIN through an interface, interface merge/filter settings, Windows MIDI drivers, and audio buffer size can all add latency outside the app. SpaceAge now drains direct input twice per block to avoid adding an avoidable extra block internally.

2026-07-14 05:23:18 - MIDI recording worker boundary

  • Live MIDI recording should not mutate Piano Roll notes, drum steps, or expression vectors directly from the audio callback. The callback should calculate timing, monitor voices, and enqueue commits; worker queues then update pattern/project state. This keeps live feel separate from project-data mutation and reduces glitch/lag risk during dense takes.

2026-07-14 05:34:23 - Dropped recording queue events must be loud

  • If MIDI note/drum or expression recording queues drop events, treat the take as untrusted and surface it in MIDI Health. Silent counters are not enough because the user's symptom will be missing notes, bad timing, or missing AUTO LANES gestures.

2026-07-14 05:39:00 - Health fields must be wired to runtime counters

  • Adding fields to MidiProjectHealthSummary is not enough. Verify the processor actually populates them from live runtime queues, and verify the UI/report path exposes the values where a user can act on them.

2026-07-14 05:45 - MIDI REC must trust armed lane before selected clip

  • Arrangement recording has two concepts that can diverge: edit selection and live-input target. REC prep must determine drum-vs-melodic behavior from the armed lane first. A selected/resolved clip is only a fallback, otherwise explicit playhead recording can accidentally use the wrong sequencer assumptions.

2026-07-14 - Arranger Clipboards Must Own Automation Snapshots

  • Never store only a source clip ID for copied clip-local AUTO LANES. Copy is a musical snapshot: later source edits or deletion must not alter what Paste restores.
  • Split is a different contract from clone. A split must partition and translate automation into each child range; copying the complete payload to every child can resurrect sibling automation when a child is later extended.
  • Never key persistent musical payloads by a UI row index. Lane-local automation uses stable lane IDs; row indices are presentation and runtime-routing coordinates only.

2026-07-14 - Broad Self-Test Fixture Lifetime

  • The broad AudioSelfTest is a monolith that retains many heavyweight processor fixtures. Prepared processors each allocate large per-pad delay banks and start MIDI output polling infrastructure.
  • A command timeout may leave the test child alive unless the harness owns and explicitly stops it. Use focused gates for iteration and a controlled process wrapper for broad runs.
  • Refactor broad phases into scopes or functions before treating wall-clock variance as a product regression.

2026-07-14 - Split From An Immutable Automation Snapshot

  • Never partition a multi-piece cut by repeatedly copying from the already-trimmed left child. Snapshot once, then derive every child from that original payload.
  • Clip-local expression ticks remain in pattern/source coordinates; do not rebase them to Arrangement time during a split.
  • Use half-open source ranges [start, end) so an event on the cut belongs to exactly one child.
  • Do not synthesize boundary controller state casually: duplicated Data Entry, RPN/NRPN selectors, sustain, or switch events can change semantics.

2026-07-14 - Restore Owners Before Payloads

  • Deserialize lane/clip payloads into temporary snapshots first. Restore and repair the Arrangement owners before attaching any payload.
  • Duplicate serialized owner IDs must become independent stable IDs. Each repaired owner may inherit the source payload initially, but later edits must not cross-talk.
  • Payload records without a surviving lane or clip owner must be discarded. Invisible orphan automation is project corruption, not useful recovery data.
  • A lane ID describes musical identity; changing lane order, lane height, collapse state, or scroll position must never change that ID.

2026-07-14 - Async recording commits must carry clip length

  • Moving note insertion off the audio callback can silently shorten a long Arrangement clip if the worker infers pattern length only from the recorded note endpoint.
  • Every queued note must carry the minimum pattern length resolved from its owning Arrangement clip. The worker commits the maximum of existing length, clip-required length, and note-inferred length.
  • Prove this with both direct-message and processBlock recording cases; correct note placement alone is insufficient.

2026-07-14 - A queue name does not prove asynchronous behavior

  • A runtime queue that calls its owner synchronously is not a queue and does not protect the audio callback.
  • Verify a bounded FIFO, an independently running consumer, overflow accounting, and callback-side absence of vector insertion/sorting before claiming real-time separation.

2026-07-14 - MIDI note ownership needs source identity

  • Channel plus note is not a complete live-performance identity when multiple controllers are open. A note-off from controller A must not release controller B's matching note or clear its live indicator.
  • Use the direct-input router slot plus device generation as the real-time identity. Do not compare device-name strings or allocate maps in the audio callback.
  • Same-source retrigger must replace that source's open recording slot, while different sources need independent slots and note lengths.
  • Plain ownership arrays are audio-callback state. Any UI-thread panic/reset that clears them must hold the audio callback lock.
  • Slot reuse after device reconnect is a separate lifecycle problem: retire the old generation and invalidate stale queued events rather than treating a new device generation as the old performer.

2026-07-14 - Controller state must include source and input channel

  • MIDI channel alone is not enough for sustain, resets, pitch bend, modulation, volume, expression, or pan when multiple controllers are connected.
  • A voice may sound on a lane's remapped output channel while its pedal and expressive controls arrive on a different input channel. Store both identities.
  • CC 120/121/123 must not be implemented as global panic aliases. Scope them to source plus input channel; reserve Panic for the deliberate global emergency exit.
  • Reusing a direct-input slot with a new generation must retire old voices, ownership, controller values, and open recording notes before accepting the new device generation.

2026-07-14 - Disconnect cleanup must precede direct queue drain

  • A direct MIDI device slot is not an identity by itself; identity is slot plus generation. Slot reuse without generation retirement can make a replacement controller inherit old notes, pedal state, controller values, or recording ownership.
  • Reconcile the router generation snapshot before consuming queued events. Retire only the old source generation, then reject queued events whose generation no longer matches.
  • Never solve disconnect cleanup with global Panic or channel-wide reset. Another controller may be holding the same channel and pitch and must remain untouched.
  • Publish generation changes with release ordering and consume them with acquire ordering. The audio path remains a bounded fixed-array scan.
  • Device removal discovery and state retirement are separate concerns. Even perfect retirement cannot occur until a non-audio inventory service observes the unplug; do not rely permanently on an editor timer.

2026-07-14 - MIDI device discovery must have one owner

  • Do not enumerate/open/close physical MIDI ports from an editor timer. Editors are optional and may be closed while audio continues.
  • Do not let prepareToPlay, policy setters, route setters, and UI reports call router synchronization concurrently. The check/open/install sequence needs one serialized owner.
  • A polling service must not traverse mutable Arrangement lanes. Publish a locked route-demand snapshot from lane mutation points.
  • Stop and synchronize the polling callback before destroying the MIDI input router.
  • Keep device enumeration and handle lifecycle off the audio callback.

2026-07-15 - Playhead targeting must yield to object selection

  • Arranger playhead targeting is a transient insertion/playback intent, not an enduring inspector owner.
  • Clicking or opening a clip must clear the playhead-target flag before any inspector control is used.
  • Single-selection handlers must resolve the target from the canvas selection and synchronize the primary selected index before editing.
  • A clip edit must never silently fall through to a legacy chain-slot edit.

2026-07-15 - Instrument Bay choices must not create shared lane instances

  • A melodic lane owns its instrument identity and mixer route even though internal backing storage remains slot-based.
  • Choosing an existing Instrument Bay sound copies its complete engine/sample/SoundFont/Quasar state into the lane-owned instance; it must not repoint two lanes at one mutable backing slot.
  • Fresh and Variant commands may allocate a new backing slot deliberately, but ordinary instrument replacement preserves the lane's instrument ID and mixer channel.
  • Regression coverage must prove independent post-copy edits plus clip-length save/restore.
  • SysEx replies must retain their input-device identity through callback queues and workers. Never let the oldest pending restore consume an ACK/NAK/WAIT/CANCEL from an unrelated device.
  • A Hardware Passport with no input device cannot provide source-pinned multi-device restore proof. Keep that state explicit rather than implying that an ACK came from the intended unit.

2026-07-15 - One-shot SysEx capture must preserve source identity

  • A valid F0...F7 frame is not sufficient evidence in a multi-device rig. Preserve the originating input device ID through every callback-to-worker queue.
  • When capture is armed for a Hardware Passport with a bound input, a different or empty source must be ignored without disarming capture or replacing its receipt.
  • A Passport with no bound input is deliberately generic. Say so in readiness/help text; do not market that state as source-verified capture.
  • Capture-intention generation is enforced: queued bytes from before Cancel/rearm cannot complete the next capture.

2026-07-15 - Saved QA receipts require a restore validation state

  • MidiProtocolQaReceiptSnapshot::canSave() is intentionally false after saved=true; passing a persisted saved record directly back through appendReceipt() will reject it even when every field is complete.
  • Restore must require the serialized record to have been explicitly saved, copy it to a temporary validation state with saved=false, and let appendReceipt() validate all required fields and reassert saved=true.
  • Never deserialize receipt arrays directly into the live ledger. Unsaved templates, malformed records, or hand-edited JSON must not become durable QA evidence.
  • Keep the ledger at project root rather than under optional sequencing payloads, and preserve append-only same-key history.

2026-07-15 - MIDI QA receipt workflow checks

  • Do not call a receipt review surface complete if it only prints terse summaries. Evidence notes, checker, timestamp, references, and blocked reason must be visible.
  • receiptRefs is required customer evidence, not optional decoration.
  • A saved receipt must be appended. Never overwrite an earlier result for the same persistent key.
  • SAVE/REVIEW QA is cold project-data workflow: it must never send MIDI, change routing, or touch external hardware.
  • Do not treat the broad legacy all-in-one self-test as equivalent to the focused MIDI closeout matrix. Its shared state and old fixtures currently produce unrelated failures; focused gates are the stable regression contract until the broad harness is isolated.

2026-07-15 - QA history and current readiness are different views

  • Never treat the existence of any saved receipt as completion. Only the latest valid saved PASS for the persistent category key closes it.
  • Latest means append order. Do not let an edited or stale timestamp reorder immutable evidence history.
  • A later FAIL or BLOCKED must reopen the category and leave SAVE QA available for another appended retest.
  • Ignore malformed saved records and unknown persistent keys when projecting coverage.
  • Receipt projection must be shared by MIDI Health and MIDI Protocol; do not let either view rebuild a static report independently.
  • Category PASS currently closes every blocker row in that category. If the closeout model later needs device-, firmware-, or build-specific proof, add explicit provenance rather than silently changing this scope.

2026-07-15 - SysEx policy must be explicit and message-family specific

  • Never infer a checksum algorithm from manufacturer ID alone. One manufacturer can use multiple framing and checksum families across products and messages.
  • checksumMatchesKnownRule=false is not proof of failure when no rule was evaluated. Customer wording must distinguish not evaluated from invalid.
  • Hardware Passport policy must be normalized identically in bulk replacement, upsert, serialization, and restore.
  • Automatic SDS response correlation requires explicit opt-in, midi-sds, a valid device ID, source pinning when available, matching packet identity, and one unambiguous active attempt.
  • Do not claim universal Yamaha, Roland, or vendor support until the device/message-family policy registry and physical-hardware evidence exist.

2026-07-15 - SysEx registry and message-family boundary

  • Persist only stable SysEx policy IDs and exact message-family IDs; never serialize executable formulas or accept runtime policy registration.
  • A manufacturer ID identifies an organization, not a checksum dialect, device byte, address layout, or response protocol.
  • The only automatic registry policy currently implemented is standard MIDI SDS scoped to midi-sds.data-packet.
  • Unknown response/checksum policy IDs must fail closed and remain visible as unsupported/manual verification.
  • Universal 7E/7F device IDs are standards-defined; manufacturer-specific payload bytes are not generic device IDs.
  • Add vendor support one verified device/message family at a time and require saved hardware evidence before making a product claim.

2026-07-15 - Instrument audio lifecycle rules

  • Keep every public engine choice unique and stable across APVTS choices, display names, serialization, and UI selectors. Duplicate names create ambiguous indices and restore failures.
  • A voice must latch its originating engine at note-on. Never dispatch an active voice by rereading the lane's mutable current-engine parameter.
  • TG55 owns its Element/envelope lifecycle. Do not retire TG55 voices with the generic pad envelope used by the other engines.
  • Direct drum-mapped note-off must match the voice identity created by direct triggering; use wildcard release when that path intentionally stores no MIDI-note identity.
  • Sanitize every new engine's persisted controls before audio rendering. TG55 controls are part of the same defensive state boundary as the older engines.
  • An all-silent sample render is failure. Never create or advertise a successful render merely because a buffer was allocated.
  • SoundFont and Quasar require valid assets for playback tests; do not claim those samplers pass from a no-asset smoke test.

2026-07-15 - Transient Mixer state and Hardware Passport test-note safety

  • Mixer subpages are temporary inspection state. Clear only mixerMoreVisible when leaving Mixer; preserve bank, panel choice, APVTS values, routing, and audio state.
  • Protected MIDI Setup rollback is single-use and valid only until the next checkpoint, normal Undo, or state restore. Never let it overwrite newer project work.
  • A Hardware Setup template is abstract until target-aware planning binds it to a real lane, Hardware Passport, output device, and channel.
  • Test-note planning must never send MIDI. Confirmed execution must reserve Note On and delayed Note Off together; partial publication can create a stuck hardware note.
  • A successful queue receipt proves SpaceAge accepted the pair, not that the external instrument sounded or acknowledged it.
  • Do not document compatibility-layer methods as complete until the compiled processor bridge and its focused gate both pass.

2026-07-15 - MIDI import review and timeline boundaries

  • Inspect-only must return a reviewed receipt without calling any project mutator or creating an Undo checkpoint.
  • Apply the reviewed request under one outer checkpoint. Nested legacy checkpoints make one user action require multiple Undo presses.
  • Never convert PPQ timestamps to seconds before placing MIDI notes. Tempo-map changes must not distort the musical grid.
  • The 64-step constant belongs to the drum surface, not Piano Roll clips. Melodic notes and expression may occupy the 4,096-step pattern timeline.
  • JUCE may insert zero-velocity note-offs at same-pitch retriggers. When extra synthetic offs exist, ignore only the coincident synthetic excess and pair genuine note-offs FIFO.
  • A split import receipt must count lanes and clips actually created. Predicted plan actions are not mutation evidence.
  • Fresh split imports must not let obsolete legacy-chain references consume the lane-owned payload pool.

Standard checksums must be standard outside SpaceAge

  • Risk: An exporter and inspector can agree with each other while both use the wrong checksum constants, creating an internally green but externally incompatible package format.
  • Rule: FNV-1a-64 uses offset basis 14695981039346656037 and prime 1099511628211; preserve a published known-vector regression (hello = a430d84680aabd0b).
  • Applies to: MIDI stem packages and any future package, asset, scene, preset, or recovery inventory that advertises FNV-1a-64.

MIDI stem-package transaction gotchas

  • A lane MIDI stem is currently imported into one editable pattern. Reject stems beyond 4,096 steps until lossless segmentation into multiple patterns/clips is implemented; never truncate silently.
  • Preflight all three finite resources: Arrangement lanes, Arrangement clips, and unused pattern payloads.
  • A multi-stem import is one transaction. On any cancellation, integrity change, or stem failure, restore project state and the prior Undo/Redo arrays.
  • Revalidate package checksums after any callback that can yield control and immediately before reopening a payload.
  • A manifest cannot truthfully contain its own final checksum. Do not write negative sentinel sizes as if they were verified metadata.

2026-07-15 - MIDI channel observation and normalization boundaries

  • Observe channel-voice input before applying an armed-lane channel filter. Diagnostics must be able to explain rejected input without routing it to audio or recording.
  • Copy incomingMidiChannelMask into the project-readiness model; an atomic that never reaches the report is invisible infrastructure.
  • Normalize recorded notes and recorded expression through the same stored lane route. Never compare behavior against a caller's pre-normalized ArrangementLane copy.
  • Raw pattern export is intentionally lane-independent: drums use channel 10, Chord Engine uses channel 1, and Piano Roll notes preserve stored channels. Arrangement export uses the lane route instead.
  • Tempo, meter, and key signature are part of a useful raw MIDI export contract, not optional decoration.

2026-07-15 - MIDI payload and drum-length boundaries

  • Do not use the editable-expression predicate as a persistence filter. Bank/program/RPN/NRPN data must survive state round-trips even when it is not curve-editable.
  • Validate modulation-route enum values and amounts before storing them; downstream DSP assumes normalized route state.
  • Do not force long channel-10 files through the 64-step grid representation. Preserve them in the shared note payload and map valid GM drum pitches back to pads for drum-lane playback.

2026-07-15 - MIDI readiness fields must be populated, not merely serialized

  • A model field exposed to reports and UI is not implemented if its producer never assigns it. liveRoutingVerdictLabel and liveRoutingVerdictDetail must always explain host, direct, combined, blocked, or unarmed ownership.
  • Input monitoring, recording, health, and support reports must derive from the same armed-lane/source-policy contract.
  • Report objects with cached headlines are immutable snapshots. A test that mutates copied counters must clear/rebuild the cached headline before checking derived summary wording.
  • A green focused test does not replace the broad gate when failures depend on earlier fixture state. Run the complete AudioSelfTest before declaring MIDI integration closed.

Automation clocks and atomic arranger edits

  • Different owners may use different clocks. Shared PTN and Clip Local are source-relative; Lane Local is absolute to the Arrangement. Never merge them and apply one timing transform.
  • Lane Local data must continue through clip gaps and must not restart at clip boundaries.
  • Selected-clip export includes Lane Local data only where the selected clip actually occupies the absolute timeline. Full lane/song export emits Lane Local once.
  • Any arranger command that can allocate patterns, clips, section markers, or preserved-gap fragments must preflight all capacity before checkpoint or mutation.
  • Refused CUT, paste, and clone operations leave project and Undo/Redo state untouched. Do not allow partial paste.
  • Keep one reachable implementation for each command path. Duplicate or unreachable paste logic is regression risk and should be removed rather than kept as a fallback.
  • Remaining edge audit: shift-drag clone and gap normalization deserve direct full-capacity UI tests even when focused ownership and clipboard gates are green.

2026-07-16 - Lane instrument and Arrangement-duration ownership

  • A lane instrument replacement is not a full Pad Settings paste. Copy sound-generation resources and parameters while preserving gain, pan, mute/solo/output, autopan, shared-effect sends, channel-strip EQ, compressor, and saturation.
  • Choosing a lane instrument must not overwrite the user's explicit Pad Settings clipboard.
  • Keep instrument identity, backing-slot storage, and mixer-channel identity conceptually separate even while the current implementation maps some of them through the same finite slot.
  • Repeated-clip duration is now canonical: length remains the editable source cycle, repeats remains the count, and arrangementClipOccupiedSteps/arrangementClipEndStep own Arrangement geometry, navigation, export, overlap, gap, and automation extent. Do not reintroduce ad hoc length-times-repeats arithmetic.
  • Timeline-end clamping must reject or explicitly trim a move/resize that would exceed the maximum; clamping the start while preserving length can create overlap.
  • Gap consumption and paste must preflight all capacity and late validation before checkpointing. A refused operation must preserve Redo and leave no partial fragments.
  • Continuous resize controls should create one Undo transaction per gesture, not one checkpoint per slider tick.

Repeated clips: source cycles versus occupied time

  • ArrangementClip.length is the reusable source-cycle length. It is not the clip's full visible duration when repeats is greater than one.
  • Use arrangementClipOccupiedSteps() for width/span and arrangementClipEndStep() for absolute timeline ends.
  • Drum-chain conversion must store the chain slot source length and repeat count separately. Storing expanded duration in length and retaining repeats multiplies playback twice.
  • Save/load must preserve length and repeats independently and reproduce the same occupied duration.
  • Until an explicit Flatten Repeats operation exists, CUT on a repeated clip and paste through the middle of one must refuse before checkpoint or mutation.

2026-07-16 - Arranger Boundary And Atomic Mutation Contract

  • Treat step 4096 as an exclusive occupied-end boundary: a clip may end at 4096, but its start must remain below 4096 and its occupied duration may not cross it.
  • Validate occupied duration with 64-bit arithmetic (length x repeats) in the processor before assigning IDs or mutating storage. Never clamp an invalid start into apparent validity.
  • Compound editor operations must simulate overlap resolution before checkpointing. A directly valid edit can still push trailing clips over the boundary.
  • Restore must compact accepted clips and omit malformed clips without leaving array holes.
  • Legacy drum-chain synchronization must preflight total occupied steps and clip capacity before clearing/rebuilding native drum clips.
  • CUT and interior paste on repeated clips remain explicit refusals until Flatten Repeats is implemented atomically.

2026-07-16 - Undo Transaction Integrity

  • A refusal is not a transaction. Complete range, capacity, ownership, and overlap preflight before checkpoint().
  • A same-value inspector selection is not a transaction. Detect a real delta before checkpointing.
  • Never clear or initialize a clip payload before proving the corresponding Arrangement clip can be placed.
  • Modal callbacks must revalidate object identity/range at commit time; the project may change while a dialog is open.
  • The focused Arranger gate exercises the real + ADD DRUM CLIP editor callback at the 4096-step boundary and requires undo() to remain false after refusal.
  • Remaining bounded audit: inspector note edits, inspector sliders, lane mute/solo, legacy drag-end callbacks, and snapped no-op gestures still need explicit one-gesture/one-checkpoint review.

2026-07-16 - Gesture Transaction Boundaries

  • A continuous inspector drag is one Undo action. Reset its latch on drag start, checkpoint only the first accepted changed value, and end the transaction on drag end.
  • A notes field is one Undo action per focused editing session. Same-text updates and stale targets must not checkpoint or fall through to another target type.
  • Snapping can turn a physical drag into a semantic no-op. Compare canonical snapped section/clip geometry before checkpointing.
  • Lane mute/solo is one atomic pair: enabling one may disable the other, and one Undo must restore both fields.
  • Chain reordering validates source/destination and checkpoints only when their normalized indices differ.
  • The Arrangement section catalog contains 105 stored types. Use maxArrangementSectionType; do not restore the obsolete hard-coded limit of 73.
  • Relabeling an existing section marker changes only its label. Never replace its start or length with a containing clip's geometry.
  • Focused editor regression coverage now exercises refused add, snapped no-op resize/move, genuine one-step section Undo, atomic lane mute Undo, and extended-section save/load.
  • Remaining audit item: form-preset application should receive the same semantic no-op review before release.

2026-07-16 - Bulk Arranger Commands Must Preflight

  • Compare a song-form preset against the full current Section lane before checkpointing; repeated application is a no-op and must preserve Redo.
  • Reject unsupported preset IDs instead of falling back to a destructive generic form.
  • CLONE must plan ripple shifts and every clone position, then validate the final lane layout before copying pattern payloads.
  • Bulk drum removal must reserve one empty, unreferenced pattern per preserved gap before checkpointing. Never partially remove a selection when capacity is short.
  • A failed bulk action must leave clips, patterns, selection-owned payloads, Undo, and Redo untouched.

2026-07-16 - Expression Editing Atomicity

  • Do not keep permanently hidden controls wired to model mutations. Remove the obsolete surface rather than maintaining a second interaction contract.
  • One editable MIDI expression lane may not acquire two points at the same tick through editor move operations.
  • Single move helpers return -1 for destination collision, 0 for stale/unresolved source, and a positive sorted index for success.
  • Group move/value/delete helpers are all-or-none. If any expected member is stale, duplicated, or unresolved, mutate none of them.
  • Group destination validation must include selected-to-selected collisions and collisions with unselected points in the same lane.
  • CLEAR STEP, empty graph erase, and duplicate ADD POINT actions must not consume Undo history.
  • Gesture checkpointing remains one checkpoint on the first proven mutation, never one checkpoint per mouse sample.

2026-07-16 - MIDI ordering, AUTO ownership, and transactional edits

  • AUTO source selection belongs to the armed lane, not to whichever controller happened to be open or active.
  • Equal-tick MIDI events retain insertion order; never restore type-based sorting that can scramble RPN/NRPN or setup messages.
  • SysEx Passport attachment is one-to-one and updates both profile and snapshot ownership atomically.
  • Cancelled accepted SysEx captures clear commit-ready bytes and metadata.
  • Stale single/group automation selections are preflighted before Undo checkpoints.
  • Eliminate audio-thread onset mutex/copy work, especially SoundFont voice setup.
  • Preserve sub-block offsets for note-off and expressive events throughout rendering.
  • Give hardware output cancellation an epoch or per-device SPSC queue so panic cannot race stale sends.
  • Validate AUTO with both mioXL DIN and USB sources in a real two-controller session.

2026-07-16 - Incoming MIDI Sample Timing

  • Never ingest an entire host MIDI block and mutate voice/expression state at block start. Dispatch accepted performance events when the renderer reaches their sample offset.
  • Playback timing and recording timing are separate coordinates. Playback is sample-accurate within the live block; recording may include measured compensation or callback age.
  • Preserve insertion order for equal-sample events. RPN/NRPN sequences, controller setup, sustain, and expression can be semantically ordered.
  • A bounded performance queue must fail safe on overflow. Release voices and ownership rather than dropping a possible Note Off or sustain-up and creating a stuck note.
  • Direct hardware received between callbacks begins at the next available audio block for minimum latency, while recording retains the measured pre-block age.
  • Exact-sample regressions must include note-on, note-off, sustain down/up, bend, modulation, and two events sharing one timestamp.

2026-07-16 - Authored expression and hardware-output lifetimes

  • Never apply all sequencer-authored expression due in a block before sample 0. Dispatch it at its exact render sample.
  • Preserve equal-sample enqueue order across Shared PTN, Lane Local, and Clip Local expression.
  • Apply incoming live performance after authored expression at the same sample so live override is deterministic.
  • Never call AbstractFifo::reset while the hardware worker or a producer may own a reservation.
  • A lane reroute/disconnect must invalidate queued traffic for the old route and release notes already sent there.
  • Panic must use guaranteed-capacity priority traffic to every open output, not append behind recall delays.
  • A hardware deadline must be represented once; never wait the reported delay again after its due time passed.
  • The audio callback must not lock the output device, copy device IDs, or open OS MIDI devices.
  • Device hot-unplug/reconnect must advance lifetime identity so old traffic cannot reach a replacement handle.

2026-07-16 - Hardware MIDI output invariants

  • Never call AbstractFifo reset while producers or the consumer may own reservations. Retire entries with an epoch and let the consumer drain them.
  • Never sleep inside one message's dispatch routine. A delayed message must not block Panic, live notes, or another endpoint.
  • Never copy hardware profiles, construct device-name collections, enumerate/open outputs, or take device/profile locks from processBlock.
  • Endpoint slot, route target, output channel, and route generation are one logical publication. Do not read or write them as unrelated state.
  • A reroute must both cancel future stale messages and release notes already sounding on the old endpoint/channel.
  • Panic and transport/device safety cleanup must use the priority safety path, not an ordinary delayed musical queue.
  • Hardware scheduler behavior is not considered fully proven until virtual-device tests observe successful sends under a manual clock.

2026-07-16 - MIDI worker and hardware scheduler gotchas

  • Do not reset an AbstractFifo while its consumer may be reading; retire traffic with epochs and let the consumer drain it.
  • A queue clear must synchronize with the final endpoint send check or one stale message can cross the clear boundary.
  • Panic and old-route release need a priority path that is independent of delayed musical messages.
  • Equal deadlines must preserve one global enqueue sequence across live and recall sources.
  • Tests for asynchronously committed recording data must wait on the committed condition with a bound; immediate reads are scheduler races, and arbitrary sleeps are not proof.
  • Physical USB/DIN soak must still confirm OS-driver behavior that a virtual endpoint cannot model.

2026-07-16 - Hardware safety and endpoint lifetime gotchas

  • Required release/Panic work must not depend on a finite FIFO that duplicate commands can fill. Coalesce safety intent by endpoint generation and channel.
  • Repeated Panic must collapse rather than serialize seconds of duplicate DIN traffic.
  • Output disappearance must advance generation before handle close; old musical and safety traffic must never reach a replacement handle.
  • Store stable device IDs for lane and clock routes; raw endpoint slots are process-local cache locations, not identities.
  • Target-mode changes with an unchanged physical route must preserve pending note-offs.
  • Pending counts must compact stale heap entries and include emergency safety work.
  • Two lanes sharing one hardware endpoint/channel do not have independent note ownership. Avoid that configuration unless intentional, or define a future ownership policy before claiming isolated reroute cleanup.
  • JUCE/WinMM queue acceptance is not proof that a physical device received bytes. Physical monitoring and hot-plug soak remain mandatory.

2026-07-16 - Gotcha: A Written Export Is Not The Same As A Valid Export

  • A MIDI file can be successfully written and still be wrong musically if timing, channels, expression, pitch bend range, or lane separation are not verified after opening or re-importing.
  • Keep readiness, job plan, and write receipt separate so UI language does not overclaim what has actually been proven.

Gotcha - Latency Compensation Can Create Legitimate Edge Splits

If recorded MIDI latency compensation moves a note-on before the start of a looping pattern while the note-off remains after the start, SpaceAge should split the note at the pattern edge. Tests and UI receipts must treat this as correct musical behavior, not duplicate-note corruption.

Gotcha - Build Script Output Directory vs Diagnostic Build Directory

tools/codex-build-selftest.ps1 builds into outputs/build-local-midi-closeout. Older diagnostic executables under C:\Users\immor\Documents\Codex\ssbuild-diagnostic0937 can remain stale and print old regression text. When validating a just-edited self-test, run the fresh build-local executable or explicitly rebuild the diagnostic directory before trusting failures.

Gotcha - Keep Build Helpers On The Same Rail

If one script builds in outputs/build-local-midi-closeout and another runs ssbuild-diagnostic0937, test results can contradict the source. The shared path helper now defaults to build-local, but any manual SPACEAGE_BUILD_DIR override should be treated as a deliberate test of a different executable.

2026-07-19 - Gotcha: Cockpit Row Budgets Must Move With New Rows

  • Any fixed visible-row budget in MIDI Health, render status, hardware setup, or automation cockpits must be updated in the same change that adds a new visible row. A model/report feature is incomplete if the panel can clip or crowd the new human-facing line.

MIDI Health Fixed Row Budgets

When adding rows to the MIDI Health cockpit, update the reserved row count in the panel layout immediately. The paint path and resized path are separate; if they drift, the panel can draw rows correctly but position buttons/recommendations as if fewer rows existed.

2026-07-20 - Gotcha: Do Not Recompute Structured Health State In The UI

  • If a model already exposes a launch/readiness/proof card, the UI should render that card rather than duplicating counts and next-action logic.
  • Duplicated Health cockpit arithmetic drifts from copied reports and tests; it also makes later UI cards harder to trust.

2026-07-20 - Gotcha: Do Not Infer Doorways From Cockpit Prose

  • Customer-facing status text is allowed to change for clarity; route IDs must remain stable.
  • Any health/proof/action card that can open a UI surface should expose a stable doorwayId and readable doorwayLabel rather than forcing the UI to parse labels like "launch proof" or "timing".

2026-07-21 18:52 - Direct MIDI Pre-Render Drain

Direct physical MIDI should be drained before queued performance processing and again immediately before voice rendering. The first pass handles normal block setup; the second pass catches last-moment controller events that arrived while SpaceAge prepared sends/effects. Do not sell this as a universal latency cure: audio buffer size, driver/interface latency, DIN merge/filter behavior, SoundFont startup, and external monitoring can still dominate the player's felt delay.

2026-07-21 - Arrangement Recording Closeout Must Use Clip Space

Any MIDI recording closeout path must use the same Arrangement-clip source mapping as note-off capture. Stopping record, interrupting step input, or closing stuck notes from an Arrangement lane cannot safely use the raw song step because repeated clips and clips with non-zero source starts turn that into the wrong pattern coordinate. Raw song-step closeout can create tiny notes, wrong note lengths, or boundary notes that wrap to the beginning of the pattern.

2026-07-21 - Avoid Duplicate Arrangement Recording Math

Do not reintroduce local copies of Arrangement recording clip math in note, expression, or closeout paths. All of them must map song-step time into clip source-step time through the shared helper, with explicit boundary behavior. Duplicated math here is how SpaceAge gets tiny notes, misplaced CC data, or notes that record correctly until the user stops transport at exactly the wrong moment.

2026-07-21 - Armed Lane Is The Recording Authority

When MIDI recording is active, selected clips are editing context, not live-input ownership. The armed Arrangement lane must be the recording authority. Any target clip ID used for recording must belong to that armed lane, or the recording path can appear to steal focus from another lane.

Gotcha: One Component Needs One Layout Owner

  • Arrangement AUTO LANES had two setBounds calls in the same resized pass. The second call won visually, but the first still consumed row width.
  • When moving a button between Menu X axes, remove the old bounds assignment instead of leaving a hidden reservation behind.

Gotcha: UI Target Order Must Match Tooltip Order

  • AUTO LANES promised selected clip -> playhead clip -> first real lane clip, but one path let the playhead beat the selected clip.
  • Any future owner-based editor should keep tooltip language, button status, and open-target logic in the same order.

Direct MIDI Record Offset Is Not Queue Age

For physical MIDI input, queue age and musical event offset have opposite meanings. Queue age asks how long a message waited before the audio block drained it; record offset asks where that event belongs relative to the block start. Use signed event offset for recording and clamped in-block position for monitoring. Reusing queue age as record position makes fresh direct-controller notes record too early and can produce takes that feel bunched or lag-compensated in the wrong direction.

MIDI Health Text Must Match Timing Semantics

Diagnostic labels are part of the architecture. If the text says queue age controls recording placement, a future fix may accidentally reintroduce the exact bug we removed. Keep MIDI Health wording aligned with the two-clock model: queue age for diagnostics, signed event offset for recording placement.

Sample-Dispatched MIDI Must Not Be Delayed Twice

Once incoming MIDI has been held until its target sample inside the audio block, do not pass that same sample position as an additional voice start delay. Sample position is still needed for recording math and ordering; internal voice delay is only for events scheduled before render, not events already dispatched at render time.

MIDI Recording Target Is Processor-Owned

During MIDI recording, UI selection is not the source of truth. Stop, note closeout, and post-record quantize must use the processor's active recording pattern. Otherwise a user can click around during recording and accidentally make the final note-off/quantize operation target a different clip than the one that captured note-ons.

2026-07-22 - Clip Identity Beats Visible Properties

  • Arrangement clips can intentionally look identical: same lane, pattern, start, length, repeats, transpose, and name.
  • Any code that needs to recover the same clip after normalization, gap consumption, or sorting should prefer the hidden clipId first and only fall back to visible properties when no ID exists.
  • This matters especially for MIDI recording targets, because a stale or duplicate-looking clip can send recorded notes into the wrong musical container.

2026-07-22 - Drum Recording Must Not Create Hidden Melodic Notes

  • A Drum lane and an Instrument lane may both be driven by MIDI input, but they write different musical data.
  • Drum recording should write Step data only. Falling through into Piano Roll note creation can leave invisible or confusing melodic artifacts behind a drum clip.
  • Keep these paths split whenever recording, import, paste, or future arrangement-lane recording features are touched.

2026-07-22 - One MIDI Block Needs One Dispatch Model

  • Direct MIDI can arrive at more than one point inside processBlock.
  • If collection is disabled too early, late direct input can bypass the sample-ordered pending queue, making live feel and record timing depend on which drain caught the message.
  • Keep collection active until the final pre-render direct drain is complete, then dispatch all incoming performance messages through the per-sample loop.

2026-07-22 - Direct MIDI Drain Modes

  • Do not casually enable lateLivePerformanceOnly for production direct MIDI draining. If a future path uses it, non-performance messages must be explicitly preserved or routed elsewhere; otherwise setup/program/clock-like data can appear to vanish.
  • Hardware MIDI input must stay sample-dispatched inside processBlock. Direct callback timing should never bypass the per-sample dispatch queue for live notes, expression, or recording.

2026-07-22 - Drum Recording Has Two Truths

  • Drum-lane MIDI recording must write both the step-grid hit and the timed note representation. The grid hit makes Drum Composer editing feel correct; the timed note representation preserves long Arrangement clips, export, clip thumbnails, and source-aware note length.
  • mapMidiRecordingStepToTargetClipLocked mutates patternStepPosition and recordLength by reference; its return value is a bool. Never assign that bool to patternStepPosition, or all recording collapses toward step 1.

2026-07-22 - Protocol-Only Gates Need Honest Boundaries

  • A MIDI-only test gate must stop after MIDI protocol coverage. If it continues into unrelated audio/editor smoke tests, a full-app UI/audio failure can masquerade as a MIDI backend regression.
  • Readiness-report assertions should check the owner of each proof concept. AUTO LANES needs receipt evidence; hardware routes need hardware cue counts; route summaries should not be forced to repeat hardware cue text just to satisfy stale test prose.

2026-07-22 - Drum Lanes vs Instrument Lanes

  • Drum lanes may remain explicitly pad-bank/pad-mixer oriented because Drum Composer is an MPC-style note-input and mixing paradigm.
  • Non-drum Arrangement lanes must be presented as lane-owned Instruments. Menus, drawer titles, status text, MIDI routing copy, export names, and future automation lanes should not ask the user to think in hidden pad slots.
  • Until the backing storage is fully moved out of pad slots, pad-backed choices should be labeled as importing/copying an existing pad sound, not as the primary instrument assignment model.

2026-07-22 - Lane Insertion Must Reindex Clips

When adding lanes anywhere other than the end of the Arranger stack, every existing clip at or below the insertion index must shift its lane index with the lane records. This is now centralized in the processor-level insertArrangementLane helper. Avoid editor-side lane surgery because it can desynchronize visible lanes, clip routing, MIDI armed lane state, and hardware route snapshots.

2026-07-22 - Synth Drawer Target Must Not Drift

When the Synth Engine drawer is open from an Arrangement lane, refresh paths must use synthDrawerTargetPad/getActiveSynthEditPad rather than selectedPad. selectedPad can change during pad refreshes, MIDI routing, or lane selection, which makes the UI report a hidden backing slot or wrong instrument identity even when audio routing is correct.

2026-07-22 - MIDI Timing Receipts Need Deterministic Invariants

A direct MIDI queue receipt failed when it used absolute wall-clock-derived sample windows. The production behavior was correct, but the test depended on the tiny delay between obtaining the test timestamp and entering processBlock. Future timing tests should prefer deterministic block-time inputs or relative in-block ordering assertions.

2026-07-22 - Instrument Slot Alias During Migration

Non-drum Arrangement lanes are moving away from user-facing pad identity. During migration, save both instrumentSlot and legacy instrumentPad, restore instrumentSlot first, and never expose hidden slot numbers as if they are Drum Pads.

2026-07-22 10:14 - Lane Instrument Slot Migration Gotcha

  • Do not remove the transitional instrumentPad backing field or decouple mixer routing from slot indices until the audio engine has explicit lane-owned voices/mixer channels. Use getArrangementLaneInstrumentSlot() for new user-facing lane code in the meantime.

2026-07-22 - Piano Roll Must Not Inherit Drum Bank Paging

  • Gotcha: the Drum Composer uses four visible 16-step banks, but Piano Roll clips can be much longer. Do not let selectedStepPage or 64-step tail padding reset long Piano Roll views during refresh.

2026-07-22 - Downbeat Work Must Stay Lightweight

  • Do not run full UI rebuilds, patch scans, or arrangement array scans from playback/downbeat transitions.
  • Living-time visuals should read atomics or cached snapshots and repaint only; they must not trigger layout rebuilds or processor mutations on musical boundaries.
  • Repeated getEffectiveBpm() calls inside realtime loops are a stutter risk because host/playhead queries can vary in cost; cache per block unless sample-accurate tempo automation is intentionally implemented.

2026-07-22 - Block-Level MIDI Timing Margin

  • Direct MIDI can be drained twice per block to catch late-arriving controller messages, but source-generation reconciliation should happen once per block unless a device inventory mutation requires otherwise.
  • Avoid getEffectiveBpm() inside per-sample loops. Host playhead reads and chain tempo multiplier lookups belong at block boundaries until we explicitly design sample-accurate tempo automation.
  • Avoid constructing per-pad scratch arrays inside the sample loop. Reuse block-local scratch storage and clear it only when the related processor is active.

2026-07-22 - Render-Loop MIDI Timing Trim

  • Per-sample audio code must not rescan loop-track arrangement state unless the state can truly change sample-by-sample. Cache block-stable loop solo/pattern state at the block boundary.
  • Source-aware MIDI expression lookups are allowed in the audio loop, but do each lookup once per voice/sample and reuse the result.

2026-07-22 - Block Meter Publication

  • Visual meters should be published from block-local peaks, not updated through atomics inside the active voice loop. GUI feedback must not tax MIDI feel.

  • 2026-07-22 13:05: AUTO LANES must remain discoverable at the lane level. Backend automation storage is not enough; lane/clip badges and tooltips must say what owner will be edited before opening the automation editor.

  • AUTO LANES ownership default: do not silently default to Shared PTN when opened from an Arrangement clip or lane. Local ownership protects linked clips from accidental global automation edits.

2026-07-23 - MIDI Recording Must Arm Before Playback Starts

Arrangement recording should arm the MIDI recorder before starting sequencer playback. If playback begins first, the first render block can advance the transport before incoming notes are captured, which feels like the first measure fell away. Keep REC startup order as: resolve target clip, enable metronome/count-in state, startMidiRecording(...), then setSequencerRunning(true).

2026-07-23 - Root Overlay Components Must Close On Navigation

When a component is promoted from a page child to an editor-root overlay, page navigation must explicitly close it or re-home it. Otherwise it may visually persist over unrelated pages and confuse the user's location.

2026-07-23 - Do Not Reintroduce Pad/Source Labels In Non-Drum Lane Menus

  • Non-drum Arrangement lane workflows should say Lane, Instrument, Mixer, and MIDI Channel. Pad language belongs to Drums/Pads only.
  • If a menu must expose a transitional backing slot, label it as an unassigned instrument and keep the visible lane as the preferred owner when one exists.

2026-07-23 - Instrument Bay Bridge Must Not Leak Pad Thinking

  • Non-drum lanes may still use instrumentPad internally during migration, but UI labels should say lane/instrument/mixer, not pad/source.
  • If a menu must expose unowned instrument slots, label them as unassigned instruments rather than pads.
  • Any future true Instrument Bay registry work should delete the transitional storage only after MIDI recording, lane mixer routing, saved presets, ghost notes, and synth editing all have non-pad identifiers.

2026-07-23 - Avoid Recreating getArrangementLaneInstrumentPad()

Do not add getArrangementLaneInstrumentPad() back. If future code needs a lane's sound source, use getArrangementLaneInstrumentSlot() during the bridge phase or the future true Instrument Bay record/id API once the bridge is retired.

2026-07-23 - Prefer laneOwned Over Slot Guessing

When deciding whether an Instrument Bay record is currently part of an Arrangement lane, use InstrumentBayRecord::laneOwned rather than re-checking slot numbers. Slot checks are a migration detail and should not be duplicated across the UI.

2026-07-23 13:21 - Gotcha: Duplicate Lane Ownership Inference

Avoid reintroducing local lambdas or ad hoc scans for instrument-slot ownership in the editor. Use the shared helper until the pad-backed bridge is fully replaced by the final lane-owned Instrument Bay registry.

2026-07-23 13:25 - Gotcha: Drawer Entry Path Can Reintroduce Pad Language

When changing Synth Editor entry paths, make sure both the target label and title prefix are lane-aware. Opening a lane-owned instrument indirectly should still read as a lane instrument, not as a numbered pad proxy.

2026-07-23 - Pad Terminology Leakage

  • When refactoring away from pad-backed instrument language, avoid blind replacements inside drum pad scans. Verified cleanup after catching stale variable replacements in find-next-empty-pattern and menu-building loops.
  • Any future instrument-slot migration should preserve drum-specific Pad Mixer wording while removing pad wording from non-drum lane surfaces.

2026-07-23 - Lane Menus Must Copy Lanes, Not Slots

  • Do not restore a raw hidden-slot list to Choose Lane Instrument. Non-drum lane menus may create fresh engines, load SoundFonts into the lane instrument, edit the lane instrument, variant the lane instrument, or copy from another lane-owned instrument. Raw backing slots belong only in internal migration code.

2026-07-24 - MIDI Recording Must Not Force Click By Default

  • Arrangement/Piano Roll MIDI recording may temporarily enable the metronome for count-in or an explicit record-only metronome setting, but plain recording should preserve the user's current metronome state.
  • Any path that auto-enables the metronome must restore the previous state when recording ends, including stop, cancel, and transport interruption paths.

2026-07-24 - Do Not Split Synth Drawer From selectPad Casually

  • openSynthDrawer() no longer calls selectPad() for lane-owned instruments. Keep it that way: sample-layer, one-shot, patch, SoundFont, and source callbacks should resolve through getActiveSynthEditPad().
  • Pad selection side effects are valid only for true Pad-page or standalone non-lane edit contexts, and those paths must keep selectedPadPage synchronized with selectedPad.

2026-07-24 - Synth Drawer / Pad proxy gotcha

  • Do not wire new Synth Drawer actions directly to selectedPad unless the action is truly Pads-page-specific. For lane-owned instruments, resolve getActiveSynthEditPad() first. This prevents editing one lane instrument while the UI silently follows whichever Pad was last selected.

2026-07-24 - Drawer attachments must follow editSlot

  • Any new Synth Drawer parameter attachment should bind through the local active edit slot or padParameterId(), not selectedPad directly. Otherwise lane-owned instruments can appear selected but edits may hit the wrong legacy Pad-backed slot.

2026-07-24 - Gotcha: Lane-Owned Instrument Editing Must Not Select Pads

If a Synth Drawer operation is opened from a non-drum Arrangement lane, do not call selectPad() or blindly assign selectedPad. That revives the deprecated Pad-proxy mental model and can make the UI appear to change drum-pad selection when the user is really editing a lane instrument. Use getActiveSynthEditPad() for the backing slot, isSynthDrawerEditingLaneOwnedInstrument() to detect lane-owned context, and syncActiveSynthEditSlot() for patch/source loading paths.

2026-07-23 - Arrangement Lanes Must Not Relearn Pad Ownership

Non-drum Arrangement lanes now restore from instrumentSlot only. Do not add a new user-facing or save-state dependency on instrumentPad for melodic/harmonic lanes. Pads remain valid for Drum Composer and Pad-page workflows; lane instruments must continue moving toward true Instrument Bay records.

2026-07-23 22:23 - Gotcha: Pad Storage Still Powers Instrument Slots

Even after the UI says Instrument Slot, many synth/sample parameters still live in the historical 64 pad-backed parameter bank. Treat slot wrappers as a transitional layer. Do not build new user-facing workflows that imply Pad numbers are the identity for melodic/harmonic lanes.

2026-07-23 22:32 - Gotcha: First 16 Slots Are Reserved In Spirit, Not Yet In Audio Architecture

New non-drum lanes avoid slots 0-15, but the audio engine still has 64 shared slot-backed parameter banks. Do not mistake the reservation policy for a completed Instrument Registry. The final architecture still needs lane-owned instrument objects that are not pad-indexed.

2026-07-24 - Gotcha: Slot Names Are Not Yet Final Architecture

  • Non-drum lanes are now allocated from private instrument slots 17-64, but the engine still stores many note/chord references in fields named pad.
  • Do not rename serialized pad fields casually. First introduce an explicit instrument registry/schema boundary, then migrate note/chord data with tests.
  • Visible pads 1-16 should remain treated as drum/MPC territory unless the user intentionally loads an instrument onto a pad.

2026-07-31 - Gotcha: Explicit Recording Target Edges Must Reject Hidden Writes

For a selected explicit Arrangement clip, the playable interval is clip.start .. clip.start + clip.length * clip.repeats. Map valid positions inside repeated clips through the source length, but reject note, drum, and expression input after the final repeat before queueing it. Do not fall through to another clip, grow hidden shared-Pattern payload, or let a longer linked clip reveal the event on another lane. Increment the existing dropped-note/drum or dropped-expression diagnostic. Preserve only the narrow pre-start clamp that protects first-measure callback ordering; it does not authorize post-end capture.

2026-07-24 - MIDI Split Import Must Not Target Visible Drum Pad Slots

MIDI split import can accept stored/custom channel destination requests, so backend code must defend against stale visible-slot values. For non-drum lanes, clamp destination instruments to firstPrivateArrangementInstrumentSlot..numPads-1; channel 10/drum lanes remain the only path that should intentionally map to the drum-pad world.

2026-07-24 - Non-Drum Lanes Must Reject Visible Pad Slot Identity

The processor-level lane setter is a guardrail: non-drum lanes normalize instrument slots into the private lane-instrument range even if an old project path, import path, or future UI regression tries to hand it a visible drum-pad slot. Keep this behavior until the true Instrument Bay record store fully replaces the transitional slot bridge.

2026-07-24 - Piano Roll Instrument Lists Must Not Reintroduce Pads 1-16

The standalone Piano Roll selector should not offer the visible first 16 drum pads as ordinary tonal instruments. Use private lane-instrument slots and stable item IDs. Drum workflows may still use pads explicitly, but melodic/harmonic Piano Roll workflows should teach the lane-owned instrument model.

2026-07-24 - Chord/Piano Helpers Need The Same Boundary As Menus

Do not only fix the visible menus. Helper paths that infer or retarget Piano Roll notes and Chord Engine clips must also clamp tonal targets into private lane-instrument space, or internal operations can quietly reintroduce visible pad-slot identity after the UI appears correct.

2026-07-24 - Do Not Recreate Piano Roll Pad Wrappers

Avoid adding setPianoRollInstrumentPad(), getPianoRollInstrumentPad(), setPianoRollLiveInputPad(), or getPianoRollLiveInputPad() back. Piano Roll and MIDI step input should speak in instrument slots during the bridge phase, and eventually in true Instrument IDs once the Instrument Bay store replaces slot-backed storage.

2026-07-24 - Preset Apply Must Use Active Synth Edit Context

Do not use selectedPad as the target inside synth/lab preset apply functions unless the code path is explicitly Pad-page-only. Use getActiveSynthEditPad() so lane-owned instruments, Piano Roll edit contexts, and Pad-page editing all resolve through the same active editor target.

2026-07-24 - Playback Must Enforce The Same Lane/Pad Boundary

Do not assume UI/menu clamps are enough. Arrangement playback, export, import, recording, and persistence should all enforce the private lane-instrument boundary for non-drum lanes. Pads 1-16 are drum/MPC territory until the final Instrument Bay registry removes the 64-slot bridge entirely.

2026-07-24 - Stale Snapshot Tests Need Test-Only Hooks

  • Some Arrangement/Instrument Bay bugs only show up when a stale UI or playback snapshot bypasses the normal setter path.
  • Keep test-only hooks available for forcing stale state into snapshots, then assert that audio/MIDI playback revalidates ownership at the point of use.
  • Rule: non-drum lanes must always resolve to private Arrangement instrument slots at playback time, even if older state or a UI reporter still says Pad 1-16.

2026-07-24 - Hidden Instrument Copies Must Be Checkpointed First

When a UI action copies generator state into a private arrangement instrument slot, checkpoint before the copy. Otherwise Undo can remove the visible lane/clip change while leaving the hidden copied synth state behind, which later feels like a ghost assignment.

2026-07-24 - Drawer Editors Must Use getActiveSynthEditPad

Any Synth Engine drawer action that can be reached from an Arrangement lane must use getActiveSynthEditPad(), not selectedPad. selectedPad is valid for the Pads page; lane-owned Instrument edits need the drawer target slot so SoundFonts, source samples, preview, and layers do not mutate the wrong pad.

2026-07-24 - Secondary Panels Need Active Edit Slot Too

Any panel opened from inside the Synth Engine drawer must resolve the target with getActiveSynthEditPad(). Subpanels like Flux Nodes are part of lane/pad instrument editing, so using selectedPad there reintroduces the old pad-proxy workflow by accident.

2026-07-24 - Do Not Expose Private Backing Slots As Pads

Until the final instrument registry storage refactor lands, non-drum lane instruments may still be backed by private parameter slots internally. UI copy must not call these Pads. Use Instrument Bay, Lane Instrument, Available Instrument, or Instrument ## depending on context.

2026-07-24 - Do Not Retarget Shared Patterns Silently

If a pattern appears in more than one non-drum lane, changing one lane's instrument must not rewrite the stored note/chord instrument fields for that shared pattern. Require variant/copy behavior before destructive retargeting, or rely on playback's lane-owned instrument override.

2026-07-24 - Shared Pattern Retarget Guard (Superseded 2026-07-30)

The older bridge rule distinguished private and shared patterns during Instrument reassignment. The final rule is simpler: Arrangement lane Instrument changes never retarget either kind of pattern.

2026-07-24 - Do Not Bypass Lane Instrument Setter

Non-drum lane instrument changes should go through setArrangementLaneInstrumentSlot() so ownership, backing-source collision repair, routing publication, and identity remain coherent. The setter deliberately does not rewrite musical pattern data.

2026-07-24 - Piano Roll Setters Must Not Target Visible Pads

Piano Roll and melodic live-input code should use the slot setters and expect them to clamp to private lane instruments. If a future feature intentionally wants pads from Piano Roll, it needs a deliberate Drum/Pads mode rather than bypassing this guardrail.

Gotcha: Ownership Lookup Must Not Clamp Drum Pads Upward

If a helper asks which lane owns PAD 01-16, the correct answer is none. Clamping visible pad slots into the private instrument range can make unrelated lane state appear to own a drum pad and revive the deprecated pad-as-lane-instrument workflow.

Gotcha: Pad Preset Edits Must Not Retarget Piano Roll

Any code path that edits a visible drum pad should not call setPianoRollInstrumentSlot() unless the slot is in the private Instrument Bay range. Otherwise a pad sound-design action can unexpectedly steal melodic MIDI focus.

Gotcha: Default Pad Labels Leak Architecture

Unused private instrument slots inherit old Pad XX labels from the backing store. Any Instrument Bay or lane-owned display should translate those defaults into instrument language so the user never has to learn about hidden pad slots.

Gotcha: Instrument Copies Must Not Write Into Pads 1-16

copyInstrumentSlotSettings() is for lane-owned/private instrument sources and destinations. Allowing visible Drum Composer pads on either side would revive the deprecated pad-proxy model and could make drum sounds look like generic instrument templates or let non-drum workflows overwrite pads. Use explicit Pad APIs for pad work.

Gotcha: InstrumentSlot APIs Are Not Pad APIs

If a function is named InstrumentSlot, it must enforce the private lane-owned instrument range. Use explicit Pad APIs for Drum Composer pad operations. Letting InstrumentSlot wrappers pass visible pad numbers through is a subtle way to reintroduce pad-proxy workflow bugs.

Gotcha: selectedPad And selectedPadPage Must Move Together

When a true Pad-page or standalone non-lane edit context intentionally changes selectedPad, update selectedPadPage in the same gesture. For lane-owned Synth Drawer contexts, do not change selectedPad at all; use getActiveSynthEditPad() and the lane source context instead.

2026-07-24 - Private Instruments Must Not Masquerade As Pads

  • When adding or editing private Instrument Bay slots, never call selectPad() or assign selectedPad unless the target is below firstPrivateArrangementInstrumentSlot.
  • For editor refresh decisions, prefer getActiveSynthEditPad() when a Synth Drawer may be editing a lane-owned instrument. selectedPad only describes the visible Drum Pad surface.

2026-07-24 - Synth Drawer Must Infer Lane Ownership

  • If a private instrument slot is opened without explicit lane context, check whether an Arrangement lane owns it before calling Pad-selection code.
  • Otherwise the GUI can reintroduce confusing labels such as Synth Engine / Pad for a non-drum lane instrument.

2026-07-24 - Pad Clipboard Must Not Recreate Pad Proxy Lanes

The Pad clipboard is allowed to serve Pad workflows, but lane-owned Synth Drawer paste must use pasteCapturedInstrumentSlotSettings(), not the raw Pad paste helper. Otherwise a visible Drum Composer pad can quietly become the backing state for a melodic Arrangement lane, recreating the workflow confusion we are removing.

2026-07-24 - Synth Generators No Longer Write Mixer Effects

  • Generator and synth preset/application paths must not write channel-strip effects or shared send state (halostarsend, roomsend, delaysend, strip compressor/saturation/EQ, etc.). Those belong to Mixer routing, not instrument identity.
  • Drum/pad sound parameters such as pitch, decay, filter, internal drive, transient shaping, and lab-specific controls are allowed in generator presets; mix bus effects are not.
  • Synth Engine pages should not advertise shared-return sends as part of patch identity. If a parameter affects Halostar, Reverb, EchoRay, Chorus, Flanger, Phaser, Tremolo, Octave return level, or channel-strip ambience, expose it from Mixer/Lane routing instead.

2026-07-24 - Do Not Reintroduce InstrumentPad Naming

The editor no longer has a formatInstrumentPadLabel() helper. Keep it that way. Lane-owned instruments should be formatted through Instrument Bay/lane helpers, while visible Pad wording belongs only to Pads, Drum Composer, and explicitly MPC-style workflows.

2026-07-24 - Selected Clips Must Not Steal MIDI Recording

When Arrangement recording is armed, the destination is the armed lane plus the playhead. A selected clip elsewhere may remain edit focus, but it must not become the recording target. Prefer helper paths that explicitly locate the playhead clip in the armed lane.

2026-07-24 - Lane-Owned Synth Drawer Headers Must Refresh Immediately

When opening the Synth Editor for a lane-owned Instrument, refresh the pad/header labels after attaching the drawer. Do not rely on selectPad(), because lane-owned editor contexts intentionally avoid changing visible Pad selection.

2026-07-24 - Do Not Parallelize Build-Backed Gates Sharing One Build Directory

MIDI_RECORD_TIMING, MIDI_CLOSEOUT, and similar compile-backed gates can collide if launched at the same time because Ninja writes the same object files. Run them serially unless the scripts are changed to use separate build directories.

2026-07-24 - Instrument Bay Wording Cleanup

  • Do not let private instrument slots leak as Pad numbers in non-drum lane UI. If a label is for a melodic/harmonic Arrangement lane, say Instrument, Mixer Channel, and MIDI Channel; reserve Pad labels for Drum Composer, Pad Bank, Pad Vault, and actual pad editing workflows.

2026-07-24 07:58 - Ownership Lookup Must Reject Visible Pads

  • Fixed the editor helper that used to clamp visible Pad numbers into the private Lane Instrument range before ownership lookup. Future helpers should return no lane owner for Pad 01-16; never clamp a drum-pad ID upward to answer an ownership question.

2026-07-24 08:06 - Raw Private Slot Ordinals Must Stay Hidden

Private lane-owned backing slots may still be numbered internally, but UI-facing records should say Lane Instrument ##. Do not display raw Instrument 57/Pad 57 style labels for non-drum lanes unless the user is explicitly editing the Pad surface.

2026-07-24 08:11 - Preset Names Can Leak Architecture Too

It is not enough to fix info text. Private lane-owned instrument preset names and display names must also avoid raw Instrument ## / Pad ## backing-slot language, or the UI will still teach the deprecated pad-proxy model.

2026-07-24 08:15 - Automation Is The User-Facing Name

Keep full-size user-facing MIDI expression wording on Automation. Compact proof/badge labels may use AUTO where space is tight, but reports, warnings, help text, and first-use guidance should not revive the old AUTO LANES phrase.

2026-07-24 08:47 - Automation Naming Must Include Launch-Proof Buckets

Do not only rename visible buttons/tooltips when moving from AUTO LANES to Automation. MIDI Health launch-proof buckets, receipt queues, action surfaces, dropped-recording warnings, and copied support reports can still carry old wording and silently break release-proof assertions. Gate with a compile-backed MIDI_HEALTH run when changing this vocabulary.

2026-07-24 08:57 - MIDI Feature Names Must Not Drift Between UI And Reports

When renaming a deep MIDI feature, update the model reports, Health cockpit labels, repair/action cards, regression expectations, and core docs together. Automation previously had current UI labels but older AUTO LANES wording in diagnostics and manuals, which could make users think there were two different systems.

2026-07-24 09:05 - Multi-Owner Buttons Need Target Receipts

Buttons that choose between selected clip, playhead clip, and lane fallback can feel random unless their status, tooltip, or receipt says which object won. Any future Arrangement, Automation, MIDI, or Instrument Bay control with fallback targeting should expose the chosen target path.

2026-07-25 00:34 - Test The Badge Text, Not Only The Drawer

The Synth Drawer can correctly say Instrument while the Arrangement lane badge or menu still leaks Pad/private-slot language through another formatter. MIDI closeout should keep direct coverage on lane-owned badge labels and choice-menu labels whenever Instrument Bay wording changes.

2026-07-25 00:58 - Automation Target Rules Must Stay Unified

Do not duplicate the selected-clip/playhead/first-clip fallback rule in multiple UI paths. Arrangement Automation labels, tooltips, and opening behavior should use resolveArrangementAutomationTargetForLane() so the visible promise and the actual target cannot drift apart.

2026-07-25 01:18 - REC Target Must Ignore Stale Editing Selection

Arrangement MIDI recording must remain playhead-first inside the armed lane. A selected clip elsewhere is editing context only and must not steal live input. Keep the regression that proves a stale selected clip loses to the real playhead clip, and keep the target summary saying that selection does not steal live input.

2026-07-25 01:42 - Do Not Overwrite Rich Route Status

setMidiInputLane() builds the authoritative armed-lane route status. Lane command panels and routing subpanels should call it and leave that status intact unless they have a more detailed route message. A terse MIDI INPUT ARMED | lane name message hides the mixer channel, MIDI channel, input filter, and lane-owned instrument context the user needs.

2026-07-25 02:03 - Clip Open Is Also MIDI Route Disclosure

The Arrangement clip-open path calls setMidiInputLane() and then switches pages. Its final status must preserve the rich route text instead of replacing it with a generic clip-editing message. Future page transitions that arm input should follow the same pattern: include edit context, but keep the route facts visible.

2026-07-25 02:31 - REC CAPTURED Must Be Route-Aware

Recorded-note status receipts must not shrink back to only pattern and note data. For non-drum lane-owned recording, keep the lane name, mixer channel, MIDI output channel, input filter, and no-Pad wording visible alongside pitch, timing, velocity, and captured MIDI channel.

2026-07-25 15:17 - MIDI Route Status Must Use The Shared Formatter

Do not hand-roll lane MIDI route status strings in new UI paths. Use the shared route summary so arming, input filters, named hardware inputs, output MIDI channels, record targets, and capture receipts all expose the same facts. Otherwise one path will inevitably hide the device, input channel, mixer channel, or lane-owned Instrument Bay wording.

2026-07-25 15:31 - Hardware Route Changes Need The Same Truth

Hardware output, Hardware Passport, and route-target receipts must be just as detailed as input arming receipts. A vague hardware assigned message is not enough; it hides the lane target, hardware output, Passport ID, input device, input filter, mixer channel, and MIDI channel exactly when a user is trying to debug external gear.

2026-07-25 15:33 - Hardware Test Notes Are Hot Receipts

Do not reduce TEST HARDWARE results to queued or failed. This is a hot external-MIDI moment, so the status must show the full route summary and make the evidence boundary clear: SpaceAge can prove the queue attempt, while audible response and patch/channel correctness still require user or device confirmation.

2026-07-25 15:40 - Blocked Hardware Tests Need Details Too

The blocked/not-ready state of TEST HARDWARE is not an exception to route-truth wording. It should show the same lane, target, output, Passport, input, mixer, and MIDI-channel facts as queued/failed states before saying CHECK ROUTE.

2026-07-25 16:05 - Do Not Over-Teach Backend Terms

Lane Instrument is useful internally, but the user-facing Arrangement menu should simply say Instrument unless the distinction is necessary. The product model is: choose an Instrument for a Lane, route it to a Mixer Channel, and reserve Pad/Pad Bank wording for Drum Composer and MPC-style pad workflows.

2026-07-25 16:27 - Import Screens Can Regress The Mental Model

MIDI import/split screens and record metadata are easy places for old storage wording to leak. Destination rows should say Instrument and MIDI Channel, not Lane Instrument or private backing-slot ordinals. Keep tests rejecting Lane Instrument, Instrument Bay slot, private slot, and non-drum Pad wording in lane-owned records.

Release fingerprints must use ordinal path ordering

Never build a source-tree fingerprint with culture-sensitive Sort-Object path ordering. Windows PowerShell 5.1 and PowerShell 7 can order the same filenames differently and therefore produce different aggregate hashes for byte-identical source. Build normalized relative paths first, sort them with StringComparer.Ordinal, then hash. The JUCE 8.0.8 manifest fingerprint is defined by that cross-runtime ordering.

2026-07-25 16:48 - Avoid Transitional Adjectives Like Lane-Ready

Labels such as LANE-READY INSTRUMENT sound like an implementation bridge rather than a musical object. Prefer plain states like AVAILABLE INSTRUMENT unless the user truly needs to know why a choice is unavailable.

2026-07-26 00:18 - Shared Load Buttons Must Resolve The Active Target

Do not call visible-Pad asset loaders directly from shared Synth Editor controls unless the workflow is truly Pad-only. One-shot, SoundFont, and future shared sample/synth-source buttons should route through active-target helpers so non-drum Arrangement lanes remain lane-owned Instruments, not accidental Pad edits.

2026-07-26 00:44 - Sample Layer Buttons Are Shared Load Buttons Too

The sample-layer editor, Liftoff source loader, and Lunacy source loader are not automatically Pad workflows just because the backing arrays are still pad-shaped. Use active-target layer wrappers for load, clear, reset, velocity-layer creation, spreading, and loaded-sample activation. Keep explicitly named Pad commands, such as Clear All Pads, on the Pad API.

2026-07-26 01:05 - Put Boundary Tests On Shared Editors

Any shared editor button that can touch sample/layer/source state needs both routing code and a regression. It is too easy for a later cosmetic pass to call the old Pad API because the storage is still pad-shaped internally.

2026-07-26 01:24 - Multisample Packages Are Shared Editor Assets Too

Quasar is not exempt from the InstrumentSlot boundary just because it was originally implemented with ForPad storage helpers. Reveal, load, name, info, and fingerprint paths should resolve the active target first, then use Pad or InstrumentSlot APIs according to that target.

2026-07-26 01:43 - Shared Effects Sends Need Neutral Source Language

The Effects page send bank can address visible Drum Pads and private lane-owned Instrument sources during the bridge phase. Do not label that customer-facing area as selected Pad unless the control is truly Pad-only; use Source or Mixer Channel wording until the final Instrument Bay routing language is settled.

2026-07-26 02:02 - Generic Source Numbers Are Only Half A Fix

Replacing Pad wording with SOURCE ## avoids one lie but creates a memory burden. Source selectors should reuse the same Drum Pad / Instrument / Mixer Channel identity shown elsewhere, and they must refresh when lane instrument labels change.

2026-07-26 02:28 - Mixer Headers Are Destinations, Not Always Pads

The Mixer page grew from Pad channels, but non-drum Arrangement lanes now own Instruments that route into mixer destinations. Do not hardcode every strip title as PAD ##; use a formatter that can say MIXER ## for lane-owned instrument destinations and reserve Pad wording for actual drum-pad workflows.

2026-07-26 02:42 - Top-Level Tooltips Can Undercut The Model

Even if the main UI labels are correct, hover copy on Mixer, Reset Audio, Multi-Out, and other global controls can quietly resurrect the old pad-only mental model. When a control affects both lane instruments and drum pads, say so directly.

2026-07-26 03:08 - Lane Synth Slots Must Not Carry Retained Sample Payload

During the Pad-to-Instrument bridge, private Arrangement instruments still live in pad-shaped storage. A non-drum lane synth may copy oscillator/filter/envelope parameters from another private slot, but it must not copy retained samples, one-shots, SoundFont payload, or Quasar zones unless the source engine is actually sample/SoundFont/Quasar based. Otherwise a Redshift edit can appear to affect a kick sample because both are secretly sharing the same dirty backing slot.

2026-07-26 04:15 - Direct Live Hardware Sends Must Not Wait For The Normal Flush

Direct-device MIDI can arrive after the normal outgoing MIDI flush for the audio block has already happened. If live hardware monitoring queues notes or expression through the same deferred lane-output path as Arrangement playback, an external synth can feel one block late even when internal monitoring is prompt. Direct live input for External and Internal+External armed lanes should enqueue with the live-immediate path.

External-only lanes must also remain truly external-only during monitoring and recording. Do not secretly start internal voices for those lanes; it makes troubleshooting impossible and violates the lane route label.

2026-07-26 04:38 - Multi-Controller Source Filtering Must Happen Before Hardware Mirroring

When more than one direct MIDI controller is open, the armed lane's pinned input device must be checked before any live hardware mirror is queued. Otherwise the wrong physical controller can send notes or expression to an external synth even though recording correctly ignored it. Keep source filtering upstream of monitoring, recording, and external send paths.

2026-07-28 - MIDI Percentages Can Hide Different Denominators

Do not report MIDI readiness as one confident percentage unless the denominator is named. Backend implementation, UI exposure, launch proof receipts, hardware proof, real-file import/export proof, and live-performance proof are different gates. Keep midiOneRemainingLaunchBlockersSummary visible anywhere a human might ask, "what is actually left?"

2026-07-30 - Preset Load Must Preserve Mixer Ownership

  • Sound and Instrument preset formats must not save, reset, or load Mixer-owned parameters.
  • This includes gain, pan, mute/solo, autopan, output routing, shared-effect sends, delay routing, channel-strip compressor/saturation/EQ, graphic EQ, and parametric EQ.
  • Reject Mixer-owned IDs even when an older preset file contains them; otherwise loading a sound can silently rewrite a mix.
  • Generic full Pad Settings copy remains a separate explicit workflow and must not accidentally inherit the filtered Instrument-copy semantics.

2026-07-30 - Drum Pad And Instrument Storage Must Not Overlap

  • SpaceAge exposes 64 Drum Pads. Never allocate private lane Instruments inside indices 0-63.
  • Pad-page load, bulk load, clear, Pad Vault, and Drum Composer sequencing must be unable to mutate or trigger lane-owned Instrument storage.
  • Instrument storage and mixer-channel identity are separate concepts. A private source slot must not become a shadow Mixer channel merely because both currently use similarly shaped arrays.
  • Add regressions for Pad 64 plus multiple lane Instruments before declaring the final Instrument Bay architecture beta-safe.

2026-07-30 - Keep Source Timbre And Mixer Processing Distinct

  • compressor and transient are source-timbre controls used by drum labs and synthesis rendering; they are not the channel-strip compressor or Mixer transient processing.
  • Mixer-owned names include gain, pan, mute/solo, output routing, autopan, sends, delay settings, strip compressor/saturation/EQ, graphic EQ, and parametric EQ.
  • Private Instrument slots must have source-timbre parameters but must not have Mixer-owned parameter IDs or pointers.
  • Do not infer ownership from an old pad variable name. Resolve voice.pad as a source and voice.mixerChannel as a Mixer destination.

2026-07-30 - Instrument Undo Must Stay Targeted

  • Never use a full project/APVTS snapshot merely to undo a lane-Instrument assignment. Asset payloads and project state make that path prohibitively large and capable of blocking the UI.
  • Store the affected Instrument snapshot in memory, preserve shared asset references, and generate the inverse record at Undo/Redo time.
  • Instrument Undo must not alter Mixer state or the musician's current copy/paste clipboard.
  • Compact Instrument snapshots must include non-APVTS source state such as Lab Flux configuration.

2026-07-30 - Never Reconstruct Identity From An Index

  • Never derive a private source's Mixer channel from sourceSlot + 1; resolve it through the owning Arrangement lane.
  • Default melodic lanes must begin in the private range and own distinct source slots.
  • Generic Drum MIDI fallback must validate against numDrumPads, not the larger source-slot count.
  • Project-load repair must not create Undo checkpoints. Repair re-establishes invariants; it is not a musician-authored edit.

2026-07-30 - Removed Patch Parameters Must Also Leave Tests

  • When a parameter is removed from Instrument ownership, remove or migrate every test that directly dereferences that parameter ID.
  • Synth-level effect sends were removed intentionally; tests must not continue treating halostarsend or similar controls as Instrument state.
  • Mixer gain for a lane must be addressed through the lane's assigned one-based Mixer channel, not through its private source slot.
  • Fresh-source tests should derive defaults from each juce::RangedAudioParameter instead of maintaining a second hand-written default table.

2026-07-30 - Preview And Effects Must Use The Owning Lane

  • Patch auditions, Piano Roll previews, chord previews, and layer previews for private Instruments must carry the owning lane's Mixer channel into the audio thread.
  • Effects-page send attachments and tempo-sync controls must bind to the resolved Mixer index, never the private source slot.
  • Test routing with hostile settings: mute the real lane channel while leaving the old source-derived channel audible, then reverse the state. Both playback and preview must follow only the lane channel.
  • Keep the source-to-Mixer resolver centralized; new preview surfaces must not reproduce routing arithmetic locally.

2026-07-30 - Presets Must Never Reset Mixer State

  • Native patch allowlists must exclude gain, pan, mute, solo, output, eqenabled, Mixer EQ parameters, and effect sends.
  • Factory-preset and SoundFont-selection code must not write those IDs as a side effect of changing the sound.
  • A control labeled as an Instrument level must use an engine-specific source parameter. Never bind a synth-drawer control to generic Mixer gain.
  • MIDI import review must not offer or commit the same private Instrument destination for two selected melodic channels.
  • Explicit reviewed destinations are strict: reject stale or occupied choices and request another review; never silently choose a fallback.

2026-07-30 - Keep Mixer Processing Out Of The Synth Drawer

  • Do not add a common EQ, fader, pan, channel mute, output-routing, or send surface to the Synth Engine drawer.
  • Engine-native filters, resonance, drive, transient shape, and intentional timbre controls remain valid patch content.
  • Use synthTabIndexForEngine() as the sole engine-to-visible-tab mapping. Do not create a second hard-coded switch for tab coloring or navigation.
  • Any tab-order change must run the Instrument-UI ownership slice and full AudioSelfTest because SoundFont and later engine indices are easy to shift silently.

2026-07-30 - Blank Project Is A Hard Lifecycle Boundary

  • Send Panic before clearing routes so internal voices and external hardware cannot retain notes owned by the departing project.
  • Reset pattern data, explicit pattern lengths, and published audio-thread snapshots together. Clearing only the editable containers leaves stale playback state.
  • Clear all source slots, not merely the 64 Drum Pads. Private lane Instruments also own labels, modulation, Flux, round-robin state, and asset caches.
  • Clear SoundFont/Quasar paths and Quasar published caches as well as loaded objects. A path-only missing asset is still project content.
  • Clear project-owned MIDI mappings, Hardware Passports, and SysEx snapshots when opening a blank project.
  • Keep recovery's definition of the canonical project synchronized with resetArrangementLanes(). The default Section is 64 steps, not 16.
  • Recovery usefulness checks must count SoundFont and Quasar references even when the project contains no notes or samples.

2026-07-30 - Parsing JSON Does Not Prove It Is A Project

  • Validate project identity and required structure before calling restoreStateObject().
  • Backward compatibility may accept a missing format marker only when the parameter XML and source array are both recognizable.
  • Never mutate the current session, current-project target, or Last Project preference after a failed load.
  • Save through a verified temporary file in the destination folder so serialization or write failure cannot truncate a known-good project.
  • Keep blank-project detection synchronized everywhere. The canonical starter Section is 64 steps; a stale 16-step comparison changes startup navigation and recovery behavior.
  • Recovery files reopen as unsaved work. Do not silently bind a recovery snapshot as the current save-over target.

2026-07-30 - Recovery Cleanup Must Prove Ownership

  • Never prune every .sskit found in a directory. Manage only files whose generated naming contract establishes that SpaceAge owns their recovery lifecycle.
  • Retention limits count valid, useful recovery snapshots, not malformed files or unrelated projects.
  • A stale Last Project preference is disposable metadata. The current musical session is not.
  • Do not hide the startup chooser after a failed automatic load; restore a usable decision point and make the failed action unavailable.
  • Test chooser cancellation before calling withFileExtension(). An empty chooser result can otherwise become a plausible-looking path.
  • Async file-chooser callbacks must use juce::Component::SafePointer; dialogs can outlive the editor that launched them.
  • Keep recovery loads untitled so an explicit Save As decision separates recovered work from an existing project target.

2026-07-30 - Treat Project Archives As Hostile Until Commit

  • Inspect every ZIP entry before extraction. Reject absolute paths, drive prefixes, traversal segments, control characters, symbolic links, duplicate normalized paths, oversized entries, and excessive total expansion.
  • Never extract directly into the musician's final import folder. Use a staging directory, validate the remapped project and packaged assets, then commit the complete directory.
  • Bind archive manifest kinds: Assets/... maps only to sample-squad-archive://asset/...; Quasar/... maps only to sample-squad-archive://quasar/....
  • A Quasar archive entry is a directory package and must contain manifest.json; an ordinary archive asset must be a file.
  • Do not delete or truncate an existing destination before the replacement ZIP has been written and inspected successfully.
  • Async missing-asset repair must capture the selected MissingAsset identity. Never trust an index after a chooser has been open.
  • Missing-asset dismissal is destructive project editing, not a UI-only ignore flag. Validate the captured receipt identity, clear the owning sample/loop/ SoundFont/Quasar/synth-cycle reference, rescan, and require an ordinary project save for persistence. Stale popup receipts must be rejected.
  • Automated tiny fixtures prove format and lifecycle correctness; large personal libraries remain required human performance and listening tests.

Automation Owner And Clock Gotchas (2026-07-31)

  • Automation owners use two clocks: Shared Pattern and Clip Local are source-relative; Lane Local is Arrangement-relative. Never merge Lane Local into a source-relative export list.
  • Every Clip Local or Lane Local automation mutation must republish the immutable sequencer snapshot, including copy, clear, remove, reset, and restore paths.

Stable Identity And MIDI Import Gotchas (2026-07-31)

  • setArrangementLane must retain an existing positive laneId; a caller-supplied replacement value is not permission to re-key Lane Local automation.
  • setArrangementClip must retain an existing positive clipId; Clip Local payloads remain attached across property edits.
  • Clear the insertion slot before routing a new lane through the replacement setter, or stable-ID enforcement can duplicate the shifted lane's identity.
  • Parse and stage MIDI before checkpointing or mutating Pattern state.
  • Return false/no-op when a valid file has no accepted payload after channel and preservation filters.
  • SysEx-only import leaves Pattern state and its playback publication count unchanged.
  • Full import preserves chord clips; partial bank import preserves Piano Roll notes, chords, out-of-window events, and the existing length floor.
  • One accepted Pattern import commit produces exactly one immutable Pattern snapshot publication.
  • Pattern and SysEx Vault state are not one cross-mutex transaction; do not document or test them as globally atomic.

Profile Delete And Live Monitor Gotchas (2026-07-31)

  • Removing a Hardware Profile clears every dependent lane route in the same transaction as registry removal.
  • Profile removal refreshes cached sync/chase readiness and republishes the sequencer snapshot before either model mutex is released.
  • Acquire patternMutex and midiProfileMutex with one std::scoped_lock; do not introduce a nested opposite-order acquisition.
  • Refresh the hardware clock output snapshot only after both model locks are released.
  • A failed or repeated profile removal is non-mutating and does not publish cleanup for an identity that was not removed.
  • Live voice/output channel remapping must not change the incoming channel that owns sustain, expression, note release, and channel-mode controls.
  • Apply the input-control/output-voice split to both melodic live monitoring and drum fallback monitoring.
  • Physical multi-controller and hardware-clock soak testing remains a release activity beyond deterministic self-tests.

Recording Restore And Long-Pattern Quantization Gotchas (2026-07-31)

  • Quantize Piano Roll notes against getPatternLength(pattern), never the 64-step drum-grid constant.
  • Preserve long Arrangement timing and the existing Pattern horizon after post-record quantization.
  • Stamp queued note, drum, and expression commits with the epoch captured by their realtime recording event.
  • On restore, cancel recording directly; do not call the normal stop path or close held notes into restored content.
  • Reset pending count-in, recording pattern/clip targets, and activation state before applying restored model state.
  • Advance the recording epoch and clear held-note arrays together under patternMutex.
  • Compare queue-item epoch inside the commit lock before model mutation, receipt updates, or snapshot publication.
  • Physical-controller, host-undo-under-load, and long-session queue soak tests remain manual release evidence beyond deterministic self-tests.

Count-In Boundary And Passport Customer Deletion Gotchas (2026-07-31)

  • When count-in reaches zero inside an audio block, leave the count-in loop without returning from processSequencer.
  • Preserve remaining so normal scheduling uses the exact in-buffer activation offset.
  • At 48 kHz / 120 BPM / 8192 samples, require step zero and its metronome event at sample 6000 and require Arrangement step advancement.
  • Route the customer button and deterministic tests through one editor-owned Passport deletion helper.
  • Return from cancel, missing-profile, and attached-SysEx guards before taking an undo checkpoint.
  • Check both Passport attachment IDs and snapshot deviceProfileId ownership; inconsistent one-sided attachment data must still block deletion.
  • Never silently detach or delete SysEx while removing a Hardware Passport.
  • Checkpoint immediately before the processor deletion transaction, then refresh editor and panel models after success.
  • Require undo to restore both the Passport registry entry and the lane attachment in mutable, snapshot, and serialized state.
  • Native confirmation-dialog appearance and real-device Passport workflows remain manual customer QA beyond helper-level deterministic tests.

Realtime MIDI Recording And Record Arm - 2026-07-31

  • Never read mutable Pattern, Arrangement lane, or clip routing under patternMutex from note/drum/expression audio-callback capture.
  • Republish RecordingReadSnapshot when record Pattern/target, armed lane, lane routing, clip mapping, or active Pattern horizon changes.
  • Keep recording mutations on bounded queues; do not use try_lock as a latency fix because it silently drops musical events.
  • Preflight clip capacity, unused Pattern capacity, and timeline range before a record-arm checkpoint or UI/model mutation.
  • A failed record arm must preserve serialized state, transport/record state, selection/routing state, and redo.
  • Run REALTIME_RECORDING_NOLOCK and PROJECT_PERSISTENCE before the broader MIDI recording/closeout gates.

2026-07-31 - Gotcha: Atomic Shared Ownership Can Still Destroy On The Audio Thread

An atomic shared_ptr load protects an in-flight loop asset, but the callback can still perform the final release of an old generation and synchronously destroy or unmap that asset. Capture one immutable loop generation per block, keep mutable DSP/runtime state in fixed audio-owned storage, and retire every overlapping replaced generation to non-audio-owned storage until a later idle epoch. Gate this with a barrier that changes asset and settings after capture, then checks old/new block generations, finite output, zero callback lock/allocation/destruction diagnostics, and at least one non-audio retirement. Remember that these counters cover the loop snapshot subsystem, not every allocation elsewhere in processBlock().

2026-07-31 - Gotcha: Atomic Asset Publication Does Not Control Final Destruction

  • Atomic shared-pointer exchange gives new voices a coherent generation, but without retirement storage an old voice can still perform the final TSF close, memory unmap, or sample-buffer destruction on the audio callback.
  • A realtime voice must retain the complete cache generation it captured, not only its selected leaf handle. This includes ordinary/user samples, Liftoff and Lunacy source assets, Quasar zones, and SoundFont prepared racks.
  • Replaced generations belong in the corresponding mutex-protected writer retirement vector. Reclaim only from a non-audio publication path and only when no callback or voice owner remains.
  • Sample completion and retireVoice must drop leaf handles before dropping the generation token. Do not add a callback-side retirement-vector lock, erase, shrink, or allocation.
  • SoundFont generation ownership includes the master cache and every prepared TSF instance. Never clone or close TSF state as a fallback on live note start.
  • Scoped diagnostics are useful regression evidence but are not a process-wide allocator or lock profiler. Keep physical rapid replace/clear, host stop/start, and long-session soak in manual beta QA.
  • Release gate: REALTIME_INSTRUMENT_ASSET_RETIREMENT, followed by the asset-backed Quasar, SoundFont, synth, realtime loop, realtime recording, MIDI closeout, and MIDI health gates.

Owner-Scoped Live Automation Remap Gotchas (2026-07-31)

  • Treat the stored automation MIDI channel as source identity; never apply it directly when an Arrangement owner resolves to remapped lanes.
  • Resolve Shared Pattern, Clip Local, and Lane Local destinations through Pattern, stable clipId, and stable laneId ownership respectively.
  • Apply and neutral-reset each affected internal lane on midiRoute.applyOutputChannel(lane.midiChannel).
  • Deduplicate equal internal channel destinations before applying or resetting controller state.
  • Use raw source-channel fallback only when no Arrangement destination exists. External-only ownership must not leak into internal state.
  • Cover pan, volume, expression, modulation, pitch bend, and sustain through the shared owner-aware helper.
  • Keep hardware-output reset behavior on its existing separate path and preserve Shared Pattern, Clip Local, and Lane Local isolation.
  • Physical hardware output and perceptual listening remain manual beta evidence beyond the deterministic internal-state gate.

2026-08-01 - Detached JUCE Callouts Own Their Own Lifetime

  • CallOutBox::launchAsynchronously(..., nullptr) places a modal surface on the desktop; editor destruction does not destroy its content.
  • Use Component::SafePointer for editor-backed panels and WeakReference for processor-only panels. Resolve immediately before every mutation.
  • CallOutBox::dismiss() is asynchronous. Timer-backed panels must stop their timer when owner resolution fails, then request dismissal.
  • A safe pointer to the panel protects only the panel. Nested callbacks must also validate the editor or processor that the panel uses.
  • Invalidate processor weak references at the beginning of processor destruction, not only when the generated weak-reference member is eventually destroyed.

2026-08-01 - A Safe Outer Panel Does Not Prove Its Editor Is Alive

  • A detached child may safely retain a Sequencer Settings panel while that Settings panel still contains an expired raw editor reference.
  • Before routing a child action through an outer panel, validate both the outer panel SafePointer and the editor SafePointer.
  • Timer liveness must cover every owner transitively used by the callback, not only the processor that supplies the refreshed snapshot.

2026-08-01 - Generated Control Rows Need the Same Lifetime Contract

  • A panel-level timer guard does not protect dynamically generated row-button callbacks.
  • Every enable, invert, pickup, resolution, range, response-curve, conflict-repair, and delete callback must resolve the weak processor immediately before mutation.
  • Read-only paint helpers may inspect the weak owner without dismissing; the message-thread timer owns retirement and asynchronous callout dismissal.

Detached SysEx Vault windows

  • Never retain CinematicDrumsAudioProcessor& in a timer-backed or asynchronously confirmed SysEx Vault surface.
  • Resolve the processor weak reference for every capture, receipt, metadata, Passport-link, and removal action.
  • A component SafePointer protects only the panel; delayed callbacks also require a live processor weak reference.
  • On owner loss, stop the polling timer and dismiss the CallOutBox.

Detached Hardware Passport surfaces

  • A Passport panel may outlive the editor action that launched it; store the processor as a JUCE weak reference.
  • Paint and report-generation paths are ownership paths too, not merely visual code.
  • Every delayed sync-policy, recall, and file-chooser callback must validate both its component SafePointer and processor WeakReference.
  • Preserve the separate editor-safe callback for project-level Passport removal; do not replace one lifetime boundary with another raw capture.

2026-08-01 - Detached MIDI Patch callbacks

  • A SafePointer to the detached panel is not enough when the callback then follows a raw editor or parent-panel capture.
  • Hardware-changing callbacks must validate the panel, editor, and any intermediate Settings surface immediately before queueing or mutating.
  • A callback that dismisses its own CallOutBox before opening another surface must capture that destination surface safely; dismissal can invalidate the original parent.
  • Regression proof: MIDI_SETUP_QUEUE, MIDI_SYNC_POLICY, and MIDI_CLOSEOUT.

2026-08-01 - Automation panels have many entry points

  • Guarding only paint() or the panel object is insufficient: buttons, combo boxes, keyboard shortcuts, mouse gestures, and transitions to sibling panels can all enter an Automation editor after its launching editor has gone away.
  • Detached editors should combine a self-invalidating owner pointer, guards at interactive/mutating entry points, and a bounded monitor that retires an orphaned CallOutBox.
  • Preserve automation ownership semantics while hardening lifetime: shared Pattern, clip-local, and lane-local data must not be collapsed into one fallback owner.
  • Regression proof: AUTOMATION_RESTORE, AUTOMATION_OWNERSHIP, and MIDI_CLOSEOUT.

2026-08-01 - Redaction is not a privacy schema

  • Never feed a support archive an arbitrary human-readable report and rely on replacement rules to remove private content afterward.
  • Support payload APIs must expose only allowlisted typed fields. If a name, identifier, warning, path, note, preset, or project string has no field, it cannot leak through a future wording change.
  • Keep fixed system-metadata path redaction as defence in depth, not as the primary privacy boundary.
  • Focused tests must enforce the exact schema key set as well as known sentinel absence; testing only one example path is insufficient.
  • Regression proof: SupportBundleFocusedTest, full standalone compilation, and MIDI_CLOSEOUT.

2026-08-01 - Standalone session evidence is not a crash detector

  • SpaceAge may run more than one standalone instance. Never infer an interrupted session from the mere existence of another active marker; first prove its per-session interprocess lock is no longer owned.
  • Customer and support wording must remain unclean_or_interrupted. A leftover marker can result from forced termination, power loss, an operating-system stop, or a crash, and the app cannot honestly distinguish those causes.
  • Session evidence must remain standalone-only. A plugin processor can be created and destroyed normally by a host for reasons unrelated to application shutdown.
  • Keep the journal schema closed. It may contain only schema version, closed-enum state, and UTC timing evidence; no project, lane, preset, sample, MIDI, hardware, path, filename, exception, or free-form text fields.
  • The exact-SHA clean-checkout runner builds and executes SessionEvidenceFocusedTest before canonical convergence.

2026-08-01 - Console tests can hide a standalone stack overflow

  • The audio self-test had a 256 MiB stack while the customer standalone retained Windows' 1 MiB default. Passing processor/editor tests therefore did not prove that JUCE's standalone wrapper could construct the editor.
  • SpaceAge's current editor initialization contains compiled startup frames slightly larger than 1 MiB. Keep the standalone target's measured 16 MiB reserve unless those large frames are deliberately moved to heap-backed storage and the launch gate proves the smaller reserve.
  • A successful build, matching SHA-256 copy, and VST3 test do not prove standalone startup. Release convergence must launch the actual standalone, require a real main window, request normal closure, and require exit code 0.
  • Packaging must test the copied staging executable rather than only the source artifact. This catches copy, metadata, signing, or staging regressions at the customer boundary.

2026-08-01 - Fresh Instruments Need Engine-Specific Neutralization

  • A generic source reset is not sufficient for engines with private oscillator, spectral, granular, unison, or motion parameters.
  • Fresh tonal engines must explicitly clear detune, pitch spread, inharmonicity, random pitch, modulation depth, gating depth, and secondary oscillator levels unless the factory INIT sound intentionally uses them.
  • Asset-backed engines must not be presented as ready-to-play fresh choices when no source asset exists. SoundFont and Quasar creation must begin from a loaded source workflow.
  • Keep fresh tonal output at concert pitch: MIDI note 69 must resolve to A440 within one cent unless the user deliberately changes tuning.
  • Regression proof includes the engine audio smoke suite, MIDI closeout lane workflows, and a human listening pass before public beta.

2026-08-01 - Synth Knob Sensitivity Is A Shared UX Contract

  • Do not inherit the generic 2200-3800-pixel full-sweep drag scale for instrument controls.
  • Standard synth controls use the shared 520-pixel sweep; time/envelope controls use the finer 720-pixel sweep.
  • Mixer, effects, sample-region, and Flux controls may keep separate scales because their physical ranges and interaction goals differ.
  • When adding an engine, explicitly apply the shared synth sensitivity constants to every customer-facing knob.

2026-08-01 - Chord Clip pan must not become hidden Mixer state

  • Chord Pan is per-ChordClip performance modulation edited in the Chord Performance popup. It is never lane pan, Mixer pan, Instrument pan, or pad pan.
  • Store the static center, motion enabled state, synced rate, depth, and shape on ChordClip. Motion adds a bounded, shaped offset around the static pan; when motion is off, the static center applies unchanged.
  • Use the shared tempo-grid division model for bars, whole, half, quarter, eighth, and sixteenth rates plus dotted and triplet variants. Do not create a Chord-only rate table that can drift from playback, export, or render timing.
  • Popup edits must refresh real-time playback. APPLY TO ALL propagates the complete Chord Pan recipe across ChordClips as one project operation, publishes every changed pattern, and supports exact Undo/Redo.
  • Persist every Chord Pan field. Clone must retain linked-score behavior; Variant must copy the complete recipe into its independent pattern before later edits diverge.
  • Internal playback and offline audio render apply the value as a voice-local additive pan offset. Audio render must reproduce the same tempo-synced motion heard in real time without changing saved lane or Mixer pan.
  • External/live MIDI and MIDI export may emit CC10 for the static center and moving pan. Live emission must not feed CC10 back into SpaceAge's internal MIDI-channel pan state or the result will be doubled and may linger after the marker ends.
  • Regression proof: popup controls and rate choices, real-time playback, apply-to-all with Undo/Redo, project persistence, Clone/Variant, CC10 MIDI export, and offline audio render parity.

2026-08-01 - APVTS attachments do not enter SpaceAge project Undo by themselves

  • SpaceAge constructs its AudioProcessorValueTreeState without a JUCE UndoManager; a SliderAttachment updates the parameter but does not create a project Undo record.
  • Every customer-facing continuous control that should be reversible must use the shared armParameterSliderUndo gesture hook.
  • Checkpoint on drag start, never on every value change. One held drag must remain one Undo action.
  • Merely attaching a Mixer bank or refreshing a control must not create history.
  • Regression proof opens the real Mixer page, edits its real gain fader, and proves exact one-step Undo/Redo through SPACEAGE_ARRANGER_EDIT_CONTRACT_ONLY.

2026-08-01 - Discrete APVTS controls need pre-attachment Undo

  • A JUCE ButtonAttachment or ComboBoxAttachment does not enter SpaceAge project Undo because the APVTS has no JUCE UndoManager.
  • Register SpaceAge's listener before constructing the attachment so a genuine user change can checkpoint the old parameter state.
  • Do not checkpoint every control notification. Parameter-driven refreshes notify the same controls and would create phantom history.
  • For buttons, compare the displayed toggle state with the current normalized parameter value. For menus, compare the displayed item index with JUCE's nearest item index for the current normalized parameter value.
  • Compact menus may expose fewer labels than the underlying choice parameter. Exact float equality is therefore wrong; use the same quantized-index rule as ComboBoxParameterAttachment.
  • Regression proof opens the real Mixer, toggles Mute, changes output routing, and proves exact Undo/Redo while all pre-existing no-op history boundaries remain green.

2026-08-01 - Master sliders are Mixer controls too

  • Arming channel-strip sliders does not automatically cover Master threshold, ratio, makeup, limiter ceiling, low-pass, or volume.
  • Every new parameter-attached Mixer or Master slider must call armParameterSliderUndo and expose a stable component ID for real-editor regression.
  • Isolated Undo tests are insufficient. Verify a mixed sequence containing a continuous control, discrete toggle, and routing choice; Undo must reverse the exact user order and Redo must replay it.
  • Regression proof reports mixerMixedHistory=1 in SPACEAGE_ARRANGER_EDIT_CONTRACT_ONLY.

2026-08-01 - Alternate slider input must share drag history

  • JUCE slider text entry and double-click reset already emit the slider drag transaction notifications used by SpaceAge Undo.
  • Do not add separate text-entry or reset checkpoints; doing so would create duplicate history entries for one user decision.
  • Regression proof must use the real editable label and a real mouse double-click, then verify exact Undo/Redo order.

2026-08-01 - Shared Effects controls need one history contract

  • Compact rack sliders, effect-enable toggles, Octave choices, selected-source sends, and deep-editor controls all participate in project Undo.
  • Parameter attachment refresh is not a user edit. For buttons and choices, compare displayed state with the current parameter before checkpointing.
  • Moving a selected-source send from zero may automatically enable its return. That send plus return-enable change is one transaction.
  • Deep effect controls need stable component IDs and listener cleanup because their editor is dynamically created and dismissed.
  • Regression proof reports effectsMixedHistory=1 and walks exact Undo/Redo order across compact, send, and deep-editor controls.

2026-08-01 - Hidden Instrument pages still need explicit project history

  • A control does not become undoable merely because it lives in the Synth drawer or has an APVTS attachment. Register every customer-editable slider, toggle, and choice with the shared project-history helpers.
  • Rebuilding an attachment can change listener order. Register discrete SpaceAge history before constructing the attachment so the old parameter value is still available for comparison.
  • Test hidden pages through narrow test accessors; component-tree searches only find controls on the currently attached/visible page and can produce false negatives.
  • The visible 4x4 Pad grid is MPC-ordered. Visible slot 0 maps to Pad 13 on bank one, not Pad 1. Tests and features must resolve padForVisiblePadSlot rather than assuming linear layout.
  • Representative control tests are necessary but not sufficient. Keep a mixed clip/lane/Instrument/automation/Mixer sequence to prove one coherent project history.

2026-08-01 - Project format compatibility must be checked before restoration

  • JSON syntax, a parameter tree, and a Pad array do not prove that a project schema is compatible with this build.
  • Require the exact SpaceAge project identity and a supported format version before calling restoreStateObject.
  • Apply the guard to both .sskit loading and host/plugin state restoration.
  • A rejected future-version file must leave tempo, notes, clips, and the rest of the open session unchanged.
  • Focused proof reports futureVersionRejected=1 and futurePluginStatePreserved=1.

2026-08-01 - Offline rendering must use one captured world

  • After project state is copied into an offline renderer, duration, tempo, timeline, routing, and audio must all come from that renderer. Never mix captured state with a live processor that may still be edited.
  • Reject NaN and infinity before audio reaches a file writer. A readable PCM file does not prove that the pre-quantized render buffer was valid.
  • A full-song export test must prove audible late musical content and an audible early effects tail, then prove the final tail becomes quiet. Merely checking file length can certify silence.
  • Store a loop in the golden fixture and require full-song export to ignore it.
  • Keep the fixture deterministic: disable probability, humanization, random pitch, Flux, and stochastic effect controls unless the test explicitly supplies a render seed.
  • Regression proof: GOLDEN_PROJECT_LIFECYCLE, canonical focused convergence 23/23.

2026-08-01 - Failed saves must be rejected before temporary staging

  • Reject an empty target, a directory target, and a parent path that cannot become a directory before constructing juce::TemporaryFile.
  • Do not treat createDirectory() as a best-effort side effect; inspect its result and confirm the parent is actually a directory.
  • Failure must leave the existing project byte-for-byte unchanged, reopenable, and associated with its prior tempo/state.
  • The open session is independent of destination failure and must retain edits made after the last successful save.
  • A failed Save/Save As must retain the current project target and report the exact rejected destination with an access hint.
  • Focused proof reports blockedParentSaveRejected=1, directoryTargetSaveRejected=1, priorProjectPreserved=1, failedSaveUiPreservedTarget=1, and failedSaveUiReportedDestination=1.

2026-08-01 - Recovery creation and discovery must define useful work identically

  • If autosave writes a state, recovery discovery must not delete that same state as empty. Keep one shared definition or paired regression coverage for Sections, edited default clips, Drum Pad labels, and private Instrument labels.
  • Never use a coarse timestamp as a unique recovery identity. Multiple instances and fast repeated writes can collide; use a sortable timestamp plus a genuinely unique suffix.
  • A recovered snapshot must remain unsaved and must never silently become the current overwrite target. The customer-facing identity is Recovered Session (Unsaved).
  • Retention may delete only malformed or expired SpaceAge-generated recovery files. It must never prune ordinary .sskit projects.
  • JSON syntax and top-level shape are not enough. All external project-state paths must pass the shared nested semantic validator before restoration. When adding a persisted field, add its required shape, finite/range rules, and hostile-state regression in the same change.
  • Regression proof: RECOVERY_RETENTION, P04-C, and canonical convergence 24/24.

2026-08-01 - Treat restore as a trusted sink, not a parser

  • restoreStateObject mutates many live subsystems and intentionally assumes its input has already been accepted.
  • Never call it from a new file, recovery, drag/drop, archive, clipboard, or host-state boundary without isRecognisableSpaceAgeProjectState or an equivalent trusted internal snapshot guarantee.
  • Parseable JSON can still be destructive: an empty step object used to default into musical data, while malformed automation could silently clear a payload.
  • Validate object-array shape, required fields, finite numbers, cardinalities, IDs, and timeline end positions before mutation. Fixed Pad/loop banks must be complete, velocity-layer declarations must match their arrays, fixed one-shot/mod-route collections must stay exact, and every clip must reference an existing lane.
  • Missing audio assets are not malformed project state. Accept their valid references and let the existing repair workflow report them.
  • Regression proof must cover both .sskit loading and setStateInformation, then compare the complete project state before and after rejection.

2026-08-02 - Preset state and visible controls must never diverge

  • APVTS attachments are the source of truth for ordinary sliders, rotary controls, choices, and toggles; do not manually shadow their values in editor-only state.
  • Finish every factory-preset and patch-file load with refreshSynthPresetPresentation. It retargets the active Instrument, rebinds the visible editor, refreshes source labels/readouts, and repaints custom ADSR/spectral displays.
  • A parameter value changing in the processor is not sufficient proof. Regression coverage must compare representative visible sliders and selectors with the recalled parameter values across every factory-preset family and through saved Redshift/native patch-file parsers.
  • New custom graphics that visualize preset state must be added to the shared presentation refresh or driven directly by attached controls.
  • SoundFont bank/program selection is distinct from SpaceAge performance overrides; do not imply that an SF2 preset recalled override values that the file does not define.

2026-08-02 - Loading and preset presentation contracts

  • Every path that displays startup progress or a busy overlay must clear that feedback on success, failure, cancellation, and refusal-before-start. A delayed callback can become invalid after the overlay is shown; recheck guards must release the UI before returning.
  • Releasing project-load feedback includes restoring the normal mouse pointer. Hiding an overlay while leaving a wait cursor behind still tells the user that work is running.
  • The startup Blank Project action must hide the startup panel only when the blank reset actually ran. A blocked reset is not a successful reset.
  • Preset recall is not complete when DSP state changes. The active Instrument slot, attachments, asset-specific editor, patch readout, lane badge, and relevant selectors must refresh from the recalled state without inventing a second source of truth.

2026-08-02 - Gotcha: Pad Records And Instrument Records Are Different Types

  • getInstrumentBayRecordForPad() accepts visible Drum Pad indices only. A private Instrument source must return an empty record through that API.
  • Use getInstrumentBayRecordForInstrumentSlot() for the private source owned by a non-drum lane, and getInstrumentBayRecordForLane() when lane ownership is already known.
  • Do not restore the old convenience behavior that clamps a private source through a Pad API. That silently recreates the deprecated Pad-proxy workflow and can derive the wrong Mixer destination.
  • numPads is currently a compatibility alias for total source slots, not the number of customer-facing Drum Pads. New processor code should use numDrumPads, numArrangementInstrumentSlots, or numSourceSlots according to the actual domain.
  • Keep a real visible Drum Pad sentinel in lane-creation regression coverage. Preserving only selected-Pad UI state is not enough proof that Instrument creation left Drum sound state untouched.

2026-08-02 - Add Clip placement is lane-local

  • The selected Arrangement lane is authoritative. Do not infer an Instrument destination from a selected Pad, selected pattern, or whichever clip was most recently opened.
  • If the selected lane is empty, Add Clip begins at step 0.
  • If the selected lane already contains material and the playhead target is step 0, Add Clip appends after that lane's last occupied step. It does not insert at song start.
  • A deliberate nonzero playhead target is an insertion request and should be honored on the selected lane.
  • Add Drum Clip follows the same destination rule but must reject non-Drum lanes and preserve the selected Drum pattern's intrinsic length.
  • Whenever lane Instrument replacement changes the DSP source, refresh the lane badge, patch identity, active Synth Editor target/tab, Piano Roll routing, and MIDI monitoring route as one transaction. Preserve the lane's Mixer Channel and existing clips.
  • Regression proof: L02-A and L03-A inside SPACEAGE_MIDI_CLOSEOUT_LANE_WORKFLOW_SLICE_ONLY.

2026-08-02 - Gotcha: Mixer Reassignment Must Refresh The Live Route

  • A lane's Mixer channel is active routing state, not merely a label saved in the project.
  • After reassignment, refresh the armed live-MIDI path and the unarmed Piano Roll audition context immediately; otherwise recorded playback and live playing can disagree until another UI action happens.
  • Normalize the lane's MIDI route Mixer field, publish playback/output snapshots, refresh the lane badge, and report the complete Mixer/MIDI identity together.
  • Persistence proof must compare the full input/output policy and customer-facing route summary before and after reopen, not only the numeric Mixer channel.
  • Keep hostile routing audio proof: muting the assigned lane Mixer must silence the private Instrument while muting a source-derived fallback must not.
  • When a saved hardware input, output, or Passport is unavailable, its control must say MISSING ... and retain the identifier; silently displaying INTERNAL ONLY would misrepresent the project and invite accidental rerouting.

2026-08-02 - Gotcha: Lane Zero Does Not Mean Drums

  • Instrument lanes are intentionally inserted at the top of the Arrangement stack, so lane zero can be an Instrument lane.
  • Use the owning ArrangementLane.type for playback, editing, export, preview, ghost-note, Variant, and sequencer decisions. Never test clip.lane == 0 to identify a Drum clip.
  • ArrangementClip.type is normalized from its owning lane whenever a clip is stored. Treat it as persistence/display compatibility, not a second source of truth.
  • New Drum lanes may be inserted beside existing Drum lanes at any index. Their pad-bank behavior comes from lane type, instrumentSlot == 0, instrumentId == 0, and the Drum MIDI policy, not their position.
  • Regression proof: drumBoundary=1 in SPACEAGE_MIDI_CLOSEOUT_LANE_WORKFLOW_SLICE_ONLY, plus SOURCE_CARDINALITY audio proof.

2026-08-02 - Gotcha: Clone Shares The Score, Variant Forks It

  • Arrangement CLONE must retain the same pattern identity. It creates a new clip ID and must not consume the next empty pattern.
  • A clone's notes, chords, and shared-pattern automation are linked musical content. Clip-local automation is copied to the new clip ID and remains independently editable.
  • VARIANT must allocate a new pattern, copy the complete musical payload, and mark the result independent. Subsequent source and variant edits must not cross-contaminate.
  • Lane ownership does not change during either operation: Instrument identity, patch, Mixer channel, MIDI route, and lane type remain authoritative.
  • Do not trust the linked label alone. Regression proof must compare exact representative musical and automation contents before and after edits and real project round trips.

2026-08-02 - Gotcha: Clip Length Is A Source Window, Not A Destructive Crop

  • Instrument clip length and source offset may address up to 4096 steps. Never route them through numSteps, which is the 64-step Drum Composer storage ceiling.
  • Validate sourceStart + length as one range. Independently clamping both values can create a clip that points beyond the legal source timeline.
  • Shrinking a clip must not delete notes, chords, or automation outside the visible window. Re-expansion is expected to reveal the hidden score again.
  • Persisted explicit pattern length is authoritative. Restore notes, chords, and expression first, then reapply saved pattern lengths because content insertion may infer a larger editing bank.
  • Ctrl+U pieces are linked windows onto one score. They need unique clip IDs and separately partitioned Clip Local automation, while shared notes, chords, and PTN automation remain shared.
  • Trigger ownership follows event start: an event beginning exactly on a cut belongs to the right piece. A sustained event begun in the left piece is not invented again as a new right-piece trigger.
  • Repeated clips remain atomic until a future explicit Flatten Repeats operation exists. Reject split without mutation or Undo history rather than guessing at repeated-source ownership.

Visible filter modes must own exactly one DSP stage

  • A synth-local filter and a shared post-voice filter must never process the same engine simultaneously unless the UI explicitly presents two filters.
  • Off must be a genuine audio bypass: changing cutoff, resonance, envelope, velocity response, or LFO depth may not change the sound while the filter is off.
  • Preset recall must update the filter type and controls on the same instrument slot used by DSP. Regression should compare audible bypass and active-mode responses and verify another slot remains untouched.

Stereo meters report post-pan truth, not stale symmetry

  • Channel-strip meters are fed from the post-pan, post-strip left/right samples. Do not replace these with a mono peak duplicated across both columns.
  • Normal release smoothing may continue when both channels fall silent, but a channel made silent by hard panning must clear immediately while the opposite side remains active.
  • Calculate meter columns from a symmetric inner rectangle. Ad hoc subtraction can leave unequal margins when strip widths are odd.

2026-08-02 - Gotcha: A Preserved Gap Owns Time, Not Music

  • A preserved gap must be canonical: pattern=0, sourceStart=0, repeats=1, transpose=0, linked, named SILENT GAP, and sized to its entire occupied timeline duration.
  • Removing a repeated Instrument clip creates one gap covering length * repeats; do not preserve the removed score's pattern, source offset, transpose, or repeat metadata inside the empty container.
  • Normalize adjacent and overlapping gaps using their occupied timeline ends, then subtract real clips. A shorter paste leaves one canonical remainder; equal paste leaves none; a longer paste may consume consecutive selected gaps without rippling later material.
  • Copy/paste must preserve user clip notes and Clip Local automation. Shared Pattern and Lane Local automation are separate owners and must not be duplicated, deleted, or shifted by gap replacement.
  • The legacy chain owns only the first Drum lane. When a completed chain mutation is explicitly projected back into Arrangement clips, replace that lane only; additional native Drum lanes must survive unchanged.
  • Playhead insertion that intentionally shifts the song is A02, not A06. Its Section and Lane Local automation timeline policy must be designed and proven separately rather than smuggled into gap replacement.
  • Regression proof: A06-A, plus neighboring A04-A, A05-A, AUTOMATION_OWNERSHIP, PROJECT_PERSISTENCE, and GOLDEN_PROJECT_LIFECYCLE.

Arrangement Capacity Is A Final-Layout Contract

  • Never preflight an Arrangement edit by counting only the obvious new clips. A real clip inserted inside a preserved gap may require both a left and right gap fragment; playhead insertion may split a real clip; Drum paste must fit both the future chain and its primary-lane Arrangement projection.
  • Capacity refusal must occur before checkpoint(), pattern clearing, chain mutation, or any clip/Section mutation. The entire serialized project and an existing Redo entry must remain usable after refusal.
  • Canonical gap accounting is: merge all gap ranges, resolve the hypothetical real-clip layout, subtract every real range from the merged gaps, then count the resulting fragments with the real clips.
  • The gap normalizer is a commit helper, not a capacity policy. It must never delete old gaps and then stop rebuilding when the array fills. An impossible projected result is a caller error and must leave the old ranges intact.
  • Drum paste has coupled ceilings: chain slots, retained non-primary clips plus future primary Drum clips, and total Drum timeline steps. Passing only the chain-slot check is not sufficient.
  • Arrangement recording also consumes timeline capacity. A new recording clip inside a gap uses the same final-layout preflight before it clears or materializes a pattern.
  • Regression proof: A07-A, plus neighboring A04-A, A05-A, A06-A, AUTOMATION_OWNERSHIP, PROJECT_PERSISTENCE, GOLDEN_PROJECT_LIFECYCLE, and MIDI_RECORD_TIMING.

Joint Section And Clip Moves Must Be Atomic

  • Preflight the entire selected destination before checkpointing or changing clips, Sections, automation, or history.
  • A joint move uses one shared horizontal delta. Do not let individually clamped Section deltas deform the selection.
  • Do not repair a joint move with overlap ripple after the fact. Collision is a refusal and must preserve the complete project plus Redo history.
  • Publish playback state once after both clip and Section arrays are committed. Publishing each half exposes a transient arrangement that never existed as a user decision.
  • Preserve clip IDs so Clip Local automation remains attached to the same musical object through move, Undo/Redo, and save/reopen.
  • A selected primary Chain-owned Drum projection clip cannot participate in native joint movement. Refuse the complete mixed move rather than partially moving its Sections or Instrument clips.

Drum Chain Projection Identity Is Persistent

  • Only the first Drum lane mirrors the legacy Chain. Additional Drum lanes are native Arrangement lanes and must survive projection unchanged.
  • ChainSlot.projectionClipId is the ownership key. Never recover identity by choosing the nearest Drum clip or by matching pattern alone.
  • A Chain property edit or reorder retains the slot's projection ID and Clip Local automation. A copied Chain slot receives a fresh projection ID and no automation alias.
  • Build and validate the complete projected lane before replacing live clips, pruning automation, or publishing playback state.
  • Legacy adoption may use an exact start plus semantic match once; ambiguous or duplicate IDs must be repaired deterministically.

Lane Audio Stem Export Must Preserve Ownership

  • Isolate stems at Arrangement lane event scheduling. Mixer-channel solo is not a lane boundary because one Drum lane can feed many Pad Mixer channels.
  • Capture project state once for the entire package. Do not read a changing live project independently for each lane.
  • Keep saved lane and Mixer mute/solo, gain, pan, effects, sends, and Master processing intact. State why a stem is silent instead of silently omitting it.
  • External-only routes intentionally produce silent audio stems. The manifest must say external-only; lack of local audio is not a renderer failure.
  • Every lane file starts at song zero and has the same musical-plus-tail sample count. Alignment is more important than trimming file size.
  • Suppress hardware transport and performance MIDI explicitly during offline render. Do not rely on the absence of a prepared physical endpoint.
  • Stage all files and the manifest under a unique temporary package. Publish only after every WAV verifies; cancellation or failure removes the staging package.
  • Refuse an existing destination rather than merging, overwriting, or recursively deleting user files.
  • A private Instrument source slot is not its Mixer channel. Sample rendering must resolve instrumentSlot -> owning lane -> mixerChannel before unmuting.
  • Regression proof: L08-A under CHAIN_AUDIO_EXPORT, plus SOURCE_CARDINALITY, project persistence, lifecycle, automation ownership, and effects-signal gates.

Drum Composer Editing Must Preserve Scope And History

  • Left-click on an empty cell adds and selects it; left-click on an active cell auditions it without deleting it.
  • Right-click deletes. Shift+right-click selects for Accent, Velocity, Probability, Ratchets, Pitch Lock, and Filter Lock.
  • A property drag is one Undo record, regardless of how many intermediate values it emits. A no-op must not checkpoint or destroy Redo.
  • Empty steps cannot retain hidden property edits that disappear after save/reopen.
  • Clear means Drum steps only. Preserve Piano Roll notes, Chord Engine markers, expression data, and the explicit pattern duration.
  • Drum entry, deletion, property edits, and Clear must all honor the selected clip's lane ownership. Do not let Drum controls mutate an Instrument-owned payload.
  • Regression proof: S01-A, with neighboring Arranger transaction and project-persistence gates.

Piano Roll Gestures Must Preserve Identity, Range, And History

  • A continuous create, move, resize, velocity, or erase-paint gesture is one Undo record. Checkpoint on the first real mutation, not every drag update.
  • A no-op gesture must not checkpoint, clear Redo, clone a note, or publish a false project change.
  • Do not derive selection identity solely from note values. Two notes may have identical start, length, pitch, velocity, and channel while remaining separate editable objects.
  • Group movement and resize must calculate one shared bounded delta. Per-note clamping deforms the selected musical shape.
  • The editable timeline must include sourceStart + clip.length; using only length hides later material in split or offset clips.
  • Keep UI and model pitch ranges aligned at MIDI 0 through 127. A narrower UI-era validation range silently discards valid imported or recorded notes.
  • Piano Roll mutations require Instrument-lane ownership. Refuse Drum-owned targets before checkpointing or changing the pattern.
  • Regression proof: S02-A, with neighboring S01-A, A04-A, P04-A, and canonical 30/30 Release convergence.

Transport Authority Must Be Explicit

  • Arrangement transport uses absolute song steps. Isolated Drum Composer and Piano Roll transport use local pattern steps. Never let a stale Arrangement target override an isolated sequencer start.
  • A Piano Roll opened from an Arrangement clip must translate its local ruler position through the clip start before updating the Arrangement playhead.
  • Pause captures the actual playback position before stopping. Resume starts from that captured position; restart and the 1 command are separate intentions.
  • The Arrangement loop and local step loop are mutually exclusive. Enabling one disables the other so two invisible ranges cannot compete.
  • Count-in target creation and the recording take are one Undo transaction. Cancelling before the first recorded event must remove any auto-created clip and restore the previous metronome state.
  • Metronome accent follows absolute musical time, not audio-buffer boundaries or a resetting local counter.
  • Transport mutations shared by the message and audio threads must use the processor callback lock. Do not repair timing races with UI delays.
  • Host Sync currently follows host tempo; customer wording must not promise host transport chase until that capability is actually wired and verified.
  • Incoming MIDI Stop/MMC may pause audio on the callback thread, but editor-owned recording transaction cleanup belongs on the message thread. Keep this in physical-hardware beta coverage.

Chord Marker Edits Must Preserve The Whole Performance Object

  • A Chord Marker is not only start, length, root, and quality. Every edit path must preserve gain, pan, six voice velocities, custom notes, strum, humanize, arp settings, playback mode, preserve-strum-end, mute, and Instrument ownership.
  • Chord drag, resize, erase-paint, Clone, and multi-item operations must checkpoint lazily once per gesture. No-op gestures must preserve Redo.
  • Floating Chord Performance and suggestion panels must write to the pattern and Instrument captured when the panel opened. Never resolve their target from whatever clip happens to be selected later.
  • Mixed Ctrl+U selections may contain Chord Markers too short for the requested division. Delete only markers that produced valid replacement pieces.
  • Typed entry must reject unsupported slash basses, MIDI notes outside 0...127, and clusters above the six-voice storage limit. Never clamp or truncate user input while claiming success.
  • preserveStrumEnd=true keeps a common chord endpoint; false gives each delayed voice the full authored chord duration from its own start.
  • Regression proof: S03-A, neighboring S01-A, S02-A, A04-A, P04-A, and canonical 31/31 Release convergence.

Arrangement Recording Must Preserve Time, Ownership, And Publication Order

  • Never map an absolute Arrangement position into clip-local time twice. Capture the target clip's source start and source-cycle length when recording begins, then use that immutable geometry for the note's complete lifetime.
  • Latency compensation for an Arrangement take must clamp to the selected source window, not merely to the complete pattern. Otherwise a compensated measure-one note can escape an offset clip and vanish from Piano Roll.
  • An exact release at the final end of a repeated clip maps to one source-cycle endpoint. Do not turn it into the full repeated occupied duration.
  • Active-note ownership is (input source, MIDI channel, note). Note number alone cannot represent two controllers or same-pitch channel independence.
  • A same-source retrigger closes the prior note at the retrigger time and starts a new note. Never overwrite the first note's pending ownership record.
  • Preserve release velocity from the note-off message; it is part of the authored MIDI performance.
  • Close held notes before disarm, target change, stop/restart, loop interruption, disconnect retirement, or Panic clears recording ownership.
  • Recording publication is asynchronous. Drain note and expression queues before save/state capture, quantize, Undo-sensitive transitions, or target retirement; copying live note vectors without the pattern lock risks stale state or iterator invalidation.
  • One user take needs one pre-take checkpoint. Replace-mode clearing and the captured performance belong to the same Undo decision.
  • Regression proof: S04-A, M04-A, REALTIME_RECORDING_NOLOCK, MIDI_RESTORE_RECORDING_BOUNDARY, P04-A, and canonical 32/32 convergence.

Long Clips Have Source, Window, Song, And Camera Coordinates

  • Pattern source extent is not clip length. A late note elsewhere in a shared pattern must not silently resize an Arrangement excerpt.
  • A clip's editable source window is [sourceStart, sourceStart + length). Ctrl+A, nudge, resize, quantize, drag, clone, and erase must not mutate material outside that window.
  • Arrangement placement is absolute song time. Piano Roll cursor and loop values are source-local and must translate through the selected clip before changing Arrangement transport.
  • The viewport is only a camera. FIT exposes the complete editable window; selection FIT centers selected material; playback follow should move the camera only when a zoomed playhead exits the visible range.
  • Repeated visual previews must tile the source-cycle drawing. Stretching one cycle across a repeated occupied span falsely describes the musical content.
  • Drum Composer intentionally authors a 64-step source cycle. A longer Drum Arrangement clip repeats that cycle; do not generalize Instrument long-source behavior by changing the Drum source contract accidentally.
  • Regression proof: S06-A, neighboring Piano Roll, Arrangement resize/split, Clone/Variant, project persistence, transport, and canonical 34/34 Release convergence.

Clear Commands Must Not Cross Editing Domains

  • Drum Composer Clear means Drum steps only. Piano Roll Clear means Piano notes plus Chord markers only.
  • Automation is never incidental Piano Roll content. Shared PTN, Clip Local, and Lane Local automation must survive both Clear commands.
  • Clearing visible content must preserve explicit Pattern duration, clip length/source offset/placement, Chain slot length/repeats, routing, and unrelated payloads.
  • Capture the Pattern target before opening an asynchronous confirmation. Never read the current selection again after the user responds.
  • Pattern identity defines sharing. clip.linked is presentation/workflow state, not a substitute for counting every Pattern reference.
  • Count both Arrangement clips and Drum-chain slots when disclosing shared impact. Recommend VARIANT before a one-placement-only destructive edit.
  • A no-op, cancellation, or ownership refusal must not checkpoint or destroy Redo. One confirmed clear must create exactly one Undo action.
  • Piano notes and Chord markers clear atomically under one Pattern lock and one playback snapshot publication; listeners must never observe a half-cleared intermediate state.
  • Regression proof: S07-A, neighboring S01-A, S02-A, S03-A, A04-A, A05-A, M03-B, P04-A, and canonical 35/35 convergence.

Arrangement Pointer Gestures Need One Visible Truth

  • The canvas selection is authoritative when a pointer gesture commits. Never move clips or Sections from an editor-side selection cache that the user can no longer see.
  • Remap moved clips by stable clipId, not by pattern, start, length, name, or other properties that legitimate duplicate clips may share.
  • A drop preview is a promise. Commit the exact previewed destination or refuse it; never silently search for a nearby opening.
  • Refused Section moves must republish processor truth because the canvas temporarily edits local Section geometry while dragging.
  • Escape must cancel active clip drag, Section drag/resize, clone drag, and lasso state before clearing selection. Otherwise mouse-up can commit an invisible abandoned gesture.
  • Ctrl-click selection subtraction must end that pointer action. Do not let the removed item become the drag anchor on the same click.
  • Edge scrolling must be timer-driven while a gesture is active. Mouse-drag events stop when the pointer is stationary, but the musician still expects the timeline to keep moving.
  • Regression proof: A01-A, A07-A, S06-A, S08-A, M03-B, and canonical 37/37 convergence.

Playhead Paste Is A Whole-Song Time Transaction

  • Derive one source span across every copied object. Do not calculate a separate insertion length per lane; silent or unselected lanes with later material must move by the same longest span.
  • Primary Drum time is chain-owned. Secondary Drum lanes are ordinary Arrangement clip lanes. Never apply primary-chain assumptions to every lane whose type happens to be Drum.
  • A primary Drum insertion may split preserved silence, but it must not cut through a real Drum block without a future explicit conversion/split design. Refuse before checkpointing.
  • Shift Lane Local automation at and after the insertion point. Copy Clip Local automation with each copied clip, including primary Drum projection IDs. Shared Pattern automation remains attached to its Pattern and is not duplicated as timeline data.
  • Named copied Sections are inserted at their relative offsets. Existing Sections shift; a crossed Section is split only when named Section material is inserted, otherwise it expands across the new blank time.
  • Preflight clips, split fragments, Drum-chain slots and total steps, Section count and bounds, automation bounds, and Arrangement capacity before mutation.
  • Defer Arrangement snapshot publication across the transaction. Playback must never observe clips shifted while the Drum chain, Sections, or automation still describe the old song.
  • One successful paste is one Undo action. Any refusal preserves both project state and an existing Redo branch.
  • Shortcut regression tests must count the lane under test rather than unrelated primary Drum projection objects created by chain synchronization.
  • Regression proof: A02-A, S08-A, A07-A, A06-A, M03-B, P04-A, and canonical 38/38 convergence.

Preserve-Time Delete Must Resolve Objects, Not Regions

  • Normal Delete is never ripple delete. Later clips, primary Drum slots, Section markers, and Lane Local automation retain their absolute song positions.
  • Remove native clips by stable clipId. Never delete every clip that overlaps the selected time range; stacked or intentionally overlapping unselected material must survive.
  • The first Drum lane is the primary chain projection. Additional Drum lanes are native Arrangement lanes and must use the same clip transaction as Instrument lanes.
  • A removed real native clip leaves a canonical gap for its full occupied duration (length * repeats). Removing a native gap deletes only the container; blank song time still exists.
  • A removed primary Drum block must remain a same-duration silent chain slot. Do not remove the chain slot, because that collapses all later primary Drum time.
  • Mixed clips and Section markers are one user selection and therefore one checkpoint, one publication boundary, and one status receipt.
  • Clip Local automation retires with the clip. Shared Pattern and Lane Local automation do not belong to that clip and must survive unchanged.
  • Preflight minimum Sections and empty patterns before checkpointing. Any refusal preserves Redo.
  • Regression proof: A03-A, A02-A, A06-A, and A07-A.

A Project Load Is A History And Identity Boundary

  • A successful load must clear Undo, Redo, and any feature-specific rollback record. Otherwise Undo can restore the prior song while the editor still saves to the newly loaded filename.
  • All successful customer project routes must converge on one adoption function. Loading state without synchronizing the target file, last-project preference, tempo, Arrangement, Instruments, Mixer, loops, auxiliary editors, and initial view creates split-brain UI.
  • A failed load must be atomic: keep the open musical state, current save target, last-project preference, labels, and history exactly as they were, then say that the current session was not changed.
  • Restore explicit effect enable flags exactly. A nonzero send is not permission to wake a return the musician saved as disabled.
  • Never resolve a stored relative asset path against the process working directory. Reject ambiguous references or deliberately rebase them against a documented project/package root.
  • File existence does not prove an asset is usable. A present but undecodable sample, one-shot, or loop needs the same repair receipt as a missing file.
  • Save As must be independently reopenable after the source project is changed or deleted and after the process working directory changes. Save Over must alter only the selected target.
  • Regression proof: P02-P03-A, project persistence, shared-effects state recall, and canonical Release convergence.

Never Invent Project-Load Progress

  • If restoration cannot report completed work units, show an indeterminate activity state. Elapsed time is not percentage complete.
  • A project load is a command boundary. Startup choices and global keyboard commands must not start transport, editing, or another load while restoration owns the UI.
  • Every delayed hide/completion callback carries the generation that created it. A stale callback must never dismiss a newer load.
  • Loading success and failure receipts need a status hold long enough to survive the Arrangement timer and be read by a human.
  • Do not delete a remembered project path merely because a removable or network drive is temporarily absent. Validate on explicit load and preserve the retry path.
  • Archive extraction completion is not project-load completion. Keep feedback visible through imported-project activation.
  • The current restore remains synchronous on the message thread. Indeterminate feedback is honest but may pause during very large asset restoration; a future staged loader needs an immutable worker-built load plan and a short atomic commit, not a casual background call to restoreStateObject().
  • Regression proof: P06-A, P02-P03-A, P04-B, and canonical 40/40 Release convergence.

Clean Shutdown Is Ordered, Not Merely Quiet

  • Stop MIDI and audio producers before destroying queues or devices. A stopped worker cannot rescue safety messages queued after it exits.
  • Drain all-notes-off, all-sound-off, sustain-off, and related high-priority safety traffic before closing hardware outputs.
  • Call the runtime release boundary from normal processor destruction as well as audio-device teardown, and keep it idempotent.
  • Write the clean-session marker only after transport, recording, voices, note ownership, queues, router threads, and device outputs have reached their terminal state.
  • Standalone SpaceAge startup choices are authoritative. Do not restore JUCE's hidden standalone filterState behind Last Project, Blank Project, or Load Project File.
  • Hosted VST3 state is different: the host owns plugin-state restoration, and reopening the hosted editor must not launch standalone splash or startup-choice surfaces.
  • A successful project restore clears the former project's Undo/Redo history. Relaunch must not expose actions from the previous musical world.
  • Regression proof: P08-A, MIDI closeout, project persistence, golden project lifecycle, and canonical 42/42 Release convergence.

Recovery Must Preserve Work Without Pretending It Was Saved

  • Do not use a short customer summary to decide whether state is recoverable. Parameter-only sound design, explicit pattern duration, routing, Flux, MIDI mappings, Hardware Passports, and SysEx may be the only valuable change.
  • Standalone recovery is owned by the standalone session. Hosted editors must not write into the global standalone recovery history because multiple hosts and editor reopen cycles have different persistence owners.
  • A recovered snapshot is an unsaved session. Loading it must clear the current save target, preserve the deliberate Last Project preference, and require Save As before it becomes a normal project.
  • Startup and Library recovery must share one production restore path. Separate implementations drift in UI synchronization, asset receipts, history clearing, and failure isolation.
  • Every delayed recovery callback carries a generation token and must fail closed after editor destruction or a newer load.
  • Do not let unchanged periodic snapshots evict useful distinct history. Delete the duplicate before applying the retention limit.
  • If snapshot writing falls back to temporary storage, discovery must search that fallback later or the successful write is operationally useless.
  • A malformed recovery must leave the open project, save target, Last Project preference, and UI unchanged.
  • Regression proof: P04-C, P04-D, P02-P03-A, P06-A, and clean-close/relaunch coverage.

Native Patches Never Own The Mixer

  • Never add Mixer gain, pan, mute, output, EQ, or shared-effects send IDs to a native synth patch allowlist, loader, saver, factory preset, clipboard payload, or reset path.
  • Treat Reverb, Halostar, EchoRay, Chorus, Flanger, Phaser, Tremolo, and Octave sends as lane/Mixer state. A patch load must leave them byte-for-byte unchanged.
  • Patch-owned drive, bit reduction, transient shaping, filter behavior, and similar processing are allowed only when they are intrinsic to the generated voice. Label that customer surface VOICE SHAPING, not EFFECTS.
  • Do not preserve obsolete hidden-send fields for unfinished-project compatibility. Unknown or forbidden patch fields are ignored rather than migrated into Mixer state.
  • When adding a synth engine, extend the X04 hostile-load test and audit its allowlist before exposing patch save/load.
  • Regression proof: X04-A, M03-A nativePatchNoMixerSends, X03-A, and canonical 44/44 Release convergence.

Mixer Bank Navigation Never Edits The Mix

  • Treat a Mixer bank as presentation state only. Switching banks must not clear or rewrite gain, pan, mute, solo, output, sends, dynamics, EQ, or automation.
  • Destroy an outgoing JUCE attachment before constructing its replacement. Direct unique_ptr = make_unique(...) constructs the new attachment first and can briefly connect two parameters to one control.
  • Never use a delayed “restore the old values” callback to repair attachment corruption. It can overwrite a legitimate automation or project-state change that happened after navigation.
  • Hidden Solo remains authored state. Disclose it in status/overview surfaces; clear it only through an explicit command such as Reset Audio.
  • Private Instrument source IDs are not Mixer Channel numbers. Omitted destinations resolve through the owning lane's published route, never source + 1 or an accidental Mixer 64 fallback.
  • Audio-thread route readers use immutable published Arrangement state rather than mutable editor-owned lane arrays.
  • Regression proof: X01-A, X02-A, focused receipts, and canonical Release convergence.

Audio Rescue And Master-Path Gotchas

  • RESET AUDIO changes authored Mixer/routing state. It must call one checkpoint before mutation so Undo restores the entire pre-reset state as one decision.
  • PANIC is transient runtime recovery. It must stop/flush sounding state and send MIDI safety messages without changing serialized project authorship.
  • Multi-Out is intentionally session/host-bus state. Project restoration and Reset Audio force a safe Main-output baseline unless a future explicit host-routing contract supersedes it.
  • Limiter regression tests must use a deterministic generated audio fixture. Instrument presets are unsuitable because oscillator, envelope, voice, or factory-patch evolution can create false failures unrelated to master-path safety.
  • Current limiter evidence proves sample-peak ceiling and below-threshold transparency. Do not describe it as a true-peak/inter-sample mastering limiter without a new DSP implementation and dedicated evidence.
  • UI page/bank navigation must never mutate Mixer values or retire audio processing. Keep continuous-playback navigation in the convergence suite whenever Mixer/editor ownership changes.

Drum Lab Flux And Render Boundaries

  • Flux range inputs are untrusted project/UI data. Clamp every node to 0...1 at the processor boundary, not only in the editor.
  • Cadence is authored state; the random draw is performance state. Save cadence/ranges exactly, but do not promise sample-identical random output unless a future explicit seed contract is added.
  • Flux applies only when a Kick, Snare, or Hat Lab voice starts. Do not let copied settings silently randomize unrelated engines.
  • Render to Sample must use a fresh offline processor restored from project state, suppress hardware MIDI/transport, clear loops, and write through a temporary file before replacing the target.
  • A successful Lab render must be finite, non-silent, stereo 48 kHz / 24-bit WAV and must report bounded monotonic progress through 1.0.
  • Reset Flux means disabled, cadence zero, counter zero, and every node range zero. Partial visual reset with live hidden ranges is a release defect.
  • Native engine additions are incomplete until they join the Save/Load control list, patch readout list, parameter allowlist, dirty-state refresh, Undo recall fixture, and factory audio smoke registry.

Native Synth Controls Share One Interaction Contract

  • Every customer-facing native synth slider must use configureNativeSynthControlInteraction(). Per-engine drag sensitivities recreate the inconsistency this helper exists to remove.
  • Slow pointer movement is the fine-adjustment path; faster movement may sweep the range. Keep exact text entry and the mouse wheel available rather than requiring full-screen dragging.
  • Arm one Undo checkpoint on drag start, not on every value callback. A continuous control gesture is one authored decision.
  • New controls and new synth subpages must be added to testNativeSynthControlQualityContract(). Test every subpage, not merely the initially visible tab.
  • Grid row counts derive from control count. Hard-coded rows caused SoundFont VIB FADE to render below the page when the bank reached thirteen controls.
  • Automated bounds at the canonical editor size do not replace human review at supported Windows scaling and the smallest supported window.
  • Regression proof: I07-A, I02-B, I01-A, and canonical 51/51 Release convergence.

Factory Presets Must Reset Every Shared State They Consume

  • A factory applicator is a complete audible-state transaction, not a convenient list of the values that differ from one imagined default.
  • Reset every shared modulation source a preset can consume before applying the recipe. Lunacy tonal presets previously inherited vibrato and gate state from the preset loaded before them even though their own recipe looked correct.
  • Test the actual customer-visible preset ComboBox as the factory manifest. A separate hard-coded list can silently miss new choices, while a clamping applicator can turn an invalid index into a false PASS.
  • Preset categories carry different musical contracts. Keys, Pads, Plucks, Leads, Organs, and Bells use restrained pitch dispersion unless a named interval is deliberate. Texture and Atmosphere may be broad, but must remain finite, bounded, and clearly categorized.
  • Validate cardinality, unique names, finite normalized/plain values, visible-control agreement, category-aware tuning, and voice retirement for every customer-visible choice.
  • CPU evidence is workload- and machine-specific. Keep deterministic regression ceilings, then repeat representative heavy cases on minimum and recommended customer hardware before making public performance claims.
  • Regression proof: I03-A, I03-B, I03-C, 619 factory choices, and canonical 51/51 Release convergence.

Granular Grain Position Is Not A Second Pitch Clock

  • A grain has a source-start position and an age. The start position chooses where to read; age advances pitch through the source. Advancing both every sample creates two clocks and detunes the result even when every preset parameter appears correct.
  • Keep grain position stable for the grain lifetime unless an explicitly named scan or scrub feature owns that motion. Use grain age and playback rate as the normal pitch clock.
  • Test generated and user-loaded sources through the real lane-owned melodic route. Raw MIDI-note injection can accidentally select a Drum pad and produce convincing but irrelevant evidence.
  • Measure a tonal INIT/default at A4, but do not use fundamental-pitch assertions for intentionally noisy or percussive engines. Asset-backed engines require real fixture assets.
  • Regression proof: I01-B and canonical 52/52 Release convergence in reports/release-convergence-i01b-20260803.out.

A Production Quasar Build Is A Snapshot And Publish Transaction

  • A package is one frozen source-state snapshot, not a live feed. Capture the source before worker rendering begins so edits made during a long build cannot produce a hybrid Instrument.
  • Render samples and write manifest.json inside a private staging package, then atomically publish by moving that complete folder to the destination. Never expose a half-built target, overwrite an existing package, or leave staging debris after failure or cancellation.
  • Cancellation wins before the publish commit. Once atomic publication succeeds, successful completion wins over a late Cancel click; do not report a published package as cancelled or delete it during completion handling.
  • Reserve 100% progress for successful publication. Captures and manifest writing may approach completion, but an unpublished package is not complete.
  • Completion may auto-load only when the active source slot, effective engine, and patch-parameter fingerprint still match the build's launch state. A source change leaves the successfully published package intact and reports that it was not auto-loaded.
  • Use deterministic portable ASCII sample filenames and package-relative forward-slash paths. Reject platform-dependent names and path traversal.
  • Prove the exact Cartesian coverage of requested roots, velocity layers, and round robins. First and last root zones must reach the selected range bounds, every root/layer pair must contain every requested round-robin index exactly once, and every declared sample must exist and load.
  • This boundary keeps Quasar useful for SpaceAge Scenes, AI patch generation, and portable collaboration: each consumer receives one complete Instrument artifact with stable provenance rather than mutable session state or private machine paths.

Hostile Instrument Resources Must Validate Before Publication

  • Treat SoundFont and Quasar loading as replacement transactions. Parse, validate, decode, bound, and prepare the complete candidate before changing any live Instrument field.
  • Do not hand obviously malformed SF2 data to TinySoundFont. Preflight the RIFF/sfbk structure, mandatory chunks, table record sizes, monotonic indices, referenced instrument/sample indices, and sample bounds first.
  • Quasar paths must be portable package-relative forward-slash paths. Reject absolute paths, drive prefixes, backslashes, empty components, ./.., traversal, and any resolved target outside the resolved package directory.
  • Bound zone count, per-sample frames, channels, sample rate, aggregate decoded sample values, numeric ranges, and finite sample data. A syntactically valid package is not automatically safe to allocate or publish.
  • Failure preserves the active engine, preset/path metadata, decoded cache generation, audible playback, lane/Instrument identity, backing slot, Mixer route, MIDI route, and history. A later valid replacement must still work.
  • One Instrument slot may own one external engine. Loading SoundFont clears stale Quasar state; loading Quasar clears stale SoundFont state; save/reopen must not resurrect the loser.
  • Missing-asset receipts cross asynchronous UI time. Include a project-generation/epoch token so a stale chooser callback cannot mutate a newer project that happens to reuse the same slot and path.
  • Same-project restore must stage external-resource adoption. Keep an exact same-path/preset known-good cache published until the replacement has decoded successfully; if the disk resource is now corrupt, retain that cache, mark it for repair, and retire only caches the incoming project did not reclaim.
  • Preserve Quasar round-robin cursor state with the retained cache. A fallback that keeps samples but silently resets performance state is not exact restoration.
  • Compare reloaded lane truth to the lane state that was actually serialized after normalization, not to an earlier caller request that the model may have canonicalized.
  • Regression proof: I08-B/C, focused receipts reports/hostile-instrument-resource-i08b-final.out and reports/hostile-instrument-resource-i08c.out, and canonical 54/54 convergence in reports/release-convergence-i08c-final-20260803.out.

A Page Colour Is Not A Page State

  • Do not communicate top-level navigation ownership by rewriting only buttonColourId. JUCE and accessibility-facing logic must receive the matching toggle state.
  • PADS and SEQUENCERS intentionally share the blue note-input family, but each must still distinguish its own active state from its inactive state.
  • Exactly one public page should be visible and exactly one corresponding page button should be active after every navigation path.
  • Shared-effect enable controls follow the same rule: the parameter, button toggle, active colour, and audible return must agree.
  • Regression proof: U02-B, focused PRIMARY_UI_VISUAL_STATE, and canonical receipt reports/release-convergence-u02-primary-state-20260804.out.

Rectification Is Not Silent Unless Its DC Is Removed

  • Full-wave rectification creates an octave-related component, but it also creates a positive DC component. Subtracting a fixed constant does not make that safe across different input amplitudes and turns literal silence into output.
  • Upward Octave stages must use stateful DC blocking after each rectification stage. One stage and two stages need independent state so +1 Oct and +2 Oct remain genuinely different.
  • Reset every DC-blocking state alongside the effect's other runtime state during audio prepare, Reset Audio, and project reset.
  • A non-tail effect regression must inspect late silence, not merely prove that its enabled render differs from dry. Sustained idle energy is a defect even when the active-input transformation sounds obvious.
  • Regression proof: X03-A, focused receipt reports/x03-full-rack-dc-safe-20260803.out, and canonical 54/54 convergence in reports/release-convergence-x03-full-rack-20260803.out.

A Readable Render Is Not Automatically The Live Mix

  • Release evidence for offline export must compare it with normal processBlock playback created from the identical captured state. A readable file with plausible peaks does not prove routing, automation, or effect parity.
  • Duration, tempo, timeline, routing, Mixer state, and effects must all come from the captured renderer. Never consult a live project that may continue changing during export.
  • Cancellation must leave an existing destination byte-for-byte intact. Render into staging storage, verify the result, and publish only after successful completion.
  • Compare after the destination encoding step. A tiny quantization difference is expected from a 24-bit WAV; a structural timing or routing difference is not.
  • Regression proof: X05-A, focused receipt reports/x05-offline-render-parity-20260803.out.
  • Do not reproduce popup bounds in a test-only layout model. Open the real component, run its production resized() path, and inspect the actual child bounds.
  • Test both a roomy viewport and the declared minimum. A fixed width can look correct at 1800 pixels while collapsing a selector, label, or hit target at 1100 pixels.
  • Containment is necessary but insufficient. Check label/control separation, minimum interactive sizes, and semantic grouping such as EchoRay dials remaining inside their intended cards.
  • Keep human readability, hover feedback, pointer feel, and Windows display scaling in the human QA matrix. Passing rectangle math is not permission to declare visual polish complete.
  • Regression proof: U01-A, focused EFFECTS_DETAIL_LAYOUT, and canonical reports/release-convergence-u01-library-layout-20260804.out (63/63 PASS).

Focused Views Can Leave Painted Geometry Behind

  • Hiding or zero-sizing child components does not stop custom paint() code from drawing labels, rows, separators, or instructions into the removed area.
  • A focus mode needs an explicit surface contract: retained surfaces, removed surfaces, expanded surface, and dismissal paths. Do not let compact and focused modes fall through the same paint tail accidentally.
  • Production geometry tests must inspect both child-component bounds and custom painted-area helpers. Component-only audits cannot detect text drawn into a zero-height list.
  • Regression proof: U01-B, focused AUTOMATION_EDITOR_LAYOUT, and canonical reports/release-convergence-u01-library-layout-20260804.out (63/63 PASS).

A Fixed Strip Count Must Scroll Before It Crushes Controls

  • Dividing the available width evenly across a fixed number of Mixer strips can produce technically contained but functionally unusable controls at the minimum viewport.
  • Protect a readable minimum owner width for each strip. When the complete strip bank exceeds the viewport, preserve the strip geometry and let the viewport scroll horizontally.
  • Vertical compression needs its own responsive rhythm. The Master section may use shorter meters, labels, and gaps at the minimum height, but no interactive control may collapse to zero or one pixel.
  • Regression proof: U01-C, focused MIXER_LAYOUT, and canonical reports/release-convergence-u01-library-layout-20260804.out (63/63 PASS).

Painted Containers And Laid-Out Containers Need One Geometry Source

  • A popup can pass child-containment checks and still look broken when its painted card and its control-layout card use different rectangles.
  • Define one responsive card-boundary helper and use it from both paint() and resized(). Production tests should inspect controls against that same public boundary.
  • Loading feedback belongs inside the contract alongside the initial choices; asynchronous state must not silently outgrow the original card.
  • Regression proof: U01-D, focused STARTUP_DIALOG_LAYOUT, and canonical reports/release-convergence-u01-library-layout-20260804.out (63/63 PASS).

A Button Row Is Not Responsive Just Because It Is Inside A Page

  • A fixed sequence of actions can exceed the viewport even when each button has a reasonable width. Audit the total row width, not only individual controls.
  • Preserve hierarchy before compressing. The Library Save action remains a centered hero; secondary file, bank, recovery, starting-point, and generated-project actions flow into responsive grids.
  • Test the real production page at both roomy and minimum supported viewports. Require containment, usable control size, and pairwise non-overlap.
  • Do not solve width overflow by shrinking text and targets until they are technically contained but hard to use. Wrap coherent actions into columns first.
  • Regression proof: U01-E, focused LIBRARY_LAYOUT, 58 audited control instances, and canonical reports/release-convergence-u01-library-layout-20260804.out (63/63 PASS).

A Shared Popup Ceiling Can Contradict Its Child Editor

  • A child editor can have correct internal geometry and still force needless navigation when its shared popup wrapper is narrower than the child's declared production width.
  • Preserve the primary work axis. Automation drawing needs its full horizontal surface; shorter screens may scroll vertically without introducing a horizontal scrollbar.
  • Transport controls shown on different pages must call the same command. Do not create separate page-local notions of Measure 1.
  • Regression proof belongs in both places: the popup-shell contract checks that a 1320-pixel editor fits horizontally, and the primary UI visual-state gate checks that Measure 1 leads every active transport cluster.

Human QA Evidence Is Not A Release Verdict

  • Never preselect PASS. A tester must deliberately choose PASS, FAIL, or BLOCKED after performing the selected test.
  • Human-reported evidence may annotate an authoritative blocker, but it must not close, rewrite, or replace that blocker automatically.
  • Keep QA ledgers outside musical projects. A project file must remain music and production state, not release-management state.
  • Treat the ledger as untrusted input on every read. Reject unsupported schemas, incomplete entries, broken sequence numbers, broken previous-fingerprint links, and fingerprint mismatches.
  • Never recover from a corrupt ledger by silently replacing it with an empty one. Lock saving, preserve the file, and explain the problem.
  • Re-read under an interprocess lock before append; an in-memory copy may be stale when two SpaceAge instances are open.
  • Bind evidence to an exact binary identity, not merely a marketing version or file timestamp.
  • Use history-preserving language unless the storage medium actually prevents replacement. Atomic replacement plus a verified hash chain is auditable history, not immutable storage.
  • An unkeyed SHA-256 chain detects mismatches but is not a trusted signature. A determined editor can recompute the whole chain; use trusted CI storage or signed attestations for adversarial release proof.
  • Plaintext evidence fields need privacy guidance. Do not prefill personal account names or encourage secrets, serial numbers, or unnecessary full paths.
  • Regression proof: U06-C, SPACEAGE_BETA_QA_RECEIPT_ONLY, and tools/test-beta-readiness-action-surface.ps1.

Keyboard Focus Must Not Masquerade As Musical Selection

  • Use a dedicated, restrained focus treatment that is visibly different from active, armed, selected, muted, soloed, and destructive states.
  • Apply focus and hover behavior in the shared LookAndFeel so new controls inherit the contract automatically.
  • Accessible names should prefer visible labels, then stable component names or IDs, then concise tooltip language. Do not expose blank or purely decorative controls as meaningful actions.
  • Automated metadata checks do not replace a Windows screen-reader pass or minimum-window visual review.
  • Regression proof: focused PRIMARY_UI_VISUAL_STATE gate, 2026-08-07.
  • Non-transactional popups should inherit Escape and click-away dismissal from a shared wrapper rather than each panel implementing its own partial convention.
  • Transactional import/export/recovery panels may retain explicit commit/cancel rules; do not make Escape bypass an in-progress or destructive boundary.
  • The popup shell owns reachability and dismissal. The child editor owns its content and operation semantics.
  • Keyboard focus must remain visible on buttons, menus, faders, bars, and rotary controls.
  • Regression proof: SETTINGS_ROOT_LAYOUT, PRIMARY_UI_VISUAL_STATE, EFFECTS_DETAIL_LAYOUT, and AUTOMATION_EDITOR_LAYOUT, 2026-08-07.

Painted Labels Are Not Automatically Accessible Names

  • A visible label next to a slider does not give the slider a spoken identity. JUCE accessibility metadata belongs on the interactive component itself.
  • Reuse the exact visible parameter vocabulary rather than inventing separate screen-reader names. This keeps synth UI, MIDI Learn, Automation, manuals, and support language aligned.
  • New native synth banks must pass keyboard focus, non-empty title, readable-value, fine-drag, gesture, Undo, and geometry checks together.
  • Regression proof: SYNTH_CONTROL_QUALITY, 590 visible controls / 590 accessible controls, 2026-08-07.

Contained Is Not The Same As Usable

  • A control can remain inside its parent and still be too small, crowded, or ambiguous to operate reliably.
  • Critical commands such as ARM, Mute, Solo, Save, Load, Clear, and Edit need semantic minimum targets, not merely non-empty rectangles.
  • Dense musical grids are a deliberate exception, not an exemption. Give them a separately documented minimum that preserves their no-scroll purpose and still supports accurate pointer use.
  • Test roomy and minimum supported viewports, including target dimensions, containment, and pairwise row separation.
  • Regression proof: ARRANGEMENT_LANE_VISUAL_STATE, PRIMARY_UI_VISUAL_STATE, and ARRANGEMENT_OVERVIEW_LAYOUT, 2026-08-07.

Painted Musical Objects Are Not Controls Until They Have An Interaction Model

  • A painted hex, note, curve point, or marker may be clickable without being keyboard reachable or screen-reader comprehensible.
  • Prefer one named, focusable canvas with roving selection over creating hundreds of tab stops.
  • Arrow keys should move the semantic selection; Space should audition or preview; Enter should commit; Escape should dismiss a non-transactional surface.
  • The accessible description must update when the semantic selection changes so assistive technology receives current musical context.
  • Regression proof: PRIMARY_UI_VISUAL_STATE honeycomb contract and strengthened AUTOMATION_EDITOR_LAYOUT, 2026-08-07.

Patch Changes Must Not Panic The Whole App

  • Before changing a lane-owned instrument patch, retire only voices belonging to that instrument slot. Do not issue a global panic or touch drum voices.
  • Opening an editor is not an audition gesture. Preview notes belong to explicit audition controls.
  • Changing loop bounds during playback must preserve the running playhead; transport relocation belongs to Play or an explicit navigation command.
  • Popup layouts must reserve their commit/action footer before assigning space to explanatory text.
  • Embedded startup artwork and About artwork are separate inventories, and every embedded asset requires a release-ledger fingerprint.
  • Regression proof: MIDI closeout live-loop resize plus Release compile, focused gates, and staged standalone launch, 2026-08-07.

Public Pages And Live Gestures Need Complete Ownership

  • Every customer-facing top-level page must be registered in component visibility, page switching, layout bounds, tooltips, and the primary visual-state regression. A tab button alone is not a page.
  • During a drag or resize gesture, update the canvas's local display model before repainting and committing through callbacks. Processor-only updates can remain invisible when full refresh is deliberately deferred until mouse-up.
  • Opening a musical editor is not an audition command. Popups should remain silent until an explicit preview gesture such as Space or an Audition button.
  • Distinguish framing intent: F follows selection; O presents the complete editable musical object.
  • Regression proof: PRIMARY_UI_VISUAL_STATE and PIANO_ROLL_WORKFLOW, 2026-08-08.

Font Enlargement Must Prove Readability And Containment Together

  • Increase common typography through the shared LookAndFeel so selectors, menus, tooltips, tabs, and accessibility language remain consistent across pages.
  • Pair every font-size minimum with geometry containment at roomy and minimum supported viewports. A larger label that clips or hides a control is not an accessibility improvement.
  • drawFittedText may silently shrink text to fit. It is useful as a last-resort guard, but it must not substitute for a deliberate readable minimum.
  • Popup-menu painting and popup-menu layout must use the same shared font source; otherwise the menu can measure one font and draw another.
  • Treat dense musical grids and timeline rulers as explicit domains with their own scaling contracts. Do not enlarge them blindly.
  • Regression proof: PRIMARY_UI_VISUAL_STATE, MIXER_LAYOUT, and the broader UI layout gate set, 2026-08-08.

2026-08-08 - Painted Musical UI Needs Explicit Type Contracts

  • A geometry audit of JUCE child components does not inspect text painted directly by a canvas. Graph readouts, timeline labels, badges, and axis text need named font-size constants plus focused regression assertions.
  • Do not force text into a density mode that cannot physically contain it. Preserve semantic state with a compact colour marker, then restore the readable label when space permits.
  • Arrangement has normal and pinned-gutter paint paths. Any lane-badge geometry, typography, or state change must be applied to and tested in both paths.
  • C++ local classes cannot contain ordinary static data members. For compile-time UI constants inside a local class, use static constexpr member functions (or move the class/constants to namespace scope).

Automation History Must Preserve Ownership Precedence

  • Effective Automation precedence is Shared Pattern -> Lane Local -> Clip Local. History restoration must refresh in that order so the most specific surviving owner sounds last.
  • Do not refresh every stored Automation row after a full-state Undo or Redo. Compare audible row fingerprints and refresh only changed, added, or deleted rows; otherwise unrelated history actions can resend hardware controllers.
  • External route reconstruction may emit an intentional MIDI safety burst. Regression checks for an Automation edit must count the relevant controller message separately from all-notes-off and controller-safety traffic.
  • A deleted row must reveal the next effective owner immediately, or return to neutral only when no owner remains.
  • Regression proof: AUTOMATION_OWNERSHIP, AUTOMATION_HARDWARE_HISTORY, AUTOMATION_RESTORE, and MIDI_CLOSEOUT, 2026-08-08.

Scrollable Popups Must Use Their Actual Display

  • Size screen-space callouts against the display containing the anchor, not unconditionally against the primary monitor.
  • Minimum-viewport tests need a deterministic work-area override; testing only the developer machine's current display cannot prove an 1100x800 contract.
  • Destructive-action receipts must remain visible after an action invoked at the bottom of scrollable content.
  • Regression proof: AUTOMATION_EDITOR_LAYOUT, 2026-08-08.

Automation Deletion Must Be Atomic And Ownership-Aware

  • A partial Shared or Lane deletion must resolve the entire Shared Pattern -> Lane Local -> Clip Local stack before refreshing live MIDI. Never send the edited owner's remaining value blindly when a more specific owner survives.
  • Validate identity, index sets, duplicates, and ranges before creating an Undo checkpoint. A refused or empty destructive action must not consume history or erase the Redo branch.
  • Range deletion uses an end-exclusive tick boundary. Reversed drag endpoints must normalize to the same contract.
  • Consolidate interpolated snapped drag steps into bounded range mutations and disclose the full gesture span in the receipt.
  • Regression proof: AUTOMATION_DELETE_MATRIX, AUTOMATION_HARDWARE_HISTORY, AUTOMATION_OWNERSHIP, and MIDI_CLOSEOUT, 2026-08-08.

Dynamic Status Copy Needs A Production Callback Test

  • Customer-facing status strings generated inside callbacks can escape static layout and source sweeps.
  • When a callback teaches ownership, routing, deletion, or safety state, exercise that callback in the nearest production UI contract and assert the meaningful sentence.
  • Reject malformed implementation artifacts such as dangling arrows in visible prose.
  • Regression proof: AUTOMATION_EDITOR_LAYOUT, PRIMARY_UI_VISUAL_STATE, and MIDI_CLOSEOUT, 2026-08-08.

Launch-Proof Actions Must Open The Test Surface

  • Do not use the main MIDI Health action as a generic shortcut to a QA receipt form.
  • Route each pending receipt to the surface where its behavior can be exercised: Automation, MIDI Timing, Hardware Passport, MIDI Maps, SysEx Vault, MIDI Import, or Beta Readiness.
  • Keep evidence capture separate. PASS, FAIL, or BLOCKED belongs to the dedicated proof action after the test is performed.
  • A green software gate does not replace real-controller, real-file, real-host, or human listening evidence.
  • Regression proof: MIDI_HEALTH and MIDI_CLOSEOUT, 2026-08-08.

Hardware Timing PASS Requires Reproducible Evidence

  • Do not accept a generic statement such as "timing looked good" for controller-latency or live-timing PASS.
  • Record the controller, complete connection path, audio buffer, observed or measured latency, compensation/pinning, and timing/recording result.
  • For live timing, also record tempo/scenario, monitor feel, and recorded placement.
  • A prefilled template is guidance, not evidence; unchanged blank labels must fail save validation for every result.
  • Keep the template derived from the receipt key so persistence cannot drift from the UI contract.
  • Regression proof: MIDI_QA_RECEIPT, MIDI_HEALTH, MIDI_RECORD_TIMING, MIDI_LIVE_INPUT_HEALTH, and MIDI_CLOSEOUT, 2026-08-08.

Release-Critical Health Rows Must Stay Above The Fold

  • Do not append launch proof after long diagnostic inventories; operators must see the active card, verdict, run, evidence, and action immediately.
  • Keep a compile-time or focused-layout guard tying critical-row count to the visible-row budget.
  • Route RPN/NRPN execution to MIDI Patch, not the generic MIDI Maps surface.
  • Use capture language when evidence is missing; REVIEW PROOF falsely suggests a receipt already exists.
  • Regression proof: MIDI_QA_RECEIPT, MIDI_HEALTH, MIDI_CLOSEOUT, and SETTINGS_ROOT_LAYOUT, 2026-08-08.

Inspect-Only Import Must Report Nonmutation

  • Inspect-only mode is authoritative. Preserve-expression, metadata, device-review, or SysEx flags must never make willMutateProject() return true.
  • Serialization and UI summaries must describe the same nonmutation contract as execution.
  • Test inspect-only mode with every optional flag enabled so later features cannot accidentally weaken the guarantee.
  • Regression proof: MIDI_IMPORT_TRANSACTION and MIDI_PROTOCOL, 2026-08-08.
  • JUCE AlertWindow buttons dismiss the modal before the completion callback validates the captured fields.
  • Never show a missing-field warning and simply return; that destroys the user's entered evidence.
  • Capture the complete draft before warning, then reopen the workflow after acknowledgement and repopulate every common and specialized field.
  • Keep Cancel as a deliberate discard and never append an invalid draft to the evidence ledger.
  • Regression proof: SETTINGS_ROOT_LAYOUT, MIDI_QA_RECEIPT, MIDI_HEALTH, and MIDI_CLOSEOUT, 2026-08-08.

Painted Status Needs A Semantic Twin

  • Text drawn only in paint() is invisible to screen readers even when it is visually prominent.
  • Expose one concise component description derived from the same health model; do not maintain a second status truth.
  • Notify accessibility clients when the model changes so stale proof state is not announced.
  • Keep the title separate from dense command controls. Fixed right-edge button packing will eventually collide as labels or localization change.
  • Derive command widths from available space and keep oversized diagnostic content inside the tested scroll shell.
  • Regression proof: SETTINGS_ROOT_LAYOUT, PRIMARY_UI_VISUAL_STATE, MIDI_QA_RECEIPT, MIDI_HEALTH, and MIDI_CLOSEOUT, 2026-08-08.

Do Not Test A Copy Of Presentation Logic

  • Dense responsive rows should use one pure geometry helper in production and tests.
  • Accessibility tests should call the same semantic-summary builder announced by the live component.
  • Cover both ordinary and compact supported widths; containment alone is insufficient if targets become too narrow to read or click.
  • Keep spacing and ordering deterministic so later label or localization work fails visibly instead of silently colliding.
  • Regression proof: SETTINGS_ROOT_LAYOUT, PRIMARY_UI_VISUAL_STATE, MIDI_HEALTH, and MIDI_CLOSEOUT, 2026-08-08.

Ordinary Popups Need One Escape Contract

  • Do not reimplement Escape dismissal separately in every lightweight JUCE callout.
  • Use the shared focus-aware popup content behavior and publish a clear accessible title/description.
  • Leave click-away to the callout shell unless the workflow has an explicit reason to stay open.
  • Do not wrap guarded or destructive confirmation workflows blindly; preserve their deliberate decision boundary.
  • Regression proof: PRIMARY_UI_VISUAL_STATE (popupEscape=1), AUTOMATION_EDITOR_LAYOUT, SETTINGS_ROOT_LAYOUT, and MIDI_CLOSEOUT, 2026-08-08.

Compressed Musical Previews Must Include Both Ends

  • Do not reduce long note, Chord, waveform, or Automation displays with a naive every-Nth-item stride; it can omit the final musical event and make later material appear absent.
  • Use the shared representative-index selector so short content remains exact and dense content spans first through last.
  • Keep the production selector and regression proof shared rather than testing an imitation.
  • Regression proof: ARRANGEMENT_LANE_VISUAL_STATE (previewCoverage=1), 2026-08-08.

A Helper Test Is Not An Integration Test

  • A correct pure helper can still be disconnected, fed the wrong data, or indexed incorrectly in production.
  • For compressed clip previews, build real pattern content, refresh the editor, then inspect the actual canvas payload.
  • Require the first and final temporal regions after the production path, not only from the representative-index helper.
  • When a focused slice owns release-critical booleans, include those same booleans in the complete closeout verdict; otherwise the broad gate can report success while silently skipping a workflow.
  • Regression proof: ARRANGEMENT_LANE_VISUAL_STATE and MIDI_CLOSEOUT (arrangementClipPreview=1 plus all lane workflow fields), 2026-08-08.

2026-08-08 - Full Render And Loop Render Must Never Share Implicit Semantics

  • Full Arrangement WAV must ignore Arrangement and step-loop playback state.
  • Active-loop WAV must be an explicit command and must snapshot start/end before asynchronous work begins.
  • Renderer ranges use an inclusive UI end converted once to an end-exclusive processor boundary.
  • Tempo-multiplier duration must include only timeline segments overlapping the captured range.
  • Invalid or cancelled range renders must preserve an existing destination file.
  • After the musical range, stop transport before rendering the fixed effects tail; never wrap and retrigger step zero.
  • Regression proof belongs in CHAIN_AUDIO_EXPORT beside full-render parity and lane-stem continuity.

Source-Backed Patches Must Be Atomic

  • Never delete a destination before determining whether it is also the active source file.
  • Use an atomic staged copy for patch-bundled WAV publication; same-file save-over must preserve the existing bytes.
  • Resolve a safe relative bundle before an original absolute path so a moved patch remains portable and a stale original cannot override it.
  • Validate that the source decodes before changing engine, parameters, source cache, patch name, Undo history, or audition state.
  • A missing or unreadable dependency must refuse the patch and preserve the previous Instrument exactly; never present a new patch name over an old sample.
  • Regression proof: INSTRUMENT_PATCH_LIFECYCLE (sourceBacked=1), 2026-08-08.

External Source Existence Is Not Source Availability

  • A stored path is not healthy merely because a filesystem entry exists; the audio must decode into usable sample data.
  • Missing-source receipts must describe musical ownership: Liftoff Source and Lunacy Source for those engines, not a generic Pad sample label.
  • Private Arrangement slots are Instruments. Do not expose their backing-slot number as a Pad identity.
  • After Load Source or Clear Source, invalidate old receipts, rescan dependencies, and refresh both the active editor label and Library repair UI.
  • Intentional source clearing must remove the dependency rather than preserving a stale repair request.
  • Regression proof: MISSING_ASSET_REPAIR (sourceRefresh=1/1), 2026-08-08.

Complete Project Asset Scans Must Include Private Instrument Slots

  • Never use numDrumPads as the upper bound for project/archive asset discovery; it excludes private Arrangement Instrument source slots.
  • Use the complete source-slot domain, then let engine and source state decide whether a slot contributes an asset.
  • Rescan the dependency ledger before staging and refuse publication when anything is unavailable. Silently skipping a missing file produces a dishonest package.
  • Archive placeholders are typed internal references, not ordinary filesystem paths. Validate the recognized prefix and a traversal-free suffix explicitly rather than depending on platform-specific absolute-path behavior.
  • A package test must remove every original source before import and then prove restored asset-backed Instruments produce finite nonzero audio.
  • SoundFont is an ordinary archived asset; Quasar is a package directory with its own typed placeholder. Both must remap below the import root and remain mutually exclusive per Instrument slot.
  • Import success is not the final portability boundary. Save the imported state as an ordinary project, reopen it in a fresh processor, and repeat path ownership plus playable-engine checks.
  • Regression proof: PROJECT_ARCHIVE (remapped=5, resourceAudio=1, resave=1/1/1 for Liftoff/Lunacy/SoundFont/Quasar), 2026-08-09.

Archive Bytes Must Activate Through Production Loaders

  • File existence is not archive integrity. Validate staged resources by loading the staged project in a fresh processor.
  • Import validation must bind placeholders to extracted staging paths before final destination paths exist; otherwise validation either checks nonexistent paths or commits unverified content.
  • Do not trust healthy in-memory decoded audio after the source file has changed on disk.
  • Archive indexing changes basenames. Folder repair must compare both the stored indexed name and its safe three-digit-prefix-free original.
  • Manual repair filters must include every format accepted by the corresponding production loaders, including FLAC.
  • Recovery and relink must preserve healthy imported paths while repairing only the unavailable external receipt.
  • Regression proof: PROJECT_ARCHIVE (corruptRefusal=1, corruptImportRefusal=1, mixedRecovery=1/1/1/1/1/1) and MISSING_ASSET_REPAIR (archiveNames=1), 2026-08-09.

Archive planning is not archive publication

  • Export plans snapshot metadata but may still reference live resource paths. Never treat planning-time validation as publication-time validation. Validate the finished temporary archive through the production import path before replacing an existing customer file.

Direct processor tests do not prove a plugin wrapper

  • A standalone launch or direct AudioProcessor test does not prove VST3 discovery, wrapper state, editor lifecycle, or host integration. Run the exact VST3 bundle in an isolated JUCE host process.
  • The current host harness uses a larger Windows stack because constructing the complete SpaceAge editor exceeded the default console-test stack. Keep that linker setting unless editor construction is materially reduced and remeasured.

Release tests must own their selectors

  • Clear every SPACEAGE_*_ONLY process variable before each release gate. A hand-maintained selector list can miss a new variable and let an unrelated focused test impersonate another gate.
  • Preserve and restore inherited selector values only after the convergence run has ended.
  • Every native release gate needs a timeout and a bounded post-kill wait. Never replace one possible deadlock with an unbounded cleanup wait.

State round trips must begin with a real mutation

  • Saving default state and restoring it into another default instance does not prove setStateInformation works.
  • Mutate an automatable parameter, require the serialized state to differ, then verify the parameter value after restoring into a fresh plugin instance.
  • Use a continuous parameter for normalized-value proof. A boolean/discrete parameter can canonicalize an arbitrary host value and create a misleading comparison.
  • Audible VST proof must exceed a pre-note silence baseline; any nonzero sample can be startup noise, DC, or autonomous output rather than MIDI response.

Plugin bundles need an exact inventory

  • A reusable build artifact directory can retain stale or foreign files. Do not recursively package it without an allowlist.
  • Include hidden files in inventory checks and reject everything except the expected module metadata and architecture binary.
  • Pass the selected build configuration through every artifact resolver and launch/host proof. A Debug convergence run must not silently validate a Release executable.

Editor construction is not editor hosting

  • createEditorIfNeeded() proves only that an editor object can be constructed. Attach it to a native peer, pump the JUCE message queue, tear it down, and repeat before claiming editor reopen compatibility.
  • Keep the hosted editor offscreen during automation so release verification does not steal focus or flash a customer-sized window.

Reproducible archives need deterministic metadata

  • Sort every archive entry, include hidden files, and assign a fixed ZIP entry timestamp. Directory enumeration order and copied file timestamps are not stable release inputs.
  • Bind ReleaseManifest.generatedUtc to the source commit time, not the packaging wall clock.
  • Compare final ZIP hashes as well as normalized manifests. Matching payload descriptions do not prove matching published bytes.

A clean patch fingerprint is the final initialization step

  • Do not capture a native Instrument's clean comparison fingerprint until every engine parameter, source choice, and derived state has reached its final sounding default.
  • Capturing early makes a factory-fresh lane display an unsaved-change marker even though the customer changed nothing.
  • Test the real Add Instrument Lane workflow, not only direct preset application.

Customer-facing patch names must describe audible state

  • A default label such as Callisto Marimba is a behavioral promise. Its underlying Physical model and parameters must actually produce that class of instrument.
  • When a factory choice uses an existing preset, prefer its honest family name over a more decorative but inaccurate label.
  • Verify engine, patch receipt, audible output, release, and dirty-state presentation together.

Dense technical panels must scroll before their text shrinks

  • Passing containment and overlap tests does not prove customer-facing prose is readable; drawFittedText may silently compress nominal font sizes.
  • Keep advanced Settings panels within the supported fixed-width allowance, then increase their natural vertical height inside the shared scroll shell.
  • Protect tertiary, detail, and body typography with explicit minimum constants and a regression contract. Do not reintroduce 7-to-9-point action labels merely to avoid vertical scrolling.
  • Human review remains mandatory at 100%, 125%, and 150% Windows scaling with long device and profile names.

Asynchronous alerts need explicit presentation ownership

  • AlertWindow::showAsync does not guarantee that a dialog will inherit the main editor's custom look-and-feel merely because it was launched by editor code.
  • Associate modal options with the owning component, or explicitly apply the shared look-and-feel to a custom AlertWindow before showing it.
  • Any new destructive, rename, relocation, plug-in-scan, or hardware-transfer dialog must be covered by the alert presentation contract so generic Windows/JUCE styling cannot return unnoticed.

Do not use ownerless alert convenience calls

  • New informational alerts must go through the SpaceAge helper and supply the originating component. New confirmations must call withAssociatedComponent before showAsync.
  • A custom AlertWindow must explicitly receive the active SpaceAge look-and-feel before entering modal state.
  • This is more than decoration: ownerless dialogs can center on the wrong display, lose application typography, weaken accessibility context, and make hardware safety wording look unrelated to the action that produced it.

Painted instructions need an accessibility twin

  • Text drawn directly in paint() is invisible to screen readers. Any painted guide, diagram, legend, or onboarding card must publish the same useful meaning through component title, description, and help text.
  • Use the exact customer-facing names of controls and workspaces. If Add Lane, Piano Roll, Mixer, or Render is renamed, update both painted and accessible guide text in the same change.
  • Test instructional surfaces at the roomy and minimum supported viewports. A visually attractive guide that clips at the supported minimum is not a guide.

Enabled startup actions cannot be placeholders

  • An enabled choice in the standalone startup dialog must complete the action its label promises. Status text such as COMING SOON is not an implementation.
  • Reusable kits and Instruments are Starting Points; complete-project templates are a broader future category. Do not call one the other merely because they may share a Library screen.
  • Any new startup destination must be covered by a regression that invokes its production callback, verifies the destination and usable content, and confirms a clear next-action receipt.

Do not publish the feature backlog as disabled menu items

  • A disabled coming soon row creates visual clutter, implies an unfinished product, and makes accessible navigation announce choices that cannot complete a task.
  • Keep future ideas in the backlog and design documents. Add them to a production menu only in the same change that supplies behavior, project persistence where relevant, Undo/Redo, accessible metadata, and a production-callback regression.
  • Lane badge tooltips must be derived from current behavior. Do not leave roadmap vocabulary such as ordering, collapse locking, ghost controls, or colors after removing or relocating the associated commands.

A sound browser label must match the model it invokes

  • Internal variable and callback names are not customer evidence. Verify the visible label, callback, engine/model selection, metadata, and auditioned sound as one identity contract.
  • When adding or renaming a Starting Point, preset category, Instrument Bay entry, or template, extend the owning regression with the exact customer label and a live callback check.
  • Do not invoke project-mutating sound-bank callbacks from a layout test merely to prove wiring. Protect callback presence and ownership automatically, then keep the auditory identity as an explicit human release check.

Do not normalize first-party compiler warnings

  • A warning that appears harmless today becomes camouflage for tomorrow's real conversion, lifetime, or ownership regression. Rename locals that mask long-lived state and make intentional numeric narrowing explicit.
  • Prefer current JUCE layout APIs over deprecated convenience measurements so framework upgrades do not turn accumulated warnings into emergency migration work.
  • Keep vendor diagnostics visibly separate from first-party diagnostics. Do not edit retained third-party code merely to make the build quieter unless the dependency decision and license record explicitly support that maintenance fork.

Song Form presets own markers, not music

  • APPLY FORM replaces the Section map only. It must never move, resize, recolor, rewrite, or delete Drum or Instrument clips as a side effect.
  • Every visible form must have an exact regression fixture. Testing one preset does not protect the others from stale IDs, renamed menu items, incorrect Section types, or accidental length changes.
  • Reapplying an already active form is a no-op and must not pollute Undo history. A future destructive or ripple-aware form command requires a different customer-facing name and an explicit transaction contract.

A wired Starting Point is not necessarily a playable Starting Point

  • A button label and non-null callback prove navigation only. The production callback must also configure the intended scope, publish meaningful labels, select the promised engine family, and render finite non-silent audio.
  • Starting Point collections may intentionally differ in scope. Record that scope explicitly in the regression instead of assuming every action fills all 64 pads.
  • Clear pending voices between automated bank auditions. A startup chime, bank-loaded voice, or previous pad tail can otherwise create a false-positive audio result.
  • Automated audio establishes function and safety; human audition remains responsible for musical usefulness, perceived loudness, and name-to-sound honesty.

Generated VST3 metadata must be valid before verification

  • JUCE 8.0.8 can generate moduleinfo.json with trailing commas that PowerShell and strict hosts reject.
  • Normalize only commas immediately before ] or }, then parse the result as JSON before allowing any VST3 smoke test or package operation to continue.
  • Keep normalization separate from inventory and host verification. A verifier must never silently accept malformed metadata.

A visible cutoff value is meaningless when the filter is bypassed

  • A factory patch that stores a shaped cutoff must also select an audible filter mode. Otherwise the UI appears broken even though parameter attachment and DSP are individually functioning.
  • Test the complete signal path: preset recall, visible control, parameter value, filter mode, and measured audio response. Do not accept a UI-only control test as proof of audible behavior.
  • Filter Type Off is a true bypass. In that state Cutoff and Resonance are intentionally inert and must not be marketed as shaping the sound.

Startup artwork must have one authoritative shipping set

  • Keep the About image separate from startup rotation.
  • Embed and rotate only the current marketing-approved startup files. A historical image may remain in the repository, but CMake, BinaryData references, and release manifests must not treat it as shipping artwork.
  • Any startup image-set change must update resource privacy, asset clearance, provenance, and legal-signoff inventories together.

JSON acceptance must not depend on the PowerShell generation

  • PowerShell 7 may accept trailing commas that Windows PowerShell 5.1 and strict VST3 hosts reject. A successful ConvertFrom-Json call in one shell is not cross-runtime proof.
  • Normalize JUCE-generated moduleinfo.json deterministically after every build path, not only during customer packaging. Remove only commas immediately before } or ], then validate the resulting JSON.
  • Keep normalization, exact bundle inventory, and real host smoke as separate gates. Passing one does not imply either of the others.

Responsive labels need stable control identity

  • Regression tests, accessibility, and automation must not locate important controls by transient visible text or by whichever anonymous component happens to appear first.
  • Give customer-significant controls stable component IDs. Visible labels may then shorten, localize, or reflow without silently breaking the contract.
  • Audio assertions must retire prior voices and effect tails when the test claims to compare isolated sources. Long-release fixture bleed is not evidence about the source under test.

Hash-locked text must not depend on checkout line endings

  • Git may materialize the same tracked text as LF or CRLF according to machine configuration. Raw byte hashes of source evidence therefore need either an enforced repository line-ending policy or canonical text hashing; SpaceAge uses both.
  • Keep * text=auto eol=lf in .gitattributes and explicit -text boundaries for audio, artwork, fonts, archives, and binaries.
  • Any new text extension entering a dependency evidence ledger must be added to the canonical-text hash set. Never canonicalize binary evidence through a text decoder.

Clean proof must consume its own pinned dependencies

  • A detached clean build must not silently rely on the developer's adjacent work folder. When CMake uses pinned FetchContent fallback, pass that exact fetched source to dependency verification explicitly.
  • JUCE fallback checkout must retain core.autocrlf=false and core.eol=lf; otherwise an exact commit can produce a different byte fingerprint on Windows.
  • Generated-artifact normalization belongs immediately after every build path. Strict VST3 inventory and host smoke remain verifiers and must not repair malformed metadata themselves.

Parameter registration is not proof of functionality

  • A visible widget, APVTS parameter, attachment, persistence, and Undo support can all exist without any DSP consumer.
  • Audit registered parameter members against runtime audio reads. A parameter with setup-only references is dead state until proven otherwise.
  • Do not invent a vague DSP meaning merely to preserve an old label. Repair a clearly intended path or remove the parameter, widget, preset entry, reset entry, attachment, and test expectation together.
  • Full processors such as the channel compressor must not retain superseded macro parameters unless that macro has an explicit, tested mapping to the live controls.

Deterministic Native Factory Recall Owns the Instrument, Not the Mixer

  • Apply one explicit shared-signal-path baseline before each native factory preset's engine-specific values. A named factory sound must not inherit hidden envelope, filter, drive, degradation, dynamics, accent, performance, velocity, randomization, or other audible shared state.
  • Clear every modulation route during factory recall. Kick Lab, Snare Lab, and Hat Lab must also reset Flux; otherwise the recalled parameter values can match while the sound still evolves according to the previous Lab state.
  • Preserve all Mixer-owned state, including gain, pan, mute, solo, output, EQ, channel processing, routing, and sends. Source activation must never unmute a channel or rewrite the routed Mixer strip.
  • Preserve dormant unrelated assets rather than deleting them as a side effect of selecting a native factory sound. Samples, bonus shots, and SoundFonts may remain loaded while engine/blend state selects synthesis.
  • Do not delete a Lunacy or Liftoff source merely to select a generated factory sound. Source participation is explicit state: Liftoff requires a User Source table, and Lunacy requires imported-source mode. Retained samples and bonus shots must remain dormant and must not multiply native synth voices.
  • Regression coverage must poison every engine-owned and shared audible parameter, all modulation routes, Lab Flux where applicable, the complete routed-Mixer inventory, and representative source assets before recall. Compare the resulting Instrument state with a clean recall while requiring exact Mixer preservation, choke/retrigger preservation for Drum Labs, retained source assets, and one intended voice per native/imported-source trigger.

Factory Audio And Voice-Pool Truth

  • A factory preset is not release-safe merely because its parameters are finite and its controls match. Every shipped preset must render sustained audible energy, remain finite and bounded without relying on the Master limiter, release naturally, and settle to silence.
  • Never prove release by forcibly clearing a voice before judging it. Force-stop is cleanup after a recorded failure, not evidence.
  • Noise-bearing factory tests require a deterministic seed so failures can be reproduced exactly.
  • Shared voice stealing must use a shared post-envelope magnitude. An engine-specific meter cannot rank other engines correctly.
  • Delayed future voices are the first retirement candidates. Scheduling an arp or strum must not immediately remove an audible note before the replacement begins.
  • Human listening still decides musical quality and loudness balance; automated thresholds only reject objectively unsafe or nonfunctional output.

Shared effects must not trade tails for idle CPU

  • An enabled shared return with no incoming send should sleep; enabling an effect is not evidence that audio needs processing.
  • The first real send wakes the effect immediately. Once awakened, preserve the complete tail until disable, Panic, audio reset, or project reset.
  • Do not infer tail completion from a short silent input or output window. Predelay, sparse diffusion, shimmer feedback, modulation, and future freeze modes can create valid delayed energy.
  • Any future automatic retirement requires explicit effect-owned internal-energy telemetry and must pass SHARED_EFFECTS_SIGNAL alongside repeated enabled-versus-disabled PERFORMANCE_SMOKE measurements.

Hidden pages must not keep repainting

  • Classify every editor heartbeat action as global or view-owned. Transport, MIDI input and recording, autosave, project loading, render progress, and audio safety remain global even when their page is hidden.
  • Metering, timeline geometry, clip animation, pad activity paint, and page-local host displays should run only while their owning page is visible. A hidden JUCE component can still accept state changes and repaint requests, so component existence alone is not a visibility contract.
  • On page entry, rebuild page-local visuals from authoritative processor state. Never make a hidden visual cache the owner of musical state.
  • Synth-editor maintenance may sleep only while the drawer is closed. Opening it must refresh the active engine, preset readout, control context, choke state, and used-tab state before the user can act.
  • Any heartbeat optimization must pass audio continuity and project-load feedback gates. Lower UI CPU is not a valid trade for stale transport, missed MIDI, lost recording, or silent recovery feedback.

Large editor grids must refresh on semantic change

  • Do not reread and restyle every cell in a large step, note, automation, or waveform grid at the editor timer rate. Cache the rendered snapshot and invalidate it when musical content or visible context changes.
  • Keep lightweight motion separate. Playheads, meters, pad activity, hover feedback, and transport pulses may update frequently without forcing the underlying grid to rebuild.
  • Opening a page while stopped is a context change and must force one fresh snapshot. Starting, crossing a musical step, stopping, switching pattern/page, or changing compact mode must also refresh immediately.
  • Accessibility titles, descriptions, and tooltips are semantic state. Rewrite them only when their displayed meaning changes.

User controls do not all belong at audio-event rate

  • Do not reload and smooth every Mixer or synth control for every sample merely because DSP consumes the resulting value. Group slowly moving controls into a small, bounded control slice.
  • Derive the slice coefficient from the established per-sample smoothing coefficient: 1 - pow(1 - coefficient, sliceSamples). Do not substitute an unrelated ramp that changes patch response.
  • Keep MIDI notes, note-offs, expression, transport edges, recording timestamps, and other musical events sample-accurate. A control-rate optimization must never move event scheduling onto the slower path.
  • Keep the maximum control interval below one millisecond at supported sample rates and verify fast automation by ear as well as with performance, continuity, and MIDI timing gates.

Expensive control transforms do not belong inside each voice sample

  • Cache deterministic parameter transforms such as dB-to-gain conversion, attack/release exponentials, filter coefficients, and nonlinear normalization at the established control rate. Recomputing them per voice per sample multiplies cost by polyphony without improving sound.
  • Keep signal-dependent operations at audio rate: peak detection, envelope following, gain reduction, waveshaping, and filter state updates still depend on the current sample.
  • Disabled processors must not pay their coefficient-building cost. Cache the enabled state with the derived values, then wake the processor on the next bounded control slice.
  • A cache is valid only when every parameter that derives it shares one explicit refresh path. Adding a new control without updating that path creates stale DSP.
  • Pair optimization proof with functional proof. For channel-strip compressor or saturation changes, run SHARED_EFFECTS_SIGNAL, repeated PERFORMANCE_SMOKE, AUDIO_SAFETY_CONTINUITY, and MIDI_RECORD_TIMING.

Mixer-owned processors must hear the summed channel

  • A channel-strip compressor or saturation stage must process the combined stereo channel after all routed voices, choke fades, and retirement tails have been summed. Processing it inside each voice changes dynamics, distortion, meters, sends, and rendered output according to polyphony.
  • Channel-strip EQ and parametric EQ follow the same ownership rule. They process one summed stereo Mixer channel, with independent left/right filter state, before channel compression and saturation.
  • Cache EQ coefficients at block or control rate, but advance filter state at audio rate. Rebuilding biquad coefficients per voice or per sample wastes CPU; sharing filter state between left and right corrupts the stereo image.
  • Keep voice-stealing evidence note-local and pre-strip. A shared compressor cannot provide a meaningful magnitude for deciding which individual voice to retire.
  • Route channel meters, main or hardware outputs, and shared-effect sends from the post-strip signal so every destination agrees with what the Mixer presents.
  • Silent strips may skip full routing work only after advancing any state that could otherwise wake stale. EQ tails, compressor release, and saturation tone memory must decay even when the current channel input is zero.
  • Preserve the channel order: EQ, compression, saturation, then meters, output routing, and shared-effect sends. A future order change must be explicit and regression-tested because it changes the sound of every Mixer strip.
  • Protect the boundary with a polyphonic test. The dry summed-level increase and compressed summed-level increase must differ enough to prove one shared detector is responding to the whole lane.
  • Protect both EQ families with a signal regression that proves their enabled output differs from bypass and remains finite.
  • Completed 2026-08-18: strip EQ and parametric EQ now own dedicated stereo state per Mixer channel; no channel-strip EQ state remains in Voice.
  • Human review: play dense stereo material through strip and parametric EQ, compare live meters/sends/rendering, and confirm left/right detail is preserved while controls are automated.

Arrangement step boundaries must not rescan every clip for every concern

  • Build one bounded, stack-owned view of clips active at the current Arrangement step, then reuse it for shared-pattern expression, clip-local expression, and note playback. Repeating a full 512-clip scan for each concern creates periodic audio-thread work spikes exactly where users hear timing most clearly.
  • Preserve the expression precedence explicitly: Shared Pattern first, Lane second, Clip Local last. An optimization that changes this order changes the music even when every event still arrives.
  • Reuse the immutable sequencer snapshot captured for the audio block. Reacquiring the same publication later in the callback adds atomic traffic and risks mixing generations within one rendered block.
  • Acquire each immutable pattern playback snapshot at most once per audio block. Multiple active clips can reference the same pattern, and expression plus note scheduling must consume the same published revision without repeating atomic shared_ptr loads at every step boundary.
  • Derive the current step length once per block and pass it into the sequencer. Tempo conversion is control state, not work to repeat inside adjacent scheduling stages.
  • Keep the active-clip cache fixed-size and allocation-free. Its maximum cardinality must remain bounded by maxArrangementClips.
  • Protect this boundary with AUDIO_SAFETY_CONTINUITY, MIDI_RECORD_TIMING, REALTIME_LOOP_SNAPSHOT, AUTOMATION_OWNERSHIP, ARRANGEMENT_MOVE_WORKFLOW, and repeated performance runs.
  • Completed 2026-08-18: Arrangement playback now filters active clips once per step boundary, reuses the block snapshot and step duration throughout scheduling, and caches each pattern snapshot on first use for the remainder of that audio block.
  • Human review: loop a dense transition repeatedly and listen specifically to the first beat of measures and section boundaries while clip, lane, and shared-pattern expression are active.

Arrangement lane routing facts belong in one block-owned context

  • Derive each lane's audibility, drum or tonal role, output MIDI channel, internal/external route flags, instrument slot, Mixer channel, and stable lane ID once before a block's first Arrangement scheduling boundary.
  • Reuse that fixed-size context for shared-pattern expression, lane-local expression, clip-local expression, drum playback, chord playback, and piano-note playback. Repeating route-policy queries for every active clip multiplies control work at the exact boundary where timing is most exposed.
  • Keep the cache stack-owned, bounded by maxArrangementLanes, allocation-free, and lazy. A block that never reaches an Arrangement step boundary must not pay to prepare playback contexts.
  • Validate clip lane indices before adding them to the active-clip view. Never repair an invalid lane by silently clamping it onto a different valid lane.
  • Any future Instrument Bay, per-lane automation, hardware routing, or MPE extension must add its derived playback fact to this one preparation path rather than creating another audio-thread lookup path.
  • Completed 2026-08-18: Arrangement scheduling now prepares one coherent lane playback context per needed block and reuses it across expression and note playback.

Timing tests must not hard-code the far side of a rounding tie

  • A test that accepts a recorded timestamp on both sides of an exact half-step boundary cannot always demand the same rounded result. Host scheduling jitter can move a synthetic callback a few thousandths of a step while production behavior remains correct.
  • Derive the expected quantized result from the timestamp actually captured, then separately test below-tie, above-tie, and exact-tie policy with deterministic values.
  • Keep the musical assertions intact: note identity, routing, clip/pattern length, integral quantized start, and tie-breaking policy must all remain proven.

Chord playback schedules must reflect how each chord produces events

  • Plain chords and strummed chords are scheduled on their source start step. Their delayed voices are emitted from that one scheduling call; do not expand them across the chord span.
  • Arpeggiated chords require scheduling on every source step touched by the chord span because individual arp events are selected inside triggerChordClip.
  • Chords with active pan motion also require span scheduling so their MIDI pan updates and terminal value are emitted at the intended steps.
  • Muted chords and silent-reference chords (playbackMode == 2) must never enter the playback schedule.
  • Build the index when publishing the immutable pattern snapshot. Do not rebuild or mutate it from the audio callback.
  • Both isolated pattern playback and Arrangement playback must consume the same prepared index so their behavior cannot diverge.
  • Preserve fractional starts and ends with the same floor/ceil convention used by playback. Off-by-one changes can omit a final arp or pan event.
  • Protect this boundary with SPACEAGE_PATTERN_CHORD_SCHEDULE_ONLY; the current fixture proves 8 relevant callbacks instead of 320 legacy callbacks.
  • Completed 2026-08-18: immutable pattern snapshots now publish chordIndicesByPlaybackStep, and both chord playback paths use it.

Meter graphics must report the pan result, not re-pan the audio

  • Meter work is display-only. Never alter channel audio to make the graphic look more separated.
  • The meter renderer uses a decibel height mapping. When the product requirement is a direct visual pan percentage, map the pan weights into meter-height space before converting them back to display gains; feeding raw 90/10 amplitudes makes the quieter side look much taller than 10%.
  • Preserve independent clip indication and silence decay for both channels.

Extracted chord lanes must preserve spatial and routing ownership

  • Insert the extracted lane immediately below its source lane. Never prepend it to the stack or place it below the drums.
  • Reserve Mixer channels already owned by tonal lanes and channels used by audible drum pads before selecting the destination. Never silently fall back to Mixer channel 1 when no channel is free.
  • Fail clearly before mutation if no truly unused Mixer channel exists.
  • Keep the destination instrument slot and pattern payload private so subsequent orchestration changes cannot alter the source lane.

Opening an instrument editor is an audio-state boundary

  • Clear active voices before changing the editor target, even when transport continues.
  • Refresh editor controls under the refresh guard so preset/state synchronization cannot fire user-edit callbacks.
  • Switching from one target to another must clear both the previous target and the destination target before attachment refresh.

Chord extraction must create private musical ownership

  • Never move chord data into a new lane by sharing the source pattern or instrument slot.
  • Keep the source melody by default; copying notes into the destination must be an explicit user choice.
  • Clone source and destination payloads separately, retarget destination notes/chords, and replace the source clip references atomically after lane insertion.
  • Validate free pattern capacity and a valid source instrument before checkpointing or mutating the project.

Embedded startup artwork has four synchronized identities

  • Keep the SampleSquadAssets CMake paths, Resources/ReleaseAssetClearance.json, docs/First_Party_Asset_Provenance.md, and the binary-resource lookup names synchronized.
  • The shipping startup set is Resources/startup_splash_01.png through Resources/startup_splash_45.png. Legacy splash.png, unpadded startup names, and source-design exports are not shipping substitutes.
  • Any content change requires a new SHA-256 in the clearance manifest; never copy a new image over an existing path and leave the old hash or evidence in place.
  • Inventory completeness is not legal clearance. tracked and hashLocked prove what ships; only an explicit rights attestation permits changing first-party artwork from blocked to cleared.
  • Run tools/test-release-asset-clearance.ps1 -AuditOnly after every artwork edit and the fail-closed release preflight before packaging.

Release paperwork must follow the embedded inventory

  • Resources/ReleaseAssetClearance.json is the authoritative list of embedded shipping assets and their exact hashes. Human signoff and action-plan documents must describe that current collection, not a remembered earlier splash set.
  • Release hygiene now rejects legal paperwork that does not cover all 45 numbered startup images plus the About artwork. Whenever CMake resource membership changes, update the manifest, provenance record, action plan, and legal signoff scope together.

Consolidated control-rate loops must preserve priming semantics

  • Related per-channel smoother updates may share one traversal, but their state remains independent.
  • Do not set any *SmoothingPrimed flag until every channel has received its exact initial target for that subsystem.
  • Preserve the existing update cadence and target values when consolidating loops; fewer traversals must not mean fewer smoothing updates.
  • Verify these changes with the full audio self-test because a priming regression can appear as a startup jump, stale send, or compressor/saturation transient rather than a compile failure.

Disabled-effect optimization must preserve live tails and independent processors

  • Skip effect-specific per-channel preparation when the effect is disabled, but preserve any explicitly supported tail-retirement path when it is enabled and still active.
  • EchoRay channel 1 retains its special enabled-path behavior even when its send is zero; other channels become active through nonzero sends or an existing delay tail.
  • Auto-pan is independent from EchoRay. Never gate or merge auto-pan behavior behind the delay enable state.
  • When folding active-path discovery into another traversal, preserve the original channel order and activation predicates exactly.
  • Verify with tools/run_tests.ps1 -NoBuild -Gate SHARED_EFFECTS_SIGNAL; the gate covers audible returns, wet-only behavior, control differentiation, stereo linking, silence, and finite output.

Arrangement recording guards must follow clip ownership

  • An armed Arrangement lane does not necessarily have an existing clip for the recording pattern. In that case, direct pattern recording remains valid and must not be discarded merely because Arrangement recording is active.
  • Reject an event that cannot map into a clip only when an explicit target clip was chosen, or when an existing non-gap clip on the armed lane owns that pattern.
  • Apply the same ownership decision to notes and recorded MIDI expression so the two event paths cannot diverge.
  • Protect this boundary with MIDI_RECORD_TIMING, MIDI_HEALTH, and MIDI_CLOSEOUT; the broad audio self-test must also retain the source-offset and first-measure recording cases.

Mixer UI tests must respect domain and message-loop lifetime

  • Controls under MIXER bind to instrumentMixerParameterId; controls under DRUM MIXER bind to pad parameterId. A fixture must assert against the domain it opens.
  • Do not rapidly construct and destroy multiple attachment-heavy editors in one console-test process while synthesizing synchronous button notifications. JUCE may queue attachment work, and this self-test configuration does not permit a modal message-loop drain between fixtures.
  • Prefer one editor lifetime per mixer-history fixture or a dedicated process-level gate. Keep functional confidence anchored by MIXER_LANE_ROUTING, MIXER_STATE_PERSISTENCE, MIXER_LAYOUT, and standalone/plugin smoke checks until the legacy monolithic Arranger fixture is split.

Single-cycle libraries must separate assets from oscillator state

  • A waveform table is immutable shared source data; oscillator selection, phase, tuning, modulation, and patch identity remain owned by one Instrument instance.
  • Never use one shared mutable current waveform value for multiple oscillators or lanes. Category changes must resolve and load that oscillator's first valid waveform rather than merely changing a label.
  • Never decode, scan, normalize, resample, detect cycles, generate mip levels, or touch the filesystem in the audio callback.
  • User-imported files require stable IDs, missing-file repair, archive policy, and explicit licensing treatment. Discoverability does not grant redistribution.
  • Patch loading may select an already prepared table but must not audition a note, rewrite another oscillator, or change Mixer-owned state.
  • Redshift custom cycles are stored as [instrument slot][oscillator] immutable publications. Never replace this with one shared current-cycle pointer. Project and .sspoly paths must remain independent for Oscillator 1 and Oscillator 2.
  • Update docs/Redshift_User_Manual.md, the instrument audit, control audit, performance impact register, and test matrix whenever this feature advances.