# src
## Purpose
All engine, terminal UI, and live-control code for the nooise binary.
## Ownership
- `main.rs` — binary entry point: CLI parsing (`run`/`version`/`update`/`render`/`auto`/song code), wires up terminal + audio engine.
- `update_check.rs` — passive crates.io update notification helper; checks in the background and exposes a short TUI-safe message.
- `audio.rs` — cpal/audio-backend plumbing, sample callback wiring. The output stream asks for `LIVE_BUFFER_FRAMES` (256) clamped into the device's advertised range and prints the size it got; left to the OS, CoreAudio hands over whatever buffer the device was last set to, and every played note waits that long.
- `midi.rs` — opt-in MIDI input and output (`nooise midi-ports`, `--midi PORT`, `--midi-in PORT`, `--midi-out PORT`): exact-name ports, independent 1–16 input/output channels defaulting to 1, and bounded nonblocking queues. The input callback admits Note On/Off and All Notes Off on the selected channel; the audio thread drains it without waiting and clears held input notes after overflow. A sender thread owns output; source-specific Note Offs preserve notes shared by Pad, Arp, and Lead. Shutdown sends All Notes Off on the selected output channel; system real-time messages carry Start, Stop, Continue, and 24 clocks per beat. Output overflow clears that channel so a dropped Note Off cannot leave a stuck note.
- `fluid/` — the core engine module:
- `mod.rs` — crate-facing glue: `run()` (TUI + live audio, with independently randomized built-in Pad progression and Tonal Phrase selections for a fresh session), `run_auto()` (TUI + live audio, slow-morph mode), `render_wav()` (headless wav render), `FluidTelemetry`. Any live start with a MIDI port, including a song-code, numbered-song, or auto start, opens with Pad Level 0%. Output takes startup priority: `--midi` and `--midi-out` start Pad Out On/In Off, while input-only `--midi-in` starts Pad In On/Out Off. The same preset applies to auto-morph endpoints so a morph does not raise Pad Level or change its MIDI direction behind the player's back. Explicit songs and auto endpoints keep their authored musical selections. Live entry points open MIDI before audio; a missing or duplicate exact port name fails startup. Headless default renders remain deterministic. Also the shared numeric helpers every submodule reaches through the `use super::*` prelude: `smoothstep` (the one ease behind ramps and LFO glides) and `splitmix64_mix` (the one finaliser behind RNG-free seeded values).
- `auto.rs` — `nooise auto [BARS]`: `AUTO_STATES` baked-in share codes decode to a `Vec<SongState>` at startup (fatal on a bad code); `MorphState` holds the decoded endpoints — each a full `SongState` (controls + automation) — plus bars-per-leg and derives leg index/from/to/t purely from the live beat clock (no stored progress, so it stays render-deterministic); `MorphWriter` throttles the engine's control- and automation-reload tick to one recompute-and-store of both per 1/8 note.
- `controls.rs` — `FluidControls`, `MasterControls`, and per-voice control structs with defaults.
- `registry.rs` — the control registry: one `ControlSpec` table per tab (stable ID, label, kind, range, step, entry semantics, reset, accessors, display). `tab_controls`/`apply_delta`/`apply_reset`/`apply_value` all derive from it. Rows matching a common archetype use the one-line macros `gain_pct!`/`time_secs!`/`time_ms!`/`beat_interval!`/`beat_offset!`; a row deviating from its archetype in any field stays a plain `ControlSpec`. Each tab's table is one `layer_controls!` invocation listing only that layer's own base rows — the macro appends the shared tails (`chord_slot_rows!` for Pads, then `module_slot_rows!` for all eight slots), so no table hand-expands a slot row. Voice-type labels come from one `&[&str]` table per voice (`PAD_TYPES`/`BASS_TYPES`/`KICK_TYPES`/`TONAL_SYNTH_TYPES`) read through the shared `type_label`/`wrapped_index` (the one float-to-table-index wrap; Bass rhythm, Tonal phrase, and Arp pattern wrap the same way into their own tables), and each table-indexed control's `max` is `last_index_of` its table so range and labels cannot drift apart. Per-tab metadata (name, mute-target level id, control table) lives in the single `TAB_META` const, in discriminant order (test-enforced).
- `automation/` owns bounded automation stacks keyed by stable control ID. `lfo.rs` owns shapes, fields, rate pickup, and the inline `Steps` staircase, whose step values are unipolar (0..100%: a step only lifts the control from its base). The song-code wire keeps its bipolar u16 step encoding, so a code carrying a negative step is refused with `SongCodeError::NegativeLfoStep` naming the control, never loaded with the step floored; a built-in state that used negative steps is re-authored by lowering the base to a stored rung and rescaling depth and steps so the modulated values match. `envelope.rs` owns triggers and envelope fields. `mod.rs` owns shared addresses, lane storage, position-space summing, morphing, and the audio-side de-clicker. Each control can carry up to four LFO lanes and four envelope lanes. Lanes sum before one clamp/snap/de-click pass. Song code persists stacked lanes by repeating the control address in the existing container-v2 route records.
- `song.rs` — binary song-code export/import for controls, automation, mute state, and runtime-session records; owns the container version and every value encoding. `Ctrl+S` copies only the raw `n1_…` code and shows a short confirmation; `nooise <code>` or `cargo run -- <code>` applies the decoded song state before audio/TUI startup.
- `range_epoch.rs` — the dial-range epoch table (`RANGE_CHANGES`, append-only), and `stale_modulation`, the decode-time refusal check.
- `song_ids.rs` — `SONG_ID_TABLE`, the append-only control-id ↔ `u16` index mapping song codes intern against, plus `song_id_index`/`song_id_at`. Never reorder, remove, or reuse an index; append only. Deliberately not derived from `all_specs()` so registry order stays free to change.
- `osc.rs` — opt-in OSC feed (bare `--osc` targets `DEFAULT_OSC_TARGET` 127.0.0.1:9000, foorm's default; `--osc=ADDR` overrides). `OscEmitter` reads `FluidTelemetry` on its own thread and preserves the legacy beat, level, chord, kick, and gesture addresses. It adds `/nooise/chord/change` (`slot`, root pitch class) and `/nooise/hit/<source>` (`level`, pitch class) for Beat, Pad, Perc, Kick, Tonal, Clap, and Arp. `MusicalHit::ALL` and `HIT_NAMES` are stable in-order contracts; C is 0, B is 11/12, and unpitched hits use -1. Writers store a payload before incrementing its Release counter, so the emitter's Acquire load sees the pair; multiple hits inside one 5 ms poll repeat the latest payload. The audio thread never waits on UDP, and an absent consumer is never an error. The ignored `song_level_profile` test prints a per-voice RMS table for a built-in song (`NOOISE_SONG=12 cargo test --release song_level_profile -- --ignored --nocapture`).
- `palette.rs` — `/` control palette: fuzzy-find over every registry control id, Tab-autocomplete + inline value entry, staged batch edits committed immediately (Enter on empty prompt) or on the next bar downbeat (Ctrl+B).
- `mix_action.rs` — static global mix-action catalog, separate from knob recipes. Kick Only (aliases `kick only`, `solo kick`) and Mute Kick (aliases `mute kick`, `kick mute`) are offered globally and in scoped palettes. Confirm publishes one mute snapshot immediately: Kick Only unmutes Kick and Master and mutes every other voice; Mute Kick sets only Kick muted, preserving every other mute including Master. Repeating applies the same state; there is no restore toggle. Levels, automation, MIDI switches, auto, transport, navigation, and MRU stay unchanged; an open automation editor resumes after the palette closes. Tab completes the action name without numeric entry.
- `recipe.rs` owns the static `RECIPES` table and its ordinary LFO templates. Sway is an 8-beat sine; Tremolo is a half-beat sine; Pulse is a one-beat square; Drift is a 16-beat RandomDrift with a fixed seed; Rise is an 8-beat RampUp. All start at 25% depth. Sidechain (alias `sc`) adds a one-beat RampUp and lowers the base by 25% of the dial (floored at zero), yielding a 50%-throw duck toward the original value before the existing smoothed wrap. It follows transport beats independently of Kick hits, offset, or mute. Repeating lowers the current base again and stacks a lane. Depth is dial-position distance; ordinary base and lane controls persist, with no recipe identity.
- `capture.rs` — retrospective manual-knob history and transport-aligned sixteen-beat capture loops; static `/capture`, `/bypass`, `/resume`, `/delete` palette actions. Keeps four loops, one per gain, continuous, or timing control, including Interval and Offset. The palette freezes its target and history endpoint on entry, including the module topology guard; confirm queues playback on the next bar. `/capture` takes the completed sixteen-beat block immediately before the current block, leaving the following sixteen beats to keep it. The window includes its start and excludes its end. Each curve has 128 u8 modulation-position samples (one per eighth of a beat), interpolated between samples and across the wrap. `capture_ratio` follows the shared taper, or modulation's linear fallback for beat grids. Active timing captures snap through the control's editing grid, preserving intervals such as 0.75 and 1.25 rather than forcing the LFO power-of-two ladder; audio and UI use `CaptureClip::playback_spec`. Before the first edit, history holds the prior base value. History retains sixteen recently edited controls with at most 4,096 events each over two blocks (32 beats). Notices distinguish an unfinished block (beats remaining), no edits in the completed block, an unsupported knob, and overflowed history. The history buffer is live-only. Capture payload version 3 stores sixteen-beat loops; the sixteen-bar payload is refused rather than retimed.
- `interaction.rs` — pure semantic interaction model: cohesive navigation, exclusive keyboard-owning modes, input-phase policy, deterministic transitions, and ordered effect values.
- `runtime.rs` — Crossterm-only terminal adapter and deterministic fair scheduler: capability negotiation, transport normalization, typed semantic/deferred mapping, bounded input admission, tick cadence, and frame deadlines. Its test-only `recording` module is the single writer of the replay fixture grammar (`SanitizedTraceRecorder`, key/phase tokens) and owns the transport test scaffolding shared with `replay.rs`: `FakeClock`, `TerminalCapabilities::full`, `TransportEvent::key`.
- `replay.rs` — test-only production-path replay harness: sanitized raw-event fixtures (parsed here, written via `runtime::recording`), the scripted input adapter, shipped raw performance phase fixtures, exact binding postconditions, the shared production coordinator/tick seam, real scheduler/normalizer/kernel/effect/view/render traversal, and model-based invariant tests.
- `effect.rs` — ordered interaction-effect execution: aggregate control/automation/mute edits, auto exit, unit state, MRU/selection consequences, staged bar commits, clipboard, and typed acknowledgements/failures that keep interaction ownership synchronized. Every bridge runs its effect list through the one `run_ordered` helper, and the arms that mutate automation go through `with_automation`, which loads the frame's automation once and acknowledges the generation the edit published. Effect execution calls *down* into `edit.rs` for those mutations; it must never call into `ui.rs`.
- `session.rs` — immutable `LiveSessionSnapshot` (controls, automation, tonal state, mute state, transport) plus its single CAS-retry `ArcSwap` transaction boundary.
- `view.rs` — cohesive immutable `UiViewModel` projection from interaction ownership, one live-session generation, telemetry, and presentation-only state; owns typed help/notice precedence and the minimum terminal contract.
- `ui.rs` — pure Ratatui rendering from `UiViewModel` and the frame area, and nothing else: it must not mutate session state, execute effects, or poll input. `render` derives one `PanelFrame` per frame (open modulator surface, numeric entry, `ModContext`, live chord slot, bar width) and composes the panel from `draw_scrim`/`draw_tabs`/`draw_control_rows`/`draw_activity`/`draw_footer`/`draw_palette`/`draw_help`, so every section reads the same frame. The footer is two stacked rows: `draw_activity` renders `UiViewModel::activity` (dim idle key hints, or a bold live readout when `activity_live`) above `draw_footer`'s stable exits/mode-help/notice line, so gesture text never replaces the other footer content. `draw_help` is the static keyboard-shortcut overlay (`ModeSurface::Help`): it covers the tab/control area but stops above the activity and footer rows, so those stay visible under it — the same pattern the palette uses to keep its own exits in view. Owns the control/modulator row widgets, the shared `lane_line` both modulator lanes render through, slider markers, and the flipped display-unit (`FlippedUnits`) formatting.
- `coordinator.rs` — the shared production turn coordinator (`coordinate_production_turn`/`_event`/`_action`/`_tick`) plus the live `production_ui_loop`. `coordinate_production_action` is the only path from a semantic action to the kernel and effect executor; replay tests that inject an action directly call it against a `production_frame`, never a private copy. Scheduler-due ticks commit before that turn's events, and the live scheduler loop and the replay harness both cross this same ordering seam.
- `edit.rs` — every control and automation mutation a UI gesture performs: adjust/reset/set, unit flips, route removal and reseeding, and opening a modulator editor. `active_field` resolves the cursor to exactly one target, and `with_active_field` applies one `FieldOp` (`Adjust`/`Reset`/`Set`) to it, so the three verbs cannot drift apart; Delay's time rows step through the registry like any other (`contextual` supplies the loaded clock's grid); only the clock row's Sync/Free arrow flip is Delay-specific (`apply_delay_row`), and a Filter Type edit replays through `switch_filter_type` so the cutoff follows a Low/High flip. `effect.rs` calls into this module, never the reverse.
- `visualizer.rs` — the ripple visualizer: `RippleField` (telemetry-driven animation state — field time, live kick ripples, chord hue), the `FluidWidget` that draws it, chord hues, and the `fluid_hsv`/`darken` colour helpers. Presentation only; it never reaches the audio engine.
- `module.rs` — per-layer module slots: the `ModuleKind` catalog, `Domain`/`Family`, and `ModuleSlot`/`LayerModules` state. A slot stores *which* module is loaded as a value; the catalog never appears in a control id. Filter is a post-synthesis module with amount, cutoff, resonance, and type; Perc, Bass, Kick, and Clap ship with factory Filter slots instead of bespoke filter controls.
- `widget.rs` — `Dial`/`DialScale`, the shared slider vocabulary every bar renders through. Owns all value-to-bar-position mapping and tapered position stepping; no other module derives a ratio.
- `engine.rs` — `FluidEngine` (voice mixer), gain smoothers, tempo clock and its `Transport`, grid triggers, shared per-layer/master effect bank, master bus, and the Master-bus gesture stage. `engine/gesture_audio.rs` owns its bounded gesture processors; see `fluid/engine/AGENTS.md`.
- `gesture.rs` — normal-browsing gesture vocabulary and one fixed scalar envelope per gesture, all over Master. Amounts are evaluated from monotonic audio seconds, independently of tempo, key repeat, or UI cadence.
- `gesture_level_probe.rs` — real-playback-level Master headroom renders: a clamp-hit regression bound, plus an ignored table of every song with each gesture held at full throw.
- `voice/` — one module per voice (pad, bass, perc, kick, tonal, clap, arp, lead) plus shared helpers (`midi_to_hz`, `tune_ratio`, `note_hz`, `soft_clip`, `normalized_lfo`, `mix_and_retain`/`mix_and_retain_mono`) in `voice/mod.rs`.
- `fx/` — shared DSP building blocks (LFO, panner, reverb) consumed by voices. See `fx/AGENTS.md`.
- `synth/` — shared synthesis primitives (envelope, oscillator, noise, multi-operator FM) consumed by voices. See `synth/AGENTS.md`.
## Local Contracts
- `automation/lifecycle.rs` owns typed `/bypass`, `/resume`, and `/delete` actions on the open LFO/envelope lane. From an automation editor, the palette freezes that lane's control, family, index, module topology, and live-only `automation_revision`; any authored lane-stack change invalidates the target, including delete/re-add, while editor changes, LFO pickups, captures, and unrelated controls do not advance that revision. A stale action publishes nothing and asks the player to reopen `/`. Bypass/resume return to the same editor; delete removes the selected lane and returns to browsing. In browsing these verbs still address the selected knob's captured loop; `/capture` keeps its sixteen-beat behavior in every context.
- LFO and envelope `enabled` defaults true and is independent of amount, editing, randomizing, and lifetime. Bypassed lanes retain their settings and count toward the four-lane family limit. Shared audio/UI summing excludes them; resume joins the current transport phase immediately through the existing de-clicker, and envelope Once does not retrigger. The UI shows a flat bypassed lane and a compact `bypassed · /resume` owner footer at 46x11; automation-owner action failures remain visible. Morphing selects enabled with the structural endpoint. Song record 9 stores only nonzero per-control disabled-lane masks (u16 control ID, four LFO bits, four envelope bits), applied after all lane records decode. Missing records mean enabled; duplicate records/targets, empty/trailing data, out-of-range masks, missing lanes, unknown IDs, and inactive module targets are refused, while retired IDs retain their specific refusal. The live revision never persists.
- Kept captures live in `AutomationState` and replace the authored base in position space before LFO/envelope summing and the existing de-clicker. Direct knob edits, including clamped edits, suspend that knob's clip in the same publication; editing an automation field leaves it running. Bypass returns to the authored base and preserves loop phase; resume admits that continuing phase on the next bar. Capture again replaces the selected knob's clip. Slot replacement clears that slot's clips. The selected-row footer and row badge report queued/loop/bypassed. Song record 8 stores the bounded samples, enabled state, phase, and remaining admission delay rebased to beat zero; the audio beat clock and explicit interaction beat supply the export anchor. Decoder rejects malformed curves, repeated targets/records, retired controls, unsupported target kinds, and stale dial ranges. Morphing snaps capture sets with the structural endpoint choice. Four full curves plus the existing stateful module families remain below 2,000 code characters.
- UI interaction work follows `docs/adr/0001-unidirectional-interaction-architecture.md`: raw terminal input normalizes to semantic intents, one exclusive interaction mode owns the keyboard, pure deterministic updates emit ordered effects, and rendering consumes one immutable view model. Coherently coupled controls, automation, and user-audible runtime state publish as one aggregate `ArcSwap` snapshot through one transaction boundary; audio reads stay lock-free. The runtime must bound input work and complete visible frames within 50 ms of application-controlled time during continuous input.
- `fluid/interaction.rs` is the terminal-independent interaction kernel. `InteractionMode` is the only keyboard owner; mode-local data stays inside its enum variant. `PaletteMode` owns its query, selection, recents, locked value entry, and staged edits so no parallel palette overlay state can exist; selection wraps over the projected registry matches, invalid locked entry indexes are safely discarded, and confirm emits typed jump/commit payloads rather than requiring adapter-side reconstruction. `InteractionMode::Performance` is the Jump leader: Space owns the one-shot interaction and repeated activation is idempotent; a layer key (`INSTRUMENTS`) opens that layer's page and a parameter key (`PARAMETERS`) resolves an address and hands the keyboard back to Browsing. A parameter key with no layer key first aims at the page already open, which is why `jump_effect` takes a `Tab` rather than a `PerformanceInstrument`: the shorthand reaches every layer, including Lead, the one page no selector key names. The selector keys read left to right across the tab strip (`asdf` then `qwer`, `r` on Master), so their positions mirror the pages on screen. It moves the cursor and never edits a value, so it carries no capability branch, no hold state, and no completion stage — `JumpStage` is `ChooseLayer` or `ChooseParameter`, nothing more. A second layer key re-aims a pending jump. `jump_effect` is the only place a layer/parameter pair becomes an address: Volume is the layer's own `Tab::level_id` row as `InteractionEffect::JumpToControl`, Filter is `FILTER_MODULE_ID` in `MODULE_CATALOG` as `InteractionEffect::PlaceModule`, which the adapter resolves to add-or-jump because only it can see whether the chain already holds one. Normal browsing owns temporary `z/c/v/b` gestures without entering a new mode; their input latches track physical holds and quarantine keys across mode transitions. Shift+P is `ToggleTransport` (plain `p` is unassigned), owned by Browsing and Automation like mute. `Intent::phase_policy` decides whether Press, Repeat, or Release is accepted before a transition runs. Every `Intent` also declares its owners in `Intent::handled_by`; both matches are exhaustive over `Intent`, so a new variant must decide its phase and its modes, and each mode's transition function guards on that ownership instead of restating what it ignores. `InteractionModel::update` dispatches one function per `ModeKind`, which report their next keyboard owner rather than assigning it. `LAYERS` is the single layer table: page order, page↔tab translation, and the navigation each page opens all index it in `Page`/`Tab` discriminant order (test-enforced). `INSTRUMENTS` is the single performance-instrument table (selector key and page, in `PerformanceInstrument` discriminant order, test-enforced) and `PARAMETERS` the single jump-parameter table (key and footer word, in `PerformanceParameter` discriminant order, test-enforced); the runtime key map and the footer read both rather than restating the layers or the parameters. `InteractionMode::Lead` is the Lead play keyboard: `i` from any page opens it (`EnterLeadPlay` lands navigation on the Lead page first, so the rows it edits are in view), as does Enter on the Lead page (any row that is not a module drill or the Steps row — the coordinator upgrades `TouchSelected` there, after the module check; on `lead.steps` it becomes `EnterLeadPattern` instead), inside the pattern drill included. Arrow Up/Down moves its visible control selection and Arrow Left/Right emits the ordinary `AdjustSelected` effect, so a player can edit the selected knob without yielding Lead ownership; `LEAD_PLAY_KEYS` (`a`–`l`) remain tones 1–9 as `PlayLeadTone`. `LEAD_NUDGES` is the single nudge table (`z`/`x` octave, `q`/`w` level, `e`/`r` decay, `t`/`y` glide, left key down and right key up, one dial step per press as `NudgeLead` → `InteractionEffect::LeadNudge`), which the runtime key map, the footer, and the executor all read rather than restating the pairs; Space is `ToggleLeadPattern` (the lane Off/Play, without leaving the keys), `c` is `CaptureLeadPhrase` (keep what was just played as the lane), every press is an edge so autorepeat plays nothing, and Esc returns to browsing (releasing any held key). Holding follows the shared capability rule: where the terminal reports releases (`TerminalCapabilities::supports_holds`) a press is `PlayLeadTone { hold: true }` and the key-up is `ReleaseLeadTone`, so the note sustains under the finger; only the key that owns the sounding note releases it. A reduced terminal (no releases, autorepeat arriving as presses; plexi panes today) gets `hold: false` attack/decay taps and the LEAD footer says so, since the kernel cannot tell a repeat from a re-press there. `InteractionMode::Help` is the static keyboard-shortcut map: `Intent::OpenHelp` (Browsing-only) opens it on Shift+/, matched against whichever of the two reports a terminal sends for that chord — the shifted glyph `?` alone (no SHIFT modifier; terminals don't synthesize one for punctuation the way crossterm does for letters via `char::is_uppercase`), or the base key `/` with an explicit SHIFT modifier (the keyboard-enhancement protocol's report-base-key-plus-modifier style) — so a plain unshifted `/` still reaches `OpenPalette`. It owns nothing but Esc-to-close and the global save/quit chords since its content never changes per frame, and `ModeSurface::Help`/`KeyboardOwner::Help` carry no data to project. Ctrl+S/Ctrl+Q/Ctrl+C are global for every owner except the palette (its own control chords) and numeric entry (swallows every chord), so no mode traps the user. The runtime and the view both read `LEAD_PLAY_KEYS` and `LEAD_NUDGES` rather than restating the rows. `InteractionModel::update` remains deterministic and returns ordered data-only effects; adapters execute those effects elsewhere.
- `fluid/runtime.rs` is the only production adapter allowed to expose Crossterm transport details. It requests event-type and all-key keyboard enhancement together, normalizes with explicit capabilities, exposes press-only fallback through `TerminalCapabilities`, and restores pushed flags, alternate screen, and raw mode in reverse order on every exit path. Without `key_event_types` a Repeat normalizes to Press and a Release normalizes to nothing at all: `normalize_key_event` returns `Option<TransportEvent>` and `EventSource::read` may yield `None`, which the scheduler charges against the turn's read budget without queueing. That is load-bearing on Windows, where the console reports key-up for every key while crossterm never reports enhancement support, so a coerced release fired every binding twice. Its canonical input mapper returns `Action`, `Ignored`, or a typed `Deferred` reason. Jump-leader bindings map raw identity onto typed intents at every phase and leave which phases act to `Intent::phase_policy`, since the leader only ever moves a cursor: it reads the same on a press-only terminal as on one reporting releases, and needs no `TerminalCapabilities` at all. The layer and parameter key sets are disjoint and neither changes meaning between stages, so the binding reads neither the stage nor the mode. Global Ctrl bindings test containment so Shift and other modifier bits do not disable them. Quit requires Ctrl+Q, with Ctrl+C retained as an alias; bare `q` is inert. Palette text accepts shifted printable characters while Ctrl P/N/B keep their navigation and bar-commit meanings. `Scheduler` owns tick and frame deadlines, bounds each input turn by read count and elapsed time, backpressures reliable events at its fixed queue capacity, and run-length encodes only adjacent identical Repeat events. A requested or due frame preempts queued input.
- `fluid/session.rs` is the only publication boundary for user-audible live state. `LiveSessionSnapshot` includes gesture envelope anchors alongside controls, automation, tonal state, mutes, the transport, and live Lead presses. All writers use `LiveSession::transact`; the audio callback reads one aggregate without a mutex. `LiveSession::audio_seconds` is an audio-owned atomic clock used to evaluate gesture anchors, never a second copy of their state. Lead key presses and the transport remain live-only; active gesture envelopes persist compactly through `song.rs`.
- `fluid/effect.rs` owns interaction effect execution and adapter-side consequences. It executes each transition's owned effect list in order, stops at the first failure, supports an injected clipboard for deterministic replay, and returns an explicit acknowledgement or failure. It updates aggregate controls/automation, auto mode, mute/unit state, MRU state, staged commits, clipboard, selection, and messages only at their defined effect boundary. `PlaceModule` is the one add-or-jump seam: `place_module` reuses the chain's existing module when it holds one and otherwise loads it into the first free slot, always inert, so the Jump leader and the palette reach a module the same way and neither can stack a second copy. Nested LFO open/close effects acknowledge the resulting depth and exact parent row; the production coordinator applies that position to the interaction model after publication. `JumpToControl` and the palette's immediate-commit effects carry their exact typed target/edit payload and receive explicit selection/publication acknowledgements. Save failures use the `Save failed: ...` notice prefix. New kernel effects must be handled or rejected explicitly.
- Browsing gestures play over the Master bus from any page and preserve arrows/Tab. Press/Repeat/Release ownership uses the production mapper; release still works after a modifier change. Opening a keyboard-owning mode, Escape with held gestures, reported focus loss, and shutdown release the gesture overlay. Unsupported terminals show the key-release requirement and leave gesture keys inactive. Effects publish envelope anchors through the aggregate session without exiting auto. Releasing returns toward the current song, never an old controls snapshot. Bloom/Echo feed shared processors through sends; Bloom's wash ramps out with its 50 ms release, while Echo's tail survives release; fixed storage avoids allocation when a gesture starts. Song codes tag gestures by `GestureKind::wire_tag`, never the storage discriminant, so a retired gesture's tag is never reused: tag 3 (Thin) is refused as `RetiredControl("thin gesture")`, and a voice target as `RetiredControl("per-layer gesture")`.
- Deliberate edits go through `EffectExecutor::edit_session`, which exits auto before publication so the morph cannot overwrite them on its next tick. `InteractionEffect::LeadTone` also bypasses it — a played note is a gesture over the song, not an edit of it, so soloing never stops an auto morph — but it also lands in the executor's `LeadPhraseBuffer` (live-only, bounded, never in a song code) so `LeadCapture` can keep it afterwards: `lead_capture` turns the phrase into `lead.steps`/`step1..16` and sets `lead.pattern` to Play through `edit_session`, so keeping a phrase exits auto like any edit, and with nothing played it is an explicit `NoChange` with a notice. `LeadOctave` and `LeadPattern` are ordinary `lead.octave`/`lead.pattern` edits and do exit auto. Mute bypasses this helper and toggles the aggregate session overlay, so auto and automation keep driving the authored Level value.
- `EffectExecutor::toggle_transport` owns Shift+P. Stop holds the beat and releases sustained Pad chords while tails, FX, gestures, and Lead play keys keep running. Play restarts at beat zero: all voice grids, chord followers, Tonal phrase position, and Arp cycle rewind; Pad voices the first chord in the current window. A live-only `transport_restart` counter preserves stop/play edges even between audio reads. Kept capture curves restart at sample zero, retaining their enabled/bypassed state; pending LFO pickups and live capture/Lead histories clear, staged edits move to the first new bar, and auto restarts at its first endpoint. Runtime evolution contents and RNGs stay intact. `FluidEngine::restart_sequence` preserves ringing audio and FX buffers. `LiveSessionSnapshot::from_song` always starts `Playing`; song codes carry no transport or restart counter.
- Live MIDI clock follows the same `TempoClock::tick` beat and transport. `FluidEngine::with_midi` attaches `MidiClockFollower`, Pad, Arp, and Lead output only to interactive engines; offline renders never emit MIDI. `pad.midi_out` defaults On and `arp.midi_out`/`lead.midi_out` default Off; all three sources share the chosen output channel. Registry setters make each track's In and Out mutually exclusive; enabling either side turns the other Off. Effective controls also give In priority if modulation or morphing crosses both switches. MIDI switches are hidden by default, but a CLI MIDI output launch shows Pad Out On, and input-only shows Pad In On. Pads Trigger, Swing, and Gate are hidden by default on all launches; `/` adds them individually without changing their values. Selecting another MIDI control from `/` adds it to the bottom of its Pads, Arp, or Lead root page without changing its value; an Arp/Lead MIDI Gate row appears with MIDI Out. `FluidControls::midi_rows` and `hidden_pad_rhythm_rows` persist row visibility in compact song-code records, omitted when the defaults are unchanged. Pad Out Off releases its chord immediately. Arp and Lead step notes play at audio Level 0 and use their own `midi_gate_beats` controls (0.125..2 beats, default 0.5); a held Lead play key stays on until release. Audio continues regardless of MIDI switches. On startup MIDI sends Start and any enabled opening notes; on stop it releases all three sources then sends Stop; on play after stop it sends Start and resets its 24-PPQN pulse index. Note Ons use fixed velocity 100 independent of audio levels. Master Tune transposes outgoing notes by semitones. Port and channel selection are runtime CLI choices; track switches and gates persist in song codes.
- `FluidEngine::with_midi_input` attaches a shared bounded input source only to live engines. `pad.midi_in` defaults Off in stored controls and starts On only for an input-only CLI launch; Arp and Lead In default Off. Input Note Off reaches a track even after its In switch is turned off; turning a switch off releases all of that track's held input notes. A connected input with Pad In On makes Hold-mode Pad audio follow incoming pitches instead of sounding the progression chord; the progression still advances. In Stabs, `pad.midi_trigger=On` (the first Trigger drill row) replaces the 16-step hit source with input Note Ons that fire the current chord for `pad.gate_beats`; notes arriving within 10 ms coalesce into one stab so a keyboard chord does not triple-trigger it. The steps stay visible but dim and inert. Arp cycles held input notes instead of Pad chord tones while any are held. Lead input plays received pitches through its voice. Input-held notes are live gestures, not saved song state; the In switches and Pad MIDI Trigger are saved.
- Individual mute toggles use `EffectExecutor::toggle_mute`; global mix actions publish their whole mute mask through the same session boundary. The engine applies a 30 ms click-free gate after each layer's full module chain and after the Master bus, so Level automation cannot raise a muted output. Mute state lives in `LiveSessionSnapshot`, renders from that same generation, and persists as a compact song-code record.
- MIDI output respects both source-track and Master mute through effective routing only. A mute releases the source's owned Pad/Arp/Lead notes, suppresses new notes while muted, and leaves saved Out switches and MIDI clock unchanged. Unmuting revoices Hold Pads immediately and lets Arp/Lead resume on their next trigger or played key. Source releases never send channel-wide All Notes Off; shared pitches remain until their final owner releases them.
- A run of homogeneous rows (the Pads' chord slots, the Lead's steps) never sits inline on its page. It lives behind a page-local drill (`ChordDrill`, `LeadDrill`): Enter on the row that sizes it (`pad.progression` on Custom, `lead.steps`) opens the run, Esc returns to that row, the registry owns the projection (`chords_tab_controls`, `lead_tab_controls`) and its inverse (`chords_drill_for_index`, `lead_drill_for_index`) so a palette jump lands inside, and `tab_controls` stays the root page. A new voice with a lane follows this shape rather than adding rows to its page.
- `fluid/view.rs` is the only boundary that combines interaction state, a coherent `LiveSessionSnapshot`, telemetry, and presentation-only state. It derives the active `KeyboardOwner`, typed `ModeSurface`/`HelpSurface`, navigation rows, and render data before drawing. Automation resolves exactly once into an `AutomationSurface`; mode/session kind mismatches and nested LFO keys that do not identify an eligible field on the active address become an explicit unavailable surface, and render/help must not infer another editor from session state. Numeric and palette render state lives only in its owning mode surface. A mode opened from inside another editor projects that editor alongside its own state rather than replacing it — `ModeSurface::Numeric` carries the `AutomationSurface` it resumes to, so typing into a drilled-down field renders the buffer on that field instead of collapsing the editor to its parent row. `PerformanceSurface` projects only how far a pending Jump has got; the leader renders as a footer line and never covers the control rows, so the page it is aiming at stays visible underneath it. `ui::render` accepts only this immutable projection and a Ratatui frame; it must not poll input, mutate state, execute effects, or recreate footer precedence. `UiViewModel::activity` is the gesture-activity row's text — the idle key-hint list, or a live readout when `activity_live` is true: `■ STOPPED` leads it whenever the transport is stopped, in every owner, followed by any held/returning gestures — derived independently of `help` so a held gesture never displaces the footer's own precedence chain. The control list scrolls through `row_scroll`: a selected row on the first screen never scrolls, and past the fold it is shown with two lines beneath it, so a page that outgrows a short frame stays reachable without the top of the page jumping. The minimum supported frame is 46x11 (the two-row footer's `activity`/`help` split costs one control-row line versus the former 46x10) and every top-level owner and nested owner depth requires a full-buffer snapshot at that size.
- Add a replay regression in `fluid/replay.rs` for every interaction bug: use only sanitized key identity/phase/modifiers/repeat count, relative clock advance, resize, focus, shutdown, explicit tick/idle, and redacted event records; keep secrets, semantic intents, and pasted or mouse text out of persisted fixtures; include the smallest event sequence that reproduces the failure. Media and modifier keys use closed typed identities rather than debug strings. Performance-mode regressions must enter through raw recorded key phases and traverse the production mapper; no semantic test adapter may bypass it. Every normalized `PhysicalKey` identity and all six modifier bits must round-trip without loss; fixtures with modifier bits outside that domain are invalid. Tick processing and idle turn delimiters remain explicit replay boundaries. Each trace must traverse the real normalizer, scheduler, kernel, effect executor, view projection, and Ratatui `TestBackend`. Property failures use structured replay outcomes, retain partial action/frame history, and preserve the same exact `PropertyViolation` value (derived equality, no separate key type) during delta reduction: edge failures carry the full action/before/after/effects record, while nondeterminism carries the first divergent history, frame, or result field and both values. Diagnostics report the concrete divergent action/before/after/effects record. Build new fixtures with the test-only `runtime::SanitizedTraceRecorder`, then delta-reduce them before committing.
- Touching UI edits use `EffectExecutor::edit_session`: stop auto first, recompute controls and automation from one aggregate snapshot inside the CAS retry, publish once, then acknowledge MRU state. Standalone auto-off publishes one no-op session fence; auto-morph writes verify the exact morph `Arc` they computed from so an off/on cycle cannot admit a stale generation.
- The `ControlSpec` tables in `fluid/registry.rs` are the single source of truth for every control row and stable song snapshot ID. Adding a control = adding one table entry with a durable ID; never reintroduce per-function match arms for control rows. The `control_registry_specs_are_internally_consistent` test enforces table sanity.
- `song.rs`'s snapshot codec is fully generic over `all_specs()`: any state expressible as `ControlSpec` rows persists and round-trips automatically, and old codes missing a newer id simply decode that field to its default — no `song.rs` change needed when adding controls this way. Only reach for a new versioned payload/section in `song.rs` for state that cannot be expressed as a flat control value (e.g. automation routes). Adding a control does require appending its id to `song_ids.rs`'s `SONG_ID_TABLE`; `song_ids_cover_every_registry_control` fails the build until you do.
- A song code has two version axes, and Capture alone versions its playback-period payload; other record payloads carry no version byte of their own. The container version dates the byte layout. The dial-range epoch (`RANGE_EPOCH_RECORD`, `u16`; absent means 0) dates what a stored modulation depth means: depths are fractions of a dial's throw, so a range change moves them even though values stay in their own units. Every encode writes `CURRENT_RANGE_EPOCH`. Decoding refuses a code from an older epoch whose automation targets a dial `RANGE_CHANGES` lists as changed since, with `SongCodeError::StaleRange`, the way a retired control is refused; every other old code loads untouched, and no load reinterprets a depth. A range change appends one `RANGE_CHANGES` entry, bumps `CURRENT_RANGE_EPOCH`, and re-authors `AUTO_STATES` so each lane on the moved dial keeps its swept Hz: its new depth is the half-span, on the new dial, of the values it reached on the old one. `built_in_cutoff_sweeps_cover_the_hz_they_did_before_the_dial_widened` pins the result for the cutoff widening. Container version 2 is the only one that exists — all v1 plumbing is deleted and a v1 code is rejected outright with `UnsupportedVersion`, never silently decoded as empty state. Control ids are interned as `SONG_ID_TABLE` indexes and continuous values stored as a u16 taper position, so those round-trip within one u16 step rather than exactly; discrete rows and the `PowerOfTwo`/`BeatGrid` step ladders stay exact, as do all beat-valued automation fields, `LfoRoute::seed`, and every tonal-sequence field. A table-indexed discrete row whose dial order is not its saved identity declares `ControlSpec::song_values`; the codec writes the table's value for the dial position and maps it back on read (`song_values_cover_every_dial_position`). Unknown *record types* stay skippable on purpose — that is how a code from a newer build still loads everything else — while an unknown *container version* is a hard error.
- Round-trip fidelity is asserted semantically by `assert_song_states_agree`, never by byte equality: the reader clamps and substitutes defaults on the way in. Compare controls through `all_specs()`/`spec.quantized_value`, not by walking `FluidControls`; compare automation through the accessors, never `AutomationState`'s derived `PartialEq`, which also covers `open`/`open_field`/`LfoRoute::pickup` — live editor state that deliberately does not persist.
- A control whose default sits off its own step grid cannot store its own quantized value: the writer prunes it as equal to the default and it reloads one grid step away. Pre-existing, not introduced by the codec; fix the defaults, not the prune.
- Control rows carry `ControlKind`; use it as the source of truth for gain/continuous/timing/discrete semantics.
- The `/` palette derives everything from the registry: `palette_entries` builds one entry per unique control id at its owning tab (`tab_owning_control`), while its empty query reserves its first ten rows for the ten UI-local MRU controls globally across every page, then lists unused current-page controls and registry order. A query that is still just a layer's name or id namespace (`bas`, `bass`, `pad`, `pads`) pins that layer's `Tab::level_id` control above the fuzzy score and the MRU alike, so typing a layer name always reaches its level; the boost releases itself once the query outgrows the namespace. It must outrank score, not tie-break it — score alone puts `pad.stereo_width` above `pad.level` for `pads`. Slider interaction includes value edits, palette jumps, mute, drill, unit, and automation actions; cursor-only Up/Down navigation does not count. The MRU has no counters and is not song state. Staged edits apply through each spec's own `apply_value` entry semantics in a single clone-modify-store pass, and jumps restore Pads/Master drill state via `chords_drill_for_index`/`master_flat_index` inverses (roundtrip test-enforced). Palette value entry is always in the control's native unit — it does not consult `FlippedUnits`. Bare-digit numeric entry on the main view is untouched; the palette owns the keyboard only while open, and `/` must stay additive per the North Star 15-second floor.
- **`palette_entries` must stay a pure function of the registry and static catalogs.** The interaction kernel and the renderer each build the entry list independently and confirm resolves by index, so anything that made entries, haystacks, or match ordering depend on live controls could desync the two and jump to the wrong control. Live state is read only where it cannot affect indices: the rendered value column (`PaletteEntry::value`), and the adapter. Module-slot rows are excluded as controls; a module is reached through its own `PaletteEntry::Module` row, one per (layer with a chain, catalog entry). Confirming one emits `InteractionEffect::PlaceModule`, and `EffectExecutor::place_module` decides add-vs-jump: a layer already holding that module gets a jump, never a second copy; a free slot gets an inert module and the cursor lands on it; a full chain returns a message naming the count. `Recipe` rows come from `RECIPES` and follow the lane recipe contract below.
- Master tempo accepts 30–200 BPM; keep the registry and `MASTER_BPM_MIN` aligned when retuning the slow-morph floor.
- A song save is a session snapshot: persist every user-audible generator state that does not regenerate on its own — sequence positions, evolution counters, seeds. Controls alone are insufficient when a feature advances internal state that would otherwise restart. The bound is size: a song code is meant to be pasted into a chat message, so raw audio buffers never go in one. FX tails (delay lines, reverb combs, compressor envelopes) rebuild within a second of playback and are excluded — `song_code_stays_short_with_every_stateful_module_filled` fails the build if a code exceeds 2,000 characters with every stateful module family loaded. State that does qualify goes on a lock-free audio-to-UI snapshot path as its own song-code record; unknown records decode as skipped, so an older code simply loads without it.
- `fluid/widget.rs` owns the shared slider vocabulary. Every bar in the app is a `Dial` (value + `DialScale` + display string), and `DialScale` (`Tapered`/`BeatGrid`/`Rungs`) is the only place a bar ratio is derived. A surface must never hand-roll ratio arithmetic: build the `Dial` and let its scale decide. `DialScale::step_in_position` gives a tapered field even presses end to end and returns `None` for scales with no inverse (`BeatGrid`, `Rungs`), which own their own stepping. `LfoField`, `EnvField`, `StepTarget`, and registry `ControlItem`s all expose a scale plus a `field_value`, so display and stepping cannot disagree. `DialScale::max_value` is the single source of any field's ceiling (`Rungs` tops out at its last rung), so a retuned range moves the bar and the `Shift+L` gesture together.
- `Shift+H`/`Shift+Left` reset a control (or open modulator field) to its `reset` target, which is the floor for most but not for controls with a `reset_at` override; `Shift+L`/`Shift+Right` are the mirror and take it to `max` (`FieldOp::Max` in `edit.rs`, dispatched through the same `with_active_field` routing as reset — a modulator row can never fall through to the control underneath it). Each field family writes its ceiling straight to storage the way its own reset arm does (`LfoRoute::max_field_at`/`max_step`, `EnvelopeRoute::max_field`, `ControlSpec::apply_max`), never through a setter that would re-apply entry semantics to a value already in storage units.
- Module slots (`fluid/module.rs`, design in `docs/proposals/2026-07-30-module-slot-addressing.md`): all seven voice layers plus Master carry `MODULE_SLOTS` anonymous slots addressed by stable storage IDs. **A module's identity is a stored value, never part of a control id** — ids are permanent `SONG_ID_TABLE` entries, so putting the catalog in the id space would cost a fresh block per module forever. `MODULE_CATALOG` is append-only because saved slots store its indexes. Domain (pre/post) comes from the loaded module, not the slot index. Only modules with complete behavior for a layer may appear in its add palette; retained catalog indexes without DSP stay unavailable. Each loaded module collapses to one row, named by `ModuleKind::collapsed_field` — Amount for every family except Filter, whose most useful single knob is Cutoff; a module with a detail drill (`ModuleKind::has_detail`) labels that row `Name ›`, Enter is the only way into its reusable `Navigation::Module` detail, `/` exposes the same dotted sub-context, and Esc restores the parent. Adding a module from the palette lands the cursor on its row and never opens the detail. A detail lists the collapsed field first (test-enforced). Pads' effects remain siblings of musical controls and never enter chord drills. Delay owns Amount/Left Time/Right Time/Feedback/Vintage; `T` independently converts each time between Sync and Free, and a time change crossfades to the new tap position over a fixed window rather than sliding the read head, so tempo-synced retargeting never pitch-bends. Reverb owns Amount/Size/Damping. Compression owns Amount/Threshold/Ratio/Release/Makeup. Filter owns Cutoff/Resonance/Type/Amount; its Amount (wet/dry mix) is detail-only and always starts fully wet — `preset_slot` overrides any caller-supplied amount for "filter" — because a palette-added filter starts audible via a maxed-out (transparent) Cutoff instead of via the usual muted Amount. Cutoff spans `FILTER_CUTOFF_MIN_HZ..FILTER_CUTOFF_MAX_HZ` (20..20000 Hz, Log2) and is stored exactly in song codes; the factory Perc/Bass/Kick Filters stay at 8 kHz, since codes that never touched them carry no cutoff. A direct row edit of Type (arrow, typed value, reset) goes through `switch_filter_type`: Low-pass ↔ High-pass mirrors the cutoff (`min * max / hz`) so a transparent filter stays transparent; Band-pass keeps it. Palette value entry, randomize, and automation set Type without the mirror. Drive and Swing are amount-only. One slot-addressed post-DSP bank executes Drive/Reverb/Delay/Compression/Filter in chain order on every voice layer and Master. Slot FX state is runtime-only: a loaded song starts every delay line and reverb silent and rebuilds the tail from playback. A processor whose slot stops asking for its family — emptied, or swapped for another module — is never dropped outright; it moves to `ModuleFxBank::retiring`, keeps running on the live input while its contribution crossfades back to dry over `LEVEL_RAMP_MS`, and only then is released. It fades using `last_loaded`, the slot's fields from the last frame it was loaded, since the live slot has already been cleared by then and its zeroed Amount would silence the tail being faded. Dropping instead of retiring cuts a live tail to zero in one sample and leaves the processor's buffers frozen rather than decayed, so refilling the slot replays the previous take (`removing_a_module_fades_its_tail_instead_of_cutting_it`, `re_adding_a_module_does_not_resurrect_the_previous_tail`, `replacing_a_module_fades_the_outgoing_one`). Drive and Swing hold no tail and so have nothing to retire. Serializing those buffers put one Delay slot past a million characters in a song code.
- Retired effect controls (`pad.reverb_mix`, `tonal.reverb_mix`, `arp.reverb_mix`, `clap.room`, `perc.swing`, `tonal.swing`, `arp.swing`, `bass.drive`, `kick.drive`, `kick.filter`, `clap.filter`, and all bespoke Master Drive/Compression ids) keep their append-only `SONG_ID_TABLE` slots but are gone from the registry, and nothing translates them. A code naming any table id that no live spec claims is refused with `SongCodeError::RetiredControl`; an index *past* the table's end is a control from a newer build and stays skipped, which is the only reason the two cases are distinguished (`reject_retired_control`, `song.rs`). Retiring a control therefore invalidates every code that set it — re-author `AUTO_STATES` by decoding on the last build that understood the id and re-encoding on the new one, carrying its value, the chain slot it belonged in, and any LFO routed at it (`auto.rs`'s `AUTO_STATES` doc comment works the conversion). The default template preloads Pads Reverb at 80%, Tonal Reverb at 10%, Arp Reverb at 0%, Bass/Kick/Master Drive, and Master Compression; Clap starts with a factory Filter and Swing remains optional. `resolve_module_chain` derives only pre-trigger Swing fields. Post effects read slot state directly.
- Every slider must use its full visual throw. Continuous controls map value to bar position through `Taper` (`Linear`, `Log2`, or `Exp(n)`). Irregular `Step::BeatGrid`, `Step::PowerOfTwo`, and LFO-rate ladders divide the bar by reachable arrow rungs instead of raw numeric magnitude; exact values between rungs interpolate within that segment. The base value and every modulation marker must use the same mapping. `ControlItem` carries real value/min/max plus step and taper; never bake position transforms into stored values. A continuous tapered dial with a plain `Step::Linear` steps in position space and stores full precision.
- Every time control reads through the shared `secs(seconds)` formatter (whole ms below 1 s, 2-dp seconds above) — ms-stored controls pass `ms/1000.0` — and uses `Taper::Exp(TIME_TAPER)` so envelope/decay dials read and step consistently regardless of whether the field is stored in seconds or milliseconds. Retune all time-dial feel from the single `TIME_TAPER` constant. Beat-grid/power-of-two musical controls keep their musical stepping and are left off the exp taper.
- Every continuous automation field, including LFO, envelope, and inline step targets, derives its range, arrow stepping, numeric entry, reset target, and dial scale from a `FieldSpec` table row, never from a per-field match arm. `Stepping` names how one press moves it (linear, snapped to the field's grid, along an explicit rung ladder, along the musical beat grid, or an equal fraction of a tapered throw) and `Entry` is the registry's own numeric-entry vocabulary. A random roll lands through `FieldSpec::value_at_ratio` (position space, or an equal share per rung) and writes the stored value directly, never through the numeric-entry setters, whose percent parse would divide it by 100. Only the discrete fields (LFO shape, envelope trigger) stay outside the tables: they are enums cycled by index, and a roll gives each entry an equal share (`index_at_ratio`). Display strings stay on `LfoRoute`/`EnvelopeRoute`, not in the spec. Arrow navigation clamps inside the open submenu; Escape closes it.
- LFO `rate` spans 0.125–64 beats; fresh lanes start at 1 beat, and saved rates restore unchanged. Arrow steps use quarter-beat rungs through 4, then 8/12/16/32/64; each rung gets an equal share of the bar and numeric entry remains exact within the range. A live rate edit never rewrites offset: the old and new globally anchored waveforms hand off at their next crossing (`LfoPickup`), after which the new rate follows the shared transport grid. Equal rates with zero offset share phase across every control.
- `FluidEngine` applies a two-second startup mix fade; keep `startup_fade_reaches_full_gain_in_two_seconds` and the seeded golden-render checksum aligned with intentional changes to that window.
- Auto morph treats drum levels musically: when a target has no drums, Perc/Kick/Clap cut together on the transition downbeat instead of morphing out; Kick also starts on that downbeat, while Perc and Clap may fade in independently.
- Per-slider `f` (LFO) and `e` (envelope) open or cycle that lane family. `Shift+F`/`Shift+E` adds and opens a lane, `x` removes only the open lane, and Escape closes the editor. `r` while browsing is `RandomizeSelected`: it sets the selected control to a random point on its own dial through `ControlSpec::apply_ratio` (position space for tapered rows, an equal share per rung for the `BeatGrid`/`PowerOfTwo` ladders, rounded onto the table for discrete rows), so a time dial is as likely to land short as long; inside an automation editor the same key is `RandomizeAutomationRow`: `FieldOp::Randomize` rolls exactly the row under the cursor (one LFO/envelope field, one Steps target, or the parent control on row 0), and on a random shape's Shape row it rerolls the seed and keeps the shape (footer `r reseed`, else `r random`). `Shift+R` is `RandomizeScope`: in browsing it uses the coordinator's pre-action visible-row projection, so it randomizes exactly the current page or drill (including a module detail) in one session transaction; in an automation editor it randomizes every field of the open route, including the LFO Steps configuration and random seed, except that with the LFO cursor on a Steps staircase row (`edit::lfo_steps_selected`: count, glide, or a value) it rolls only the live step values (`LfoRoute::randomize_step_values`) and the footer reads `Shift+R randomize steps`. The roll comes from `EffectExecutor`'s one `StdRng` — entropy-seeded in production, `REPLAY_RNG_SEED` in replay — so a replayed trace rolls the same values twice. Do not add voice-specific LFO/envelope controls to core slider tabs.
- New lanes start at zero amount and persist until explicitly removed, including untouched lanes. Closing an editor or navigating away never deletes a lane. Song codes retain zero-amount LFOs and envelopes in stack order; amount zero is silent, not a deletion marker. Each family is capped at four lanes per control, including silent lanes; hitting the cap returns a visible interaction failure, and `x` frees the selected lane's slot.
Recipes deliberately start at their table's nonzero amount. `/` offers the same static recipe rows globally and inside module details; aliases affect matching while the display stays clean. Tab completes a recipe name without opening value entry. Confirm adds one ordinary lane to the knob captured when the palette opened, including timing and discrete knobs, leaves navigation in place, exits auto, and names the edit in a notice. Repeating adds another lane; existing silent lanes count toward the cap. A failed preflight leaves auto and the session unchanged. The fallible transaction rechecks capacity before publication. Recipe identity is never saved: the lanes use the existing song codec and editors. Recipe application closes an open automation editor and returns to browsing.
Slot-targeted recipes capture `LiveSessionSnapshot::module_topology_revision`. `LiveSession::transact` advances this live-only revision whenever any slot kind changes, so delete/re-add of the same kind invalidates a pending recipe. Parameter edits leave it unchanged. This conservative guard also refuses a pending module recipe after an unrelated slot changes; reopening `/` captures the current structure. Non-module targets keep their stable control id. The Jump leader grammar is unchanged.
- `modulated_control_value_full` sums every active lane in dial-position space, then clamps and snaps once. UI markers show this semantic target. `AutomationPlan` applies the same sum through one 3 ms de-clicker after composition, preserving continuity across envelope retriggers, route edits, and lane removal without storing that transient in song codes.
- Automation colour identifies the modulator family: LFO lanes are pink and envelope lanes are green. Envelope polarity changes direction, never colour. Envelope previews use the same signed, centred height mapping as LFO previews: a negative zero-attack envelope starts below neutral and rises back to it.
- Grid-timing controls carry `LfoSnap` in their `ControlSpec` (intervals snap modulation to power-of-two subdivisions, offsets to their step grid). `GridTrigger` may pull a scheduled hit earlier when a reshaped grid lands sooner (so a denser grid never starves), but never within half an interval of the hit already emitted (`earliest_hit`, floored on `last_hit_beat`); a reshape moves any slot by at most half an interval, so this guard stops a live swing/offset/rate change from re-firing the slot that just sounded. Grids that move later latch at the next fire.
- Chord length (`pad.chord_bars`) stores bars for the pad/bass engines but displays and accepts numeric entry in beats; typed values convert to bars and snap to the existing power-of-two grid.
- Live-read gain controls are ramped by registry-derived `GainSmoothers` in `FluidEngine`; every unique `ControlKind::Gain` spec must get a smoother automatically.
- Perc applies a fixed `0.5 / 3.0` source trim to both hits and continuous noise so its useful Level range sits above the first dial step. Built-in morph states compensate their Perc Level and Level-LFO depths by three through the current song encoder; no endpoint clips, and their authored balance stays intact.
- A layer's Level scales its engine's summed output every sample; it is never captured into a voice at trigger time. Capturing it means the fader cannot reach notes already sounding, which on Tonal/Arp (up to 6 s of decay) left a layer audible long after it was pulled to zero while Mute cut instantly (`tonal_level_ducks_notes_that_are_already_sounding`). Level scales the *output* of any tone-shaping filter the engine owns, never its input: `TonalLowCut` is a high pass, and feeding it an abruptly silenced signal discharges its state as a decaying thump. Tonal and Arp still skip creating a voice at a Level of exactly 0, which keeps a silent layer from accumulating inaudible voices without affecting the fader's reach.
- Every voice reads `master.tune` at the moment it builds a note, so a tune change lands on the next trigger. `PadEngine::new` is the one constructor that must be handed the live tune rather than assuming neutral: it voices the opening chord, which then holds for a whole `chord_bars` with its release bleeding into the next, so a song code carrying a non-zero tune would otherwise open at concert pitch against a transposed Bass/Tonal/Arp (`pad_voices_its_opening_chord_at_the_master_tune`).
- TUI automation edits must go through `EffectExecutor`, which publishes through the aggregate live-session transaction on every mutation.
- While auto morph runs, each immutable UI frame reads automation from the current aggregate session snapshot, so LFO markers and song saves match what the engine hears; the first touching edit exits auto from that current snapshot.
- Pitched voices (Pad, Bass, Tonal, Arp, Lead) route note numbers through `voice::note_hz` (`midi_to_hz` under master tune); unpitched voices (Perc, Kick, Clap) do not.
- Tonal separates trigger density from phrase shape: `tonal.rate_beats` controls note trigger spacing; `tonal.step_interval_beats` is the stable cycle-length ID for phrase wrapping and evolution boundaries. `tonal.offset_beats` moves the cycle-length window forward through the phrase without phase-shifting the trigger grid, so Cycle 2 + Offset 1 plays phrase beats 2–3.
- Tonal synth selection lives in `tonal.synth_type`; it changes the voice created for new tonal notes while preserving the shared phrase/randomness/timing/master-tune/reverb path. Exploration variants may differ in harmonic tables, spectral tilt, and pitch-scaled harmonic decay; keep piano-family profiles warm and non-metallic unless the user explicitly asks for FM-like brightness.
- `tonal.octave` (`TonalControls::octave`, default 0) shifts the note selected from `TONAL_SCALE_MIDI`/the evolved phrase by whole octaves (`± round(octave) * 12` semitones) right before `note_hz` in `TonalEngine::next` (`voice/tonal.rs`) — it never touches `TONAL_SCALE_MIDI` itself, so the minor-pentatonic interval set is preserved at every octave. Arp reuses Tonal's piano synthesis but derives its own pitches from pad chords in `arp.rs`, so `tonal.octave` affects only the Tonal voice.
- Every tonal/arp note is a single attack+decay shape via the shared `attack_decay_gain` helper (Sine and all piano-family profiles): the gain ramps in over `attack` seconds, then falls from the peak to silence over `decay` seconds, so a note's whole sounding life is `attack + decay` and the envelope reaches exactly 0 there (no click at voice death). The step grid (`tonal.rate_beats`/`arp.rate_beats`) only spaces triggers — it never bounds a note's length — so notes overlap freely when `decay` outlasts the step (both engines accumulate polyphonic voices). `decay` is floored at `TONAL_DECAY_MIN` (10 ms) so the UI can't request a hard, clicking cut; only the decay curve's shape exponent (`PianoProfile::body_power`, or the sine voice's fixed sqrt taper) stays profile-owned character. Clap and perc already embody this decay-to-zero model (their inline `decay_ms` fades) and are the reference the tonal/arp envelope was unified to. Pad and Bass are deliberately separate (long sustained chords; mono voice-stealing) and keep their own envelopes.
- Tonal owns a slight fixed low cut before engine mixing so its low notes sit above sub/bass energy without requiring a user-facing control.
- Pad, Tonal, Clap, and Arp emit dry voice output. Any Reverb is owned exclusively by that layer's shared module chain; there is no voice-local or ambient-send reverb path.
- Arp follows the Pad's current chord without reaching into `PadEngine` directly: it holds a `ProgressionFollower` (pad.rs; Bass and Lead hold the same one) whose `ProgressionCursor` keeps the Pad's phrase, reads chord tones via `pad_chord_tones`, and cycles them (Up/Down/Up-Down/Random, 1–3 octave span) on its own `arp.rate_beats` grid, phase-shifted by `arp.offset_beats` (default 0, matching the Level/Interval/Offset idiom shared with Perc/Bass/Kick/Tonal/Clap) — only `note_trigger` reads the offset; the chord clock stays the Pad's, unshifted. Its synth character lives in `arp.type` (`ArpControls::voice_type`), reusing Tonal's `TonalVoice`/`TONAL_SYNTH_TYPES`/`piano_profile` path (same Sine + 9 piano-profile set and labels as `tonal.synth_type`); default `6.0` ("Pluck") matches the arp's former hardcoded profile byte-for-byte. `arp.gain` defaults to 0 (silent) so adding the voice never changes an existing song or default startup. When the chord or octave span changes mid-cycle, the cycle position is clamped into the new tone list rather than reset, avoiding a click.
- Lead (`voice/lead.rs`, `LeadControls`, `Tab::Lead` between Arp and Master) is the one hands-on melodic voice: a mono additive voice whose character is `lead.type` (`LEAD_TYPES`: Saw/Square/Soft/Pure/Reed, each a `LeadRecipe` of eight signed harmonic gains plus a trim that `lead_types_render_at_a_matched_level` holds within 10%; read per sample, so a Type edit reshapes the sounding note), with a one-pole pitch glide (`lead.glide`, `glide_coefficient`) and an attack/decay life per note, retriggered in place so a new note slides from the last without a click. It plays a step lane: `lead.steps` (1–16 live steps) and `lead.step1`…`step16`, each a rest or one of `LEAD_TONE_COUNT` (9) tones, under one transport row, `lead.pattern` (`LEAD_PATTERNS`: Off/Play, default Play). Off is the solo position — the lane is silent, played keys still sound, and the trigger keeps ticking so Play resumes on the clock's step. It is a discrete control like any other, so a Steps LFO can gate the pattern in bars. There is no armed record state: a phrase enters the lane by capture after it was played (`lead_capture`: the presses since the last bar of silence, within the last 16 steps, each snapped to its nearest step, on the shortest of 4/8/16 steps that holds them, placed at `absolute step mod length` so the loop plays back where it was played), the way Ableton's Capture MIDI keeps a take nobody armed. A tone is an index into the Lead's reach (`LeadReach`), which `lead.follow` picks: Chord is the Pad's current four voiced tones through the shared `ProgressionFollower`/`pad_chord_tones` path, so keys move as chords move; Scale is `progression_scale`, every pitch class the chord window's chords touch laid out from the progression's first chord's lowest note (whatever the Offset), derived rather than declared so a custom progression and each built-in table yield their own and a chord change never moves a key. Tones past the reach wrap an octave (a four-note reach reads `1 2 3 4 1' 2' 3' 4' 1''`, `lead_tone_label`), then `lead.octave` lifts the note. The steps live in the page-local pattern drill (`LeadDrill::Pattern`, `lead_tab_controls`), never on the root page; only the live steps show there, and `DEFAULT_LEAD_STEPS` rests past the default length so lengthening the lane adds silence. The active lane row carries the same amber `♪` follower marker as the chord progression; an LFO in its `Steps` shape marks its active step too. Both markers derive from their transport clocks, so neither adds persisted state. A retrigger on a sounding voice resumes the attack from the current level (`Adsr::start_at`) with the phase untouched, so fast playing is click-free; the default glide (30 ms) is a slide between held notes, not lag under a line. The step a hit plays is derived from the transport (`lead_step_at`, like the LFO staircase), so there is no lane position to persist. Its "slightly distorted" character is a factory Drive slot at 0.1, not a bespoke control. Trims are set so full Level sits below the Bass (`lead_at_full_level_sits_under_the_bass`): a bright driven lead reads louder than it measures, and the fader's useful range has to be the whole bar. `lead.level` defaults to 0 and its audio triggers nothing at that level, while MIDI Out can still send the lane and played keys. Play mode (`LeadEngine::observe`/`with_play_state`) sounds a pressed tone on the next sample, sliding from whatever the lane is playing; `LeadPlayState::held` sustains it (`LeadShape { hold }` sets the ADSR sustain to 1 and the release to `decay`), the flag dropping releases it, and the lane yields while a key is held so the player owns the voice; the footer names a 0 level so a silent keyboard is not mistaken for a dead one.
- The song-code mute record is indexed by `Tab::mute_bit`, a per-tab bit assigned once in the order tabs were added (Master is 7, Lead is 8), never by discriminant, and is `MUTE_BYTES` wide. Adding a tab mid-strip therefore cannot move a saved mute onto another tab; a shorter payload from an older build simply carries no bit for the newer tabs.
- Built-in progressions are `voice::PROGRESSIONS`, one `Progression` each: eight `Chord`s (symbol plus voicing; the symbol is the chord's only name, and `built_in_chord_names_match_their_voicings` holds it to its notes), the authored Bass line, a mood word, and a permanent `song_value`. `progression_label` pairs the key (`progression_key`, derived from the first chord's symbol) with the mood ("Am · Drift"). Dial order is free; the song value is not. `pad.progression` saves through `ControlSpec::song_values(&PROGRESSION_SONG_VALUES)`: A–H keep 0–7, Custom keeps 8 forever, and a new progression takes the next unused value, so appending one never reinterprets a code. A value this build lacks is refused (`SongCodeError::UnknownValue`), never clamped. Adding progressions still lengthens the dial a sweep crosses, so each addition is a `RANGE_CHANGES` entry for `pad.progression` (epoch 2 covers the 9 → 15 growth); no built-in song modulates it (`no_built_in_song_modulates_the_progression`). Progressions from song value 9 on hold an exact common tone across the full loop and within each half's own loop (`voice_led_progressions_hold_a_tone_through_every_window`); E–H hold one across the full loop; A–D predate the rule.
- One dial step past the last built-in (`voice::CUSTOM_PROGRESSION_INDEX`) selects a user-built progression instead: 8 chord slots (`PadControls::chord_slots`, `ControlSpec` rows `pad.chordN_degree/_accidental/_quality/_extension/_inversion`, visible directly on the Pads tab) each define a tonic-relative root degree, semitone accidental, quality (modal-interchange third override: -1 forces minor, +1 forces major, 0 keeps the diatonic third; the row's display resolves the inherit position to `scale (maj)`/`scale (min)` via `pad_chord_slot_is_minor`), extension (triad/6th/7th/9th-flavor top voice), and inversion; `pad.chord_count` (1–8) and `pad.chord_offset` (0–7) pick the **chord window** of every progression, built-in or custom: `ChordWindow` plays `count` table slots starting at `offset`, wrapping modulo 8 (Count 4, Offset 4 plays chords 5–8). `ProgressionCursor` is the one phrase position Pad, Bass, Arp, and Lead each hold (the pad directly, the others through `ProgressionFollower`); it owns the chord clock (first chord boundary at the first `chord_bars` grid line after start, each next boundary counted from the last one) and reads the pad controls only at a boundary. `voice::pad_chord_tones`/`bass_root_note` resolve a *table slot*, so every voice agrees on the chord without reaching into another's state. Chord Length, Count, and Offset edits wait for the sounding chord to end. If the current slot remains in the new window, its new successor plays next; otherwise the old next slot plays if still present, or the new window starts at `offset`. Progression selection always restarts at `offset`, since slot numbers then name different chords. The old length times the pending boundary; the new length times later ones. Automation that returns to the current controls before a boundary does not alter the phrase, and a morph that changes progression restarts once (`a_morph_restarts_the_phrase_once`). A pending change needs no song-code state: it is already in the saved controls, and a load starts a fresh phrase from them at beat 0. No built-in song modulates any of the four. Telemetry's `chord_slot` is that table slot. `pad_chord_name` names any slot, built-in or Custom (`custom_chord_name` reads the slot's own fields). The Pads root page shows the window's chord names in the spacer under Progression, the sounding one in amber; the Custom Root list shows all eight slots in table order (so a slot can be written before the window reaches it), each Root row naming its chord, with slots outside the window dimmed and the sounding one badged. The custom slot data is inert unless progression selects it.
- Bass character lives in `bass.type` (`BassControls::voice_type`): index 0 (`Sub`) is the legacy voice and the default — its DSP path must stay byte-identical to a pre-`bass.type` render. Index 1 (`Saw`) is a brighter additive-harmonic character, index 2 (`Pluck`) a shorter character with an attack transient; all three share the same trigger/rhythm, pitch (`midi_to_hz` + master tune), and drive/panner tail, and are gain-authored to a comparable perceived level.
- Bass is monophonic (`BassEngine::voice: Option<BassVoice>` in `bass.rs`): each rhythm-grid hit hard-cuts whatever is currently sounding and starts the new note immediately, regardless of `decay_time`. The replaced voice isn't dropped instantly (that clicks) — it rings down over a fixed short (`BASS_MONO_FADE_SECONDS`, ~3ms) `fading_voice` slot, independent of the voice's own envelope. This applies identically to all three `bass.type` characters. No user-facing control; this is not a voice pool.
- Post-synthesis filtering runs through the shared `ModuleFxBank` at a stable layer/slot identity. The Filter module owns its left/right state and accepts only its persisted amount, cutoff, resonance, and type fields. A zero Amount is an exact dry bypass. Shared Filters replace the retired `perc.filter`, `bass.cutoff`, `kick.filter`, and `clap.filter` controls and are preloaded into slot 1 on all four layers. Clap's is fully wet at `CLAP_FACTORY_FILTER_CUTOFF_HZ` (3170 Hz), the cutoff of a fit to the retired one-pole's rendered default; the voice itself now emits unfiltered noise. Bass and Kick Drive occupy slot 2. Kick keeps fixed voice-local filters as part of each character; the shared module owns the user-facing sweep.
- Pad character lives in `pad.type` (`PadControls::voice_type`): one `PadTone` runs the shared `PadOscStack` into the shared soft-clipper, and a character is exactly one `PadStage` between them plus one output trim after. Index 0 (`Warm`) is the legacy tone and default; `PadStage::None` with a 1.0 trim keeps its original render byte-identical (`pad_type_zero_keeps_the_legacy_warm_signal_path`). `Dark` adds a lowpass, `Glass` a two-octave shimmer, `Choir` a moving upper partial, `Hollow` a sub-octave tone, and `Tape` a lowpass with slow level drift. A Type edit swaps the stage in place on already-sounding tones without restarting the envelopes. Only phrase controls (chord length, count, progression, offset) wait for the chord boundary. In Hold mode, Pad keeps its authored attack/release. `pad.trigger=Stabs` uses a fixed 16-step sixteenth-note Hit/Rest lane by default, `pad.swing` through `GridTrigger::pop_swung`, and a short audio attack/release. `pad.gate_beats` (0.125..2 beats, default 0.5) sets both audio and MIDI hold time; a new hit releases/retriggers the previous chord early, so consecutive hits never stack on the external synth. MIDI Note Off starts the synth's own release envelope. Stabs keep the same chord source. Enter on Trigger opens the page-local `ChordDrill::Pattern`; its 16 step controls and MIDI Trigger source row stay off the Pads root page and persist in song codes. Switching back to Hold revoices the current chord. All six characters share chord selection, pans, and the dry-output/engine-owned-reverb contract, and are gain-authored to comparable levels.
- `pad.chord_notes` is the Pad output voicing count (2–5, default 4), distinct from `pad.type`'s character and `pad.chord_count`'s progression window. It changes Pad audio and MIDI together on the current chord: 2 = root/fifth, 3 = triad, 4 = original voicing, 5 = original voicing plus high root. `pad_voicing` is their shared selection path; Bass, Arp, and Lead keep following the underlying four chord tones. The four-note default keeps the established render unchanged. A count edit in Hold revoices immediately; Stabs take the new count on the next hit. Song codes persist the count.
- Kick character lives in `kick.type` (`KickControls::voice_type`, `KickVoice` enum over `LowpassKickVoice`/`WoodKickVoice`): index 0 (`Sub`) is the legacy voice and the default — its DSP path must stay byte-identical to a pre-`kick.type` render. Index 1 (`Warm`) is a round FM body (shallow depth at a hollow 1.5x modulator ratio); index 2 (`Wood`) sends the post-drive signal through a heavily-damped hand-rolled bandpass (Chamberlin SVF, centered in 110-400Hz at the fixed character position) and blends it back against the dry signal so low-end weight survives; index 3 (`Felt`) swaps the sine carrier for a naive triangle and biases its character filter darker than Sub. All four share the same trigger/scheduling path in `KickEngine::next`, the click/drive/amp-envelope/soft-attack/pan machinery (`KickVoiceCore`), and the pitch-glide/FM carrier body (`KickFmBody`, which returns a carrier phase each variant shapes itself); Sub/Warm/Felt are one `LowpassKickVoice` driven by a `LowpassKickRecipe` const (`KICK_SUB`/`KICK_WARM`/`KICK_FELT`: attack, click scale, pitch drop, FM ratio/depth, carrier wave, `KickLowPass` bias, output trim; Sub's trim is exactly 1.0). A new lowpass type is a recipe const and a dispatch arm — never its own voice struct; only a different signal path (Wood's bandpass) earns one. `KickFmBody` owns only the pitch glide; the modulator/carrier math is one `synth::fm::FmStack` pair, so a kick type is a recipe (`mod_ratio`, index, index decay, `FmWave`) rather than bespoke oscillator code.
- All four kick types render at a matched level behind their fixed character filters, enforced by `kick_types_render_at_a_matched_level`, with `no_kick_type_exceeds_the_headroom_budget` bounding peak. Output trims are measured against Sub. Wood keeps the calibrated gain from the retired filter control's 0.7 position.
- Kick types 1-3 are authored for ambient use, not drum-machine impact: each passes a nonzero attack ms and a sub-1.0 click scale to `KickVoiceCore::new`, which linearly fades the output sample in over the attack (never touching `amp`, so the decay envelope and `is_done` are unaffected) and scales `kick.click`. Sub passes `0.0`/`1.0`, both exactly inert. Do not retune these types toward a harder transient; optional aggression belongs to the shared Drive module.
- Master Drive and Compression are ordinary preloaded shared modules. They use the same root/detail interaction, DSP primitives, registry semantics, automation, and song persistence as copies on voice layers; no bespoke Master compression drill or release row exists.
- Voice RNGs must stay reseedable via `FluidEngine::reseed` so `nooise render --seed` stays byte-reproducible. `a_seeded_render_repeats_itself_exactly` pins that in every profile. The separate `GOLDEN_RENDER_CHECKSUM` tripwire for unintended DSP-math drift is `#[cfg(debug_assertions)]`: debug and release evaluate the same float expressions differently and diverge by an ULP partway through the render, so one constant cannot hold for both. Re-bless it from a debug run.
- Passive update checks must never block the TUI or audio callback; keep crates.io/network work off the main loop and show no message on failure.
- `nooise update` checks crates.io before invoking Cargo; do not force reinstall when the installed version is already current.
- `nooise auto` morphs by owning and rewriting both the live `FluidControls` and `AutomationState`, using the same clone-modify-store path as the UI. Controls are driven by registry `ControlKind`: each leg holds steady for `HOLD_FRACTION` (2/3) of its length, then transitions in the final third. `Gain`/`Continuous` glide across the transition window; `Timing`/`Discrete` snap through `spec.quantize`. The `STRUCTURAL_SNAP_IDS` group jumps atomically on the transition downbeat; other grid params stagger at `STAGGER_STEP_BARS` offsets after it. `AutomationState::morph` pairs lanes by family and ordinal. Each lane's amount glides while its curve and trigger fields snap together at the transition downbeat; lanes present on only one endpoint fade in or out. The AUTO footer reports `MorphState::position_at`: the song sounding on its own through the hold, and `playing → next` with a percentage only once the leg is actually crossing, so it never announces a transition that has not started; a live toggle labels its initial session snapshot `LIVE`. Any manual control or automation edit exits auto. A live toggle's first leg (endpoint 0 = the current live state) always runs `LIVE_FIRST_LEG_BARS` (16 bars), regardless of the loop's configured `bars` (`DEFAULT_AUTO_BARS` 64 normally) — pressing `a` mid-vibe must start audibly moving within the next 16 bars, not wait out a full slow-evolution leg; every leg after the first reverts to the loop's normal length (`MorphState::leg_bars`/`leg_at_indexed`). The CLI names one thing one way: a **song** is either a built-in number or an `n1_` code, and `nooise <SONG>` plays it — `nooise 9`, `nooise 9,10,11` (morphing through them and looping), or `nooise n1_...`. `nooise auto` is every built-in song; `--bars` is the only knob and applies to both. A song number outside the built-in range is an error, never a clamp. `auto` deliberately takes no positional so an old `nooise auto 4` fails loudly rather than silently meaning song four. Add a morph target with `just add-morph <code>`; the helper inserts at the `AUTO_STATES` marker, rejects duplicate codes, verifies decoding, and commits only `auto.rs`.
## Conventions
Crate-wide rules. A change that breaks one is a change to this section, not an exception.
- **Visibility**: `pub(crate)` on everything, fields included. Never bare `pub` — the crate is a binary and exports nothing, so bare `pub` only misleads about reach.
- **Errors**: typed enums with `Display` + `std::error::Error`, and `source()` where a cause exists. No `Result<_, String>` and no stringly channels: an error's identity is data, its prose is `Display`. A payload string is only for text a foreign backend produced (`ClipboardError`, `AudioStartError::Backend`); the variant still names which step failed. User-facing notices format with `{error}`, never `{error:?}`.
- **Infallible lookups**: `.expect()` only where a table test enforces the claim, and the doc comment names that test. Everything else routes through the same failure path as its neighbours.
- **Module docs**: every file opens with `//!` saying what it owns. Not `// ====` banners — those remain only as in-file section separators.
- **Test-only helpers**: gate with `#[cfg(test)]`. Never bare `pub(crate)` plus a dead-code allow; `#[allow(dead_code)]`/`expect(dead_code)` is reserved for enum variants in a closed vocabulary that cannot be conditionally compiled.
- **DSP process fns** take one `…Params` struct, never loose arguments. Details in `fx/AGENTS.md`.
- **Bounds**: `ControlSpec::contextual` is the single source of a control's live range, step, taper, and entry semantics. Nothing downstream re-clamps; pin a DSP's assumed range with a test instead of restating it.
- **Naming**: an index is `*_index` or `index`, never `idx`. Multi-field positions get a named struct (`ModuleScope`), not an unnamed tuple.
- **`use super::*;`** is the deliberate internal prelude of the `fluid` tree: `fluid/mod.rs` re-exports its submodules and each submodule glob-imports them back. This is the one sanctioned wildcard import.
## Verification
- `just test` (cargo test) covers engine logic and the control registry; tests live in `fluid/tests.rs`.
- `just check` runs clippy across all targets.
- `nooise render --seconds N --seed N --out X.wav` (or `just render`) renders audio headlessly for DSP verification — same seed must produce byte-identical output.
## Child DOX Index
- `fx/AGENTS.md` — shared DSP effects (LFO, panner, reverb)
- `synth/AGENTS.md` — shared synthesis primitives (envelope, oscillator, noise, multi-operator FM)
- `fluid/engine/AGENTS.md` — temporary audio gesture processing and resource bounds