StemDeck Stage 1 — Quality Gate
- **Target**: `crates/audio-engine/src/stem_deck.rs` (1643 lines) + the `fx.rs` SVF coefficient-caching change. - **HEAD**: `749408de` on `feat/femto-only-bar` (21 prior meta-review findings already fixed). - **Baseline**: `cargo test -p audio-engine` — 67 pass, 0 fail, 3 ignored. - **Method**: Layer 1 meta-review (6 parallel domain passes + contrarian) → Layer 2 meta:amr (6-domain adversarial debate) → Layer 3 meta:adversarial (Codex gpt-5.3-codex, read-only).
Full Public Reader
StemDeck Stage 1 — Quality Gate
`/chain:quality-gate` — triple-layer review. 2026-05-20.
- Target: `crates/audio-engine/src/stem_deck.rs` (1643 lines) + the `fx.rs` SVF coefficient-caching change.
- HEAD: `749408de` on `feat/femto-only-bar` (21 prior meta-review findings already fixed).
- Baseline: `cargo test -p audio-engine` — 67 pass, 0 fail, 3 ignored.
- Method: Layer 1 meta-review (6 parallel domain passes + contrarian) → Layer 2 meta:amr (6-domain adversarial debate) → Layer 3 meta:adversarial (Codex gpt-5.3-codex, read-only).
Verdict — CONDITIONAL PASS
0 Critical · 2 High · 4 Medium · ~14 Low.
The Hydra quality gate (0 critical AND 0 high) does not pass as-is — the 2 High findings block. Both are small, well-localized fixes. Codex's structural finding: the sample-locked playhead model and the `render()` path are sound; Stage 2 (`StemConductor`) can be built on `StemDeck` once the 2 Highs (and ideally the 4 Mediums) are fixed.
The chain earned its cost: it found QG-A — a reachable panic in the audio worker — and QG-FX1 — reverb default-state incoherence. Both were missed by the original 21-finding meta-review. It also killed 8 false or overstated findings under adversarial pressure.
High — blocks Stage 2
| ID | File:Line | Finding | Fix |
|---|---|---|---|
| QG-A | fx.rs:170, 184, 210, 214 / fx.rs:515, 564, 570 / stem_deck.rs:319 | No sample-rate validation at the construction boundary. `DelayFx::new`/`FlangerFx::new` size their ring buffers as `(sample_rate * N) as usize`, which is `0` for a zero/tiny `sample_rate` → empty `Vec`. Then `DelayFx::set_delay_ms` does `buffer.len() - 1` (usize underflow) and `process_sample` does ` | |
| QG-2 | stem_deck.rs:1599 | `loop_seam_is_click_free` tolerance is `filtered_jump <= raw_jump 1.25 + 1e-4` — it permits the filter to amplify the seam discontinuity by 25 |
Medium
| ID | File:Line | Finding | Fix |
|---|---|---|---|
| QG-1 | stem_deck.rs:179 | `StemFeatureSet::parse` accepts non-finite/negative `bpm` — the `bpm <= 0.0` guard does not catch `NaN` (NaN comparisons are false). Downstream is mostly protected (`bpm()` returns `None` for non-positive), so impact is contained, but a malformed `features.json` should be rejected at parse, not absorbed silently. | `bpm.is_finite() && bpm > 0.0` in the parser; same treatment for `duration_sec` if required. |
| QG-FX1 | fx.rs:287, 360 | `ReverbFx::new` sets the struct field `damping = 0.5` but constructs its comb filters with `damping = 0.2` and never syncs them — the reverb's reported parameter does not match its actual DSP state until `set_damping` is called. | `ReverbFx::new` calls `set_room_size(room_size)` and `set_damping(damping)` before returning. |
| QG-6 | stem_deck.rs:1040 | `renders_summed_and_sample_locked` tests only block size 256; a block-boundary frame-offset bug could hide. | Add a second non-power-of-two block size (e.g. 257 or 511) in the same assertion path. |
| QG-9 | stem_deck.rs:1377 | `render_returns_frames_advanced` asserts `advanced <= 100` where the end-of-clip advance is deterministically exactly 100. | `assert_eq!(advanced, 100)`. |
Low — polish, non-gating
`NX-1` `filter_processes_audio` asserts only `s != 0.5` (proves "filter ran", not "filter correct") · `QG-3` loop-seam test skips the final partial block · `QG-7` `output_is_limited_within_unity` does not isolate soft-clip vs filter · `QG-8` `seek_*`/`play()` unloaded-state behavior undocumented · `QG-10` `StemFeatureSet::bpm()` 0.0-sentinel vs `StemDeck::bpm()` `Option` dual semantics; `soft_clip` doc lacks the `(-1,1)` bound + zero-state/zero-latency note; `recompute_coeffs` cadence comment · `NA-2` single-sample output slice not zeroed · `NA-3` `set_bus_delay` partial application without a BPM · `NX-2` `deinterleave_stereo` `debug_assert` on ragged input is dropped in release · `NP1/NP3/NP4/NP5` micro-perf (flatten branch, flanger `sin` per sample, reverb non-pow2 `
Forward note (NP2): `soft_clip` calls `tanh()` per sample — a libm call (~96k/s). Fine for a single deck through Stage 2; revisit with a polynomial/rational approximation only if multiple decks ever run concurrently.
Killed by the chain (adversarial value)
| Finding | Why killed |
|---|---|
| P1 — reverb `/= 8.0` → `*= 0.125` | LLVM folds `fdiv` by a power-of-two float constant into `fmul` exactly, unconditionally. Non-finding. |
| D1 / QG-12 — tokio-tungstenite 0.21 vs 0.24 | `optional = true`, feature-gated behind `strudel`; not in StemDeck's dependency path. Workspace hygiene, out of scope. |
| C1 / QG-11 — loop-control field "data race" | `StemDeck` is `!Sync` by default (mutable `f32` filter state); the compiler rejects cross-thread sharing. The single-worker-thread contract is structurally enforced, not just documented. |
| QG-4 — `per_stem_gain_mutes` does not prove muting | It does — `max_muted_err < 1e-3` against the analytic 3-stem sum tightly proves the gain reached zero. |
| QG-5 — degenerate-loop test checks symptoms | It does check the clamp — asserts `loop_start < len` and `loop_end > loop_start` at lines 1322-1323. |
| NA-1 — `play()` before load auto-starts | `load_stem_set()` force-resets `playing = false` (stem_deck.rs:450). No auto-start. |
| "Critical / undefined behavior" on NaN bpm | Rust float→int `as` casts saturate (NaN→0) since 1.45 — not UB. Downgraded to a robustness issue. |
| hound / serde / serde_json advisories | All current, no RUSTSEC advisories. Dependency domain clean. |
Remediation wave (one pass, ~1 file each)
1. QG-A — `sample_rate` guard in `StemDeck::new` + `.max(1)` in `DelayFx::new`/`FlangerFx::new`. Add a test: `StemDeck::new` with a bad sample rate fails cleanly; FX constructors never produce a zero-length buffer.
2. QG-2 — flip the `loop_seam_is_click_free` tolerance; this test must be able to fail.
3. QG-FX1 — `ReverbFx::new` syncs comb-filter damping/room-size before returning.
4. QG-1 — `is_finite()` guard in `StemFeatureSet::parse`.
5. QG-6 / QG-9 — strengthen the two transport tests.
After the wave: re-run `cargo test -p audio-engine`, confirm the gate reaches 0 High, then Stage 2 (`StemConductor`) is unblocked.
Promotion Decision
Attach run IDs, datasets, metrics, and reproduction commands.
Source Anchor
Comp-Core/core/audio-media/cc-echelon/tools/lume-music/STEMDECK_QUALITY_GATE.md
Detected Structure
Method · Evaluation · Code Anchors