From fd0aee2354a51196269d8d2a4cddaf673fa1a4ea Mon Sep 17 00:00:00 2001 From: monkey-w1n5t0n Date: Tue, 21 Jul 2026 01:24:35 +0200 Subject: [PATCH] =?UTF-8?q?docs(audit):=20simplification=20audit=202026-07?= =?UTF-8?q?=20=E2=80=94=20recon=20findings,=20phased=20plan,=20ALIGNMENT?= =?UTF-8?q?=20rewrite?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 66-agent adversarially-verified audit vs the five-bullet one-core vision. 113 findings: CI red since 2026-07-13 (memllib pin on no remote), ungated deploys, unshared mode layer, no curated/advanced split, dead-mass inventory. Recon: docs/specs/recon/simplification-audit-2026-07.md (immutable). Plan: docs/specs/plans/simplification-plan.md (proposal; phases gated on operator adoption, §7 decisions). ALIGNMENT rewritten; MAP flatly-false lines fixed (phantom MEMLCelium-upstream entry, exploration.ts, daisysp non-submodule, pre-P5 sentence, perf-attr claims); MAIN registry updated. --- ALIGNMENT.md | 121 ++++--- MAP.md | 19 +- docs/specs/MAIN.md | 2 + docs/specs/plans/simplification-plan.md | 115 +++++++ .../recon/simplification-audit-2026-07.md | 298 ++++++++++++++++++ 5 files changed, 496 insertions(+), 59 deletions(-) create mode 100644 docs/specs/plans/simplification-plan.md create mode 100644 docs/specs/recon/simplification-audit-2026-07.md diff --git a/ALIGNMENT.md b/ALIGNMENT.md index 08da0a3..f22467a 100644 --- a/ALIGNMENT.md +++ b/ALIGNMENT.md @@ -6,87 +6,110 @@ A research platform for interactive ML control of audio. We're building it to figure out what works and what doesn't — different ergonomics and ergodynamics of parameter sets, modes, ML architectures, audio engines, UI, and UX. Therefore: keep most/all parameters tweakable, ML/engine/UI/UX should each be configurable on their own axis, and the codebase has to enable/assist agentic AI coding patterns (confident changes, verifiable without hardware). -The clean-slate rewrite (2026-04-29) consolidated everything into one C++20 codebase compiling to firmware AND WASM. Since 2026-07-13 (P1 of `docs/specs/plans/one-core-engine-refactor.md`) the sole browser app is the React Manifold; the SolidJS playground is archived (`archive/playground-solidjs`). JSON schemas remain the firmware↔browser parameter contract. +**Target vision (operator, 2026-07-20):** (1) one C++20 NISPS core serving RP2350 firmware and the browser, performance-sensitive on the MCU; (2) firmware modes runnable as modes in Manifold; (3) Manifold defaults to curated presets/modes, with the maximalist surface behind an "advanced" dev mode used to author them; (4) PlatformIO for hardware, no more .ino; (5) Manifold doubles as interface/editor for the hardware MEMLNaut (settings, presets, training, examples, visualisation). + +The clean-slate rewrite (2026-04-29) consolidated everything into one C++20 codebase compiling to firmware AND WASM. Since 2026-07-13 (P1) the sole browser app is the React Manifold. JSON schemas remain the firmware↔browser parameter contract. A full-repo audit (2026-07-21, `docs/specs/recon/simplification-audit-2026-07.md`) grounds the entries below; mitigations are phased in `docs/specs/plans/simplification-plan.md`. ## Top defects (ranked by mission impact) -### 1. Browser-only audio engines incomplete (2026-04-29; updated 2026-07-13) +### 1. The verification story is void: CI red for a month, unpushed load-bearing commits, ungated deploys (2026-07-21) -**What.** C15 never got past a stubbed placeholder mode, and with the playground retired at P1 (2026-07-13) it has NO home on main — the stub UI, `c15.wasm`, and `c15-glue.js` live only on branch `archive/playground-solidjs`. Mic input for XIASRI / SoundAnalysisMIDI is likewise not wired in manifold. +**What.** CI has been 100% failing on main since 2026-07-13: the memllib submodule pin (`b37fc53`, local branch `feat/nisps-core-swap`) is reachable from no remote, so GitHub checkout dies before any gate runs — and those three firmware-critical commits exist only on this one disk. Meanwhile push-to-main deploys straight to `meml.lnfinitemonkeys.org/next/` via webhook with no gate, shipping the *committed* `manifold/public/nisps.wasm`, which has no freshness check. All P4–P6 "gates green" claims rest on local runs. -**Why it blocks the mission.** "Browser engines ⊇ firmware engines" was a non-negotiable. Without C15 + mic input, manifold can't fully demonstrate the modes; users can't audition XIASRI or SoundAnalysisMIDI in the browser. +**Why it blocks the mission.** "Verifiable without hardware" and "confident agentic changes" are the mission's operating premises; both are currently fiction at the remote/deploy boundary. One disk failure loses firmware-critical code. -**Rough cost.** ~2 days. Reviving C15 now means porting the archived bridge into manifold's engine host. Mic input requires the engine-host to expose an input stream to the worklet (small worklet refactor). +**Rough cost.** Half a day (plan §1): push the branch to the `monkey-w1n5t0n/memllib` fork, repoint `.gitmodules`, confirm green, add codegen + WASM-freshness steps; deploy gating is an operator decision (plan §7.4). -### 2. Per-iteration loss curve not plumbed through WASM (2026-04-29) +### 2. The mode layer is not shared: WASM re-orchestrates modes by hand (2026-07-21) -**What.** Stream 7's WASM C API exposes `_nisps_ml_train` but only returns the final loss; `MLP::loss_history()` exists in C++ but isn't reached. `lossHistory` in `mlStore` is a single-element array per training run. +**What.** `nisps/modes/` — the CRTP layer binding ML config, engine, voice-space and I/O — compiles only into firmware. `nisps/wasm/bindings.cpp` includes engines and ML primitives but zero mode headers, and Manifold re-assembles mode behaviour (jolt stepping, OU, routing) in TS. "Firmware and WASM share the same modes" is true only at the engine level; every ModeBase behaviour must be mirrored browser-side by hand. -**Why it blocks the mission.** Gradient flow / loss visualization is a core UX affordance for "is the network learning?" — a research-mode debugging tool that's been implemented end-to-end in C++ but stops at the WASM boundary. +**Why it blocks the mission.** Vision bullet 2 is precisely this. Until the control-tick orchestration exists once in C++, every new mode behaviour is a dual implementation with drift risk. -**Rough cost.** Half a day. Add `nisps_ml_train_with_history` (or extend the existing call) returning a pointer to the loss array; copy on the JS side. +**Rough cost.** Spec first, then ~a week: storage-policy the ModeBase orchestration the way P2 did MLPCore (verified shape in plan §6.5a — *not* binding monolithic mode objects, which would contradict the locked two-instance RT architecture). Related honesty gap: Manifold currently catalogues 4 modes that structurally cannot run in the browser (no mic input, event-only engines) — plan §6.5b (absorbs the old C15/mic-input defect; C15 itself lives on `archive/playground-solidjs`). -### 4. `NISPS_AUDIO_FUNC` host fallback is misshapen (2026-04-29) +### 3. No curated/advanced split and no in-UI mode picker — the UI fights vision 3 (2026-07-21) -**What.** `nisps/core/perf.hpp` defines `NISPS_AUDIO_FUNC(decl) decl` for the host but the firmware path `__not_in_flash_func(name)` takes only a function name (it stringifies into a section attribute). The two forms don't match. Stream 6 (firmware glue) avoided the macro to dodge the inconsistency, but it's still a footgun. +**What.** Manifold is 100% dev-maximalist: five drawers of everything, no preset data model to author against, and mode switching exists only via the debug hook — there is no instrument picker in the UI at all (the plumbing, `ctx.modes`/`setModeId`, already exists unused). A stratum of decorative controls (training-param sliders, master volume, bpm, A/B, snapshots, fabricated gradient health) renders real-looking UI that drives nothing. -**Why it blocks the mission.** Future agents touching `nisps/` will hit this. Either decoration form in the codebase is fine; what's wrong is that the same call site shape doesn't work both places. +**Why it blocks the mission.** The default experience is supposed to be curated presets; the advanced surface is the authoring tool. Neither exists, and the decorative stratum actively misleads research use. -**Rough cost.** Tiny. Pick one form and apply consistently: -- Option A: `NISPS_AUDIO_FUNC` decorates a function name (e.g. `void NISPS_AUDIO_FUNC(my_callback)(...) { ... }`). Host stub: `#define NISPS_AUDIO_FUNC(name) name`. -- Option B: separate `NISPS_AUDIO_FUNC_BEGIN` / `_END` markers around the function, or a different macro. -Pick A. Update perf.hpp + every `nisps/` use site. +**Rough cost.** Product-model decision first (plan §7.6), then incremental: picker is days; the curated-preset model seeds from `backends/presets.ts` + schemas; disclosure via per-drawer depth levels. Deleting the decorative stratum is part of the Phase-1 sweep. -### 5. RMSProp deferred from `nisps/ml/` (2026-04-29) +### 4. Arduino-CLI build machinery is actively hostile — vision 4 unstarted (2026-07-21) -**What.** The legacy MLP supported both SGD and RMSProp paths. Stream 2 shipped only SGD as MVP. The architecture spec called for both. Documented as a follow-up Ergo task when needed. +**What.** The build script sed-mutates the committed `.ino` to select variants (polluting history), the mode list is triple-bookkept (a `NISPS_ST_*` token-paste table is already silently missing the currently-active SLPWorkshop variant), a symlink forest works around Arduino's include rules, and the toolchain globally mutates the installed TFT_eSPI library. Firmware compilation is in no automated gate anywhere. -**Why it blocks the mission.** Optimizer choice is one of the things research wants to vary. Not blocking for the current XOR-style fits, but as soon as we tune for harder loss landscapes, RMSProp will matter. +**Why it blocks the mission.** Fragile-by-design builds are the opposite of "confident agentic changes"; the vision names PlatformIO explicitly. `firmware/useq-celium/` already proves the PIO pattern in-repo. -**Rough cost.** A day. Port the firmware's RMSProp from `src/memlp/MLP.cpp:415-543` (decay 0.9, epsilon 1e-6, gradient accumulation, batch size). Add tests for batch training convergence. +**Rough cost.** 2–3 days, one cut (plan §5): env-per-variant `platformio.ini`, delete ~400 lines of hackery, then a firmware CI job. Gated on the memllib ownership decision (plan §7.5). + +### 5. Manifold-as-hardware-editor is a facade (2026-07-21) + +**What.** Vision bullet 5 exists as a 237-line Web Serial shell: sound connect lifecycle, zero protocol (`saveModel`/`restoreModel`/`getSettings` are literal stubs), and firmware has no serial command surface or on-device persistence to talk to. + +**Why it blocks the mission.** The hardware research loop (train on device, inspect/curate in browser) is closed only by this bridge. + +**Rough cost.** Week+, spec-first (plan §6.5d). The right discipline already exists in-repo: useq-celium's C-header wire truth + TS mirror + parity test; settings payloads should derive from schema codegen. + +### 6. Dead mass and registry sprawl across every layer (2026-07-21) + +**What.** The audit's aggregate: ~40% of `feedback.hpp` is legacy modes nothing reaches; a dead four-way focus/altitude UI system; ~25 unconsumed ConsoleCtx fields; a dozen dead WASM API entries threaded through a 5-file registration chain; daisysp compiled into every firmware build with zero consumers; retired-playground artifacts and dead planning relics tracked at root; mode identity spread across ~6 hand-maintained registries with demonstrated drift; assorted stale specs presenting a deleted world as present tense. + +**Why it blocks the mission.** Every dead path is agent-confusing surface area and drift risk; the registries are dual-truth bugs waiting to fire (one already did: the selftest table). + +**Rough cost.** Plan phases 1–3 (~a week total, mostly mechanical deletions with green gates). Behaviour bugs found en route (dataset-cap divergence 100 vs 128 + OOB read, VCV 2-D input truncation, VCV audio-thread race + JSON) are plan §3. + +### 7. No performance measurement despite a performance-defined mission (2026-07-21) + +**What.** The "super performance-sensitive" constraint is enforced only by static discipline (no-heap lint — itself with proven false negatives — and section attrs, 3/5 of which are dead macros). No benchmark, no CPU-load assertion, no flash/RAM size report on either target; the 16 KB dead buffer was found by reading, not by any gate. + +**Rough cost.** ~A day for a host-side blocks-per-second benchmark + a per-variant size report in `build-firmware.sh` (plan §6.5f). + +### 8. Training-health telemetry: one product decision fragmented into four half-features (2026-07-21) + +**What.** A 16 KB loss-history buffer in every firmware MLP that nothing reads; a WASM worker faking a 1-element loss history; decorative gradient-health UI; and a real `get_layer_stats` API plumbed end-to-end and consumed by nobody. + +**Why it blocks the mission.** "Is the network learning?" is a core research affordance — currently it *looks* answered while being fake. Decide feature-or-delete once (plan §7.3) and collapse all four limbs accordingly. + +### 9. RMSProp still deferred from `nisps/ml/` (2026-04-29; reaffirmed 2026-07-21) + +**What.** `training.hpp` ships SGD only; the legacy firmware used RMSProp for `TrainBatch`. Optimizer choice is a research axis. Not blocking current fits; will matter for harder loss landscapes. Port target: upstream MusicallyEmbodiedML `memlp` (the in-repo `src/memlp` copy is deleted; use the GitHub remote or archive branch). + +**Rough cost.** A day, plus batch-convergence tests. ## Open mission questions ### Q1: Per-mode MLP architectures or one shared shape? (2026-04-29) -Schemas declare per-mode `input_size`/`hidden_layers`/`output_size` (some hidden `[10, 10, 14]`, some `[10, 14, 18]`; inputs 4 or 10; outputs 24–56). As of P5.3 BOTH targets honour them: firmware compiles per-mode, and the browser now reshapes the runtime-shaped WASM net to the active mode's `ml` config on mode switch (was fixed at one 32→126 shape). This works for now. Is the mission served by maintaining per-mode shapes (research diversity) or by collapsing to one (simpler ops)? +Schemas declare per-mode dims and since P5.3 both targets honour them. Is the mission served by maintaining per-mode shapes (research diversity) or collapsing to one (simpler ops)? Note the audit found all 9 mode schemas share copy-pasted ML defaults and 20 params are anonymous placeholders — the per-mode diversity is currently nominal (plan L40). -### Q2: How to express "advanced" features (gradient flow, weight health) without cluttering modes? (2026-04-29) +### Q2: Engine event taxonomy (2026-04-29) -The retired playground reproduced the a-immersive "Advanced" toggle; manifold hides power features in drawers instead. Is this the right model, or should the mode UI itself decide what's exposed (some modes are "expert-only", some are simpler)? +`ControlEvent` is a flat enum consumed by the two sequencer modes. Revisit when a third event-emitting mode lands. -### Q3: Engine event taxonomy (2026-04-29) +### Q3: Should Manifold stay desktop-first? (2026-04-29) -`nisps/modes/base.hpp` exposes a `ControlEvent` ring buffer pop_events interface for sequencer modes (BreakOr, Elysiamorf). Currently events are a flat enum. As we add more event-emitting modes (custom MIDI mappings, lighting, networked control), how should the event vocabulary grow? Open question; revisit when we add the third event-emitting mode. +Legacy a-immersive was mobile-first; Manifold is desktop-first. Defer until user data exists. -### Q4: Should the browser app (manifold) stay desktop-first? (2026-04-29) +### Q4: Who owns memllib? (2026-07-21) -The original a-immersive was mobile-first ("designed for touch / foldable phone use"). The SolidJS rewrite is desktop-first by default. If the research story is "the user holds a phone and pinches to zoom while a synth runs in their pocket", we'll need a responsive pass. Defer until we have user data. +Fork-pin (PIO `lib_deps` on `monkey-w1n5t0n/memllib`), vendor the actually-used subset, or upstream the nisps-swap to MusicallyEmbodiedML? Requires the load-bearing-surface inventory (plan §5). Phase 0 pushes the branch either way. + +### Q5: Legacy feedback modes — delete or keep for A/B? (2026-07-21) + +`RandomiseOutputs`/`RandomiseMlp`/`Diffuse`/`on_drag` have no product consumer, but `docs/adr/rl-feedback-design.md` explicitly kept Diffuse for A/B comparison. Deleting reverses a recorded decision — operator call (plan §7.1). ## Deferred / accepted debt -- **EOC effects chain integration** — out of v1 rewrite (recon flagged as legacy complexity). -- **ShapeSeq sequencer** — gated behind `?shapeseq=1` in legacy; out of v1. -- **Modular engine (Phase E)** — newer JS-side feature in legacy; out of v1. -- **Engine configuration panel** (SPEC-controls Part 8) — backlog. Would let users tune network architecture, loss, optimizer at runtime. Currently compile-time only. -- **VCV Rack module** — used to consume `nisps-core/`. Now gone. If revived, it'd consume `nisps/` directly via CMake; not currently maintained. -- **Geometric-dislike deliberate divergences** (2026-07-14, one-core-engine P3; supersedes and RETRACTS the 2026-06-18 "Avoid = move_weights, geometric push not ported" note — the k-NN centroid push IS now ported, upstream `InterfaceRL` @ `0a541cc`, into `nisps/ml/{replay,geo_push}.hpp` + `feedback.hpp` `AvoidStyle::Geometric` default). Two divergences are by design: - 1. The upstream `useRandom` degenerate branch (disliked action exactly on the centroid) draws from the controller's deterministic `nisps::Rng`, not libc `rand()` — native==WASM parity holds (parity Stage 6); the value is generated, never compared against upstream. - 2. Upstream trains via async `optimise()` with shuffled `TrainBatch` over positive+geometric batches at two LRs; nisps collapses press+optimise into ONE synchronous `dislike_geometric()` that trains only the pressed negative's target (per-sample SGD, no shuffle). Behavioural — not bitwise — parity with firmware upstream, by design; `native == WASM` is pinned at 1e-5 instead. - Also: `RandomiseMlp` still uses `draw_weights(spread)` rather than the old asymmetric `RandomiseWeightsAndBiasesLin(-0.9,1.1,-0.9,0.3)` (unchanged accepted divergence). - -- **Manifold dock splits `state`/`muted`/`armed` into three fields, diverging from the deployed conflated `frozen`↔`muted`** (2026-06-28) — the deployed a-immersive override system maps the heatmap-popup `frozen` and the group-drawer `muted` onto ONE underlying field. The Manifold per-output model (`manifold/src/dock/output-state.ts`, folded onto `MFParam`) deliberately separates them: `status` carries the off/fixed/live tri-state, `muted` is downstream-silence (still computed + visible), `armed` is solo/focus-training. Cleaner semantics; intentional divergence (dock-spec §3.3, open choice 3). Note `muted`-downstream and the `soloMode` gradient-mask variants (mask-gradients / zero-loss / dont-care) are UI+state only so far — the engine C API exposes `set_focus` but not per-mode gradient masking nor a downstream mute gate yet (TODOs in `ConsoleApp.tsx` / `Drawers.tsx` reference rl-feedback-design §3 and dock-spec §3.3). +- **EOC effects chain, ShapeSeq sequencer, modular engine (Phase E)** — legacy features consciously out of the v1 rewrite; revisit only if a mode wants them. +- **Inputs multi-source composition** (2026-06-28, reaffirmed 2026-07-21) — mix-and-match pad+gamepad+MIDI is a recorded, unreversed decision; the UI currently enforces exclusive single-source and the composition machinery sits dormant *by design*. Schedule or keep dormant — but the inputs-spec must stop presenting composition as current behaviour (plan §8). +- **Schema content is partially placeholder** (2026-07-21) — 20 anonymous "Param NN" slots across paf_synth/channel_strip/xiasri and copy-pasted ML defaults across all 9 modes. Name them during the first curated-preset pass per mode (plan §6.5c), or shrink `output_size` where the engine allows. +- **Geometric-dislike deliberate divergences** (2026-07-14, one-core P3): (1) the degenerate-branch RNG draws from the controller's deterministic `nisps::Rng`, not libc `rand()` — native==WASM parity holds; (2) upstream's async shuffled two-LR `optimise()` is collapsed into one synchronous `dislike_geometric()` training only the pressed negative's target — behavioural, not bitwise, parity with firmware upstream, by design; (3) `RandomiseMlp` uses `draw_weights(spread)` rather than the old asymmetric ranges. All intentional. +- **Manifold dock splits `state`/`muted`/`armed`** (2026-06-28) — deliberate divergence from the deployed conflated `frozen`↔`muted` model (dock-spec §3.3). `muted`-downstream and the `soloMode` gradient-mask variants remain UI-only; the C API exposes `set_focus` but no per-mode gradient masking yet. (The audit found `soloMode` behaviourally inert in the controller — plan L20 trims it until `train_masked` exists.) ## Recently resolved (delete after a few weeks) -- 2026-07-18: Browser curve maths unified onto the canonical `nisps/core/math.hpp` catalog at P4. The retired TS mirror had silently divergent maths for `exp`/`log` (k=4 vs the C++ k=1-normalised pair), `sigmoid` (slope 8 vs 6) and `cubic` (smoothstep vs x³) — browser-shaped params now behave firmware-exact. `linear/square/sqrt/centred-power` were already identical; the four changed curves were re-baselined in `manifold/tests/fixtures/curves-golden.json`. - -- 2026-07-14: Defect "WASM MLP architecture is fixed" resolved by one-core-engine P2: `nisps/ml/` is storage-policied (`MLPCore`); the browser MLP is runtime-shaped (`DynamicStorage`), `nisps_ml_create` honours dims, `nisps_ml_reshape` warm-starts. Firmware keeps the zero-heap fixed template (`.text` +0.30%, within contract). - -- 2026-04-29: Three-implementation ML duplication (firmware `memlp`, `nisps-core`, JS engine) collapsed to single `nisps/` C++ codebase. -- 2026-04-29: Firmware mode forks (~280–400 lines duplicated across 8 modes) collapsed via `nisps/modes/base.hpp` CRTP scaffold; concrete modes are now ~50–130 lines. -- 2026-04-29: meml-ues double-scaling MSE bug fixed in `nisps/ml/loss.hpp` + `mlp.hpp`. -- 2026-04-29: `nisps-core/` retired; firmware is the canonical source of truth for ML. -- 2026-04-29: `src/memlp/` submodule deleted. -- 2026-04-29: Legacy playground variants (a-immersive.html, b-workbench, c-journey, designs.html, all `js/`) deleted in favor of SolidJS scaffold. -- 2026-04-29: Native↔WASM parity verified within 1e-5 (max delta 2.4e-7) for representative ML + engine outputs. +- 2026-07-21: Full-repo simplification audit landed (recon + plan + this rewrite). Superseded entries removed: "browser-only engines incomplete" (→ defect 2/plan 5b), "loss curve not plumbed" (→ defect 8), "NISPS_AUDIO_FUNC misshapen" (→ plan Phase 1, S21/L13), stale "VCV not currently maintained" note (vcv/ is active and consumes `nisps/` directly post-P6). +- 2026-07-18: Browser curve maths unified onto the canonical `nisps/core/math.hpp` catalog at P4; four silently-divergent TS curves re-baselined. +- 2026-07-14: WASM MLP fixed-architecture defect resolved by P2 (`MLPCore`; browser runtime-shaped, firmware zero-heap fixed). diff --git a/MAP.md b/MAP.md index b07b753..788c990 100644 --- a/MAP.md +++ b/MAP.md @@ -27,7 +27,6 @@ MEMLNaut-NISPS — Neural Interactive Shaping of Parameter Spaces. One C++20 cod - `settings_view.hpp` — `wire_settings(mode)`: adds on-device settings views to the MEMLNaut display carousel (TFT + rotary encoder). Joystick Dual/Single toggle for the 4-input ("two 2-D joystick") modes — "Single" pins ML input channels 2,3 to neutral via `ModeBase::set_input_pinned` (no net rebuild). Registered in the `.ino` after `addSystemInfoView()`. - `firmware/MEMLNaut-NISPS/src/{memllib,daisysp,nisps}` — symlinks (Arduino-CLI requires sketch-tree includes; preprocessor refuses `..` in headers). - `firmware/README.md` — structure + build instructions. -- `firmware/MEMLCelium-upstream/` — **vendored** verbatim snapshot of the upstream `MusicallyEmbodiedML/MEMLNaut-NISPS` @ `main` Arduino sketch (pre-refactor monorepo, does NOT use `nisps/`), preset to `MODE_MEMLCELIUM`. Self-contained: upstream `memllib`@`e291192d` + `memlp`@`ea777502` vendored as plain files; `src/daisysp` in-tree. Built directly with `arduino-cli` (not the repo build scripts) — see its `README.md` for provenance + the compile command. - `firmware/useq-celium/` — standalone RP2040 firmware (PlatformIO, Arduino-Pico core) that turns a uSEQ module + CV expander into a USB→CV/gate converter driven by the manifold `cvgate` backend. `shared/protocol.h` is the v2 wire-protocol single source of truth (mirrored by `manifold/src/backends/useq-protocol.ts`); `main/` (USB serial → CV1–3 + GATE1–3, I2C → expander) and `expander/` (I2C slave → CV4–11). Wire spec: `docs/specs/useq-cv-protocol.md`. Restored from the April-2026 "uSEQ-Celium" mode. ### `manifold/` — Vite + React + TS convertible-mode app (the sole browser app) @@ -76,9 +75,9 @@ anchor + locked decisions) and the `docs/specs/*-spec.md` set. prototype), `rng.ts` (seeded). - `manifold/src/settings/` — `settings-store.ts` (monochrome icons, input-map shape, corner radius). - `manifold/src/serial/` — `memlnaut-serial.ts` Web Serial scaffold + `EditorPanel.tsx` (MEMLNaut Editor mode). -- `manifold/src/engine/exploration.ts` (+ `jolt.ts`, `ou-explore.ts`) — Jolt press + OU explore gestures - (Learning drawer). Interim TS math from the retired playground via get/set-weights + the spine's - `setOutputMorph` hook; marked `P3 SWAP POINT` for the WASM bindings. +- `manifold/src/engine/exploration.ts` — Jolt press + OU explore gestures (Learning drawer): a thin + timer-driver over the shared C++ core via the `nisps_ml_jolt_*`/`nisps_ml_ou_*` bindings (the interim + TS math and `jolt.ts`/`ou-explore.ts` were deleted when P3 landed). - `manifold/src/debug/probe.ts` — `window.__nisps` (`?debug=1`). `manifold/tests/e2e/` — `smoke`, `probe-api` (15-test engine-contract port), `spine` (spine invariant + probe-survives-mode-switch). E2E on the VPS runs via non-snap node (see BUILD-PLAN). `manifold/tests/fixtures/` — golden parity @@ -122,9 +121,9 @@ includes; no `nisps-core`. ### `.github/workflows/` - `ci.yml` — GitHub Actions: cmake build + ctest + WASM build + parity check + lint + Playwright (cpp-tests + manifold-tests jobs). Firmware compile is documented as manual. -### Submodules (in `src/`) -- `src/memllib/` — hardware abstraction (audio driver, peripherals, MIDI). **Not auto-initialized** — fresh clones need `git submodule update --init --recursive`. -- `src/daisysp/` — vendored DSP library. Used by some firmware glue; nisps replaced its PitchShifter with a custom granular impl. +### `src/` — submodule + vendored trees +- `src/memllib/` — hardware abstraction (audio driver, peripherals, MIDI), the only true submodule. **Not auto-initialized** — fresh clones need `git submodule update --init --recursive`. ⚠ The pinned commit currently lives on no remote (ALIGNMENT defect 1 / plan §1). +- `src/daisysp/` — vendored plain files (NOT a submodule). Zero remaining consumers — nisps replaced its PitchShifter with a custom granular impl; deletion planned (plan S8). ### Top-level docs - `CLAUDE.md` — long-form architecture narrative. @@ -148,11 +147,11 @@ includes; no `nisps-core`. ## Conventions - Firmware mode selection is compile-time only — `#define MEMLNAUT_MODE_TYPE` in the `.ino`. -- `nisps/` follows Chris's RP2350 perf rules globally: no heap, `static const float` for non-trivial constants, strict `.f` suffix, memory section attrs (`NISPS_AUDIO_MEM`, `NISPS_AUDIO_FUNC`, `NISPS_APP_SRAM`, `NISPS_HOT`, `NISPS_FORCE_INLINE`). +- `nisps/` follows Chris's RP2350 perf rules globally: no heap, `static const float` for non-trivial constants, strict `.f` suffix. Of the `perf.hpp` section attrs only `NISPS_HOT`/`NISPS_FORCE_INLINE` are actually in use — `NISPS_AUDIO_MEM`/`NISPS_APP_SRAM`/`NISPS_AUDIO_FUNC` are dead or misshapen and slated for deletion (plan S21). - C++ identifiers: `PascalCase` types, `snake_case` functions/variables, `kPascalCase` constexpr. JSON keys `snake_case`. TS types `PascalCase`, components `PascalCase.tsx`, modules `kebab-case.ts`. - `Curve` enum lives in `nisps/core/math.hpp` (lowercase: `linear/exp/log/square/sqrt/sigmoid/cubic`, plus the parameterised `centered_power` free function); generated mode headers re-export via `using Curve = ::nisps::Curve;`. Since P4 there is NO TS mirror — the browser samples the WASM catalog (`nisps_curve_apply(+batch)`). - Modes are TSX components composed of primitives; mode parameter contracts are JSON schemas with codegen → C++ **and** TS types (`MF_MODES` derives params/ml-config from the generated schemas since P5; labels/ordering stay a manifold overlay). **No declarative JSON UI.** -- WASM and firmware share the same C++; the browser MLP is runtime-shaped (`MLPCore`, since P2): `nisps_ml_create` honours `(input, output, hidden[3])` with non-positive/null args defaulting to `32→[10,14,18]→126`; `nisps_ml_reshape` warm-starts a new shape. Firmware keeps compile-time `MLP<...>` (zero heap). Modes currently still use a slice of the default 126 outputs (per-mode dims become schema-real at P5). +- WASM and firmware share the same C++; the browser MLP is runtime-shaped (`MLPCore`, since P2): `nisps_ml_create` honours `(input, output, hidden[3])` with non-positive/null args defaulting to `32→[10,14,18]→126`; `nisps_ml_reshape` warm-starts a new shape. Firmware keeps compile-time `MLP<...>` (zero heap). Per-mode dims are schema-real on both targets since P5.3 (the browser reshapes on mode switch). - Cross-platform parity: `scripts/parity-check.sh` enforces native vs WASM agreement within 1e-5. ## Gotchas @@ -161,7 +160,7 @@ includes; no `nisps-core`. - Firmware sketch path is `firmware/MEMLNaut-NISPS/MEMLNaut-NISPS.ino` (Arduino-CLI requires sketch dir name == sketch file name); `firmware/MEMLNaut-NISPS/src/{memllib,daisysp,nisps}` are symlinks because Arduino's preprocessor refuses `..` in includes from sketch headers. - `firmware/MEMLNaut-NISPS/glue/mode_select.hpp` `#undef`s Arduino macros (`sq`, `min`, `max`, `abs`, `round`) before pulling nisps headers — engines use those identifiers as method names. - `nisps_firmware::g_active_mode_bridge` is `extern` in `glue/audio_driver.hpp` and defined in the `.ino`; combining `inline` with `__not_in_flash` produces a comdat conflict at link time. -- The host fallback of `NISPS_AUDIO_FUNC` in `nisps/core/perf.hpp` is misshapen for use as a function-name decorator (firmware path expands to `__not_in_flash_func` which takes only a name); firmware glue avoids the macro to dodge the inconsistency. See `ALIGNMENT.md`. +- The host fallback of `NISPS_AUDIO_FUNC` in `nisps/core/perf.hpp` is misshapen for use as a function-name decorator (firmware path expands to `__not_in_flash_func` which takes only a name); `glue/midi_io.hpp:72` still uses it despite this. Deletion planned (plan S21/L13). - `nisps_modes_tests` builds against generated schemas under `nisps/modes/generated/`; if you add a new mode, regenerate via `bun run codegen/generate.ts` before building. ## Smells / strategic concerns diff --git a/docs/specs/MAIN.md b/docs/specs/MAIN.md index cf30605..f8d1f98 100644 --- a/docs/specs/MAIN.md +++ b/docs/specs/MAIN.md @@ -100,6 +100,7 @@ All specs below are stable or evolving (no aspirational ones in shipped features | `plans/BUILD-PLAN.md` | active | Manifold build resume anchor (dated 2026-06-27). Locked decisions: React app in `manifold/`, parity-tested TS engine from `playground/src`, staging deploy at `meml.lnfinitemonkeys.org/next`, default feedback mode = Explore-and-Place. | | `plans/playground-2.0-rewrite-plan.md` | executed | SolidJS clean-room rewrite plan (June 2026). Largely implemented in Manifold + playground foundation: one fullscreen instrument, Console interaction model, right-edge dock with three depths, snapshot DAG, control-point tri-state. | | `plans/one-core-engine-refactor.md` | active | Firmware+Manifold core reunification (dated 2026-07-13). Locked: retire `playground/`, all algorithms into `nisps/` C++ (geometric dislike, Jolt, OU, RNG, pipelines, curves), storage-policy MLP (fixed template on RP2350, runtime-shaped in WASM/VCV), codegen serves manifold, VCV last. Supersedes BUILD-PLAN's "multiple WASM modules" MLP decision. | +| `plans/simplification-plan.md` | active | Phased burn-down of the 2026-07-21 simplification audit (proposal; phases gated on operator adoption). Phase 0 restores CI/verification (memllib pin unreachable, deploys ungated); then dead-mass deletion, behaviour bugs, registry consolidation, PlatformIO migration, vision-facing builds. §7 lists the operator decisions. | --- @@ -115,6 +116,7 @@ All findings are dated 2026-06-27 unless otherwise noted; exempt from drift lint | `recon/midi-gamepad-inputs-worklog.md` | 2026-06-27 | Work log of `feat/midi-inputs` branch. Modular input sources (MIDI, gamepad), 32-input WASM foundation, gamepad→verdict wiring. Exclusive picker (not mixing) shipped; mixing engine groundwork done but UI deferred. | | `recon/upstream-firmware-survey.md` | 2026-06-27 | Git archaeology of the MusicallyEmbodiedML ecosystem. origin/main (C++20/SolidJS rewrite) and upstream/main (old `.ino` firmware) forked at `6efbe9c` (2026-04-14); 49 commits upstream not in origin are ports, not merges. | | `recon/playground-2026.md` | 2026-04-12 | Design intent snapshot of an unfinished playground UI redesign. Reference-only for SolidJS rewrite; do NOT merge into vanilla playground (those files moved on independently). | +| `recon/simplification-audit-2026-07.md` | 2026-07-21 | Full-repo smell/bloat/spec audit vs the five-bullet one-core vision (66-agent workflow, adversarially verified). 113 findings: CI red since 2026-07-13, memllib pin unpushed, ungated deploys, mode layer unshared, no curated/advanced split, dead-mass inventory. Mitigations in `plans/simplification-plan.md`. | --- diff --git a/docs/specs/plans/simplification-plan.md b/docs/specs/plans/simplification-plan.md new file mode 100644 index 0000000..7bebae0 --- /dev/null +++ b/docs/specs/plans/simplification-plan.md @@ -0,0 +1,115 @@ +--- +kind: plan +status: active +--- + +# Simplification Plan 2026-07 — burning down the audit + +*Dated 2026-07-21. Derived from `../recon/simplification-audit-2026-07.md` (finding IDs `A*`/`S*`/`L*`/`ST*` refer to its inventory; every mitigation here was adversarially verified against the code). Companion: `ALIGNMENT.md` carries the strategic tier. Proposal only — no phase is adopted until the operator says so; §7 lists the decisions that gate specific items.* + +## §0 Principles and ordering rationale + +- **Deletion over refactor, refactor over addition.** Nothing gets a compat shim without a named consumer. +- **Hard constraints hold throughout**: `nisps/` platform-neutral + allocation-free hot paths; native↔WASM parity ≤1e-5; schema changes ship with both codegen outputs; RT-safe worklet/dual-core comms. Every phase ends with `run-all-tests.sh` green. +- **Docs move in the same commits as code** (MAP/ALIGNMENT/spec-corpus sync is part of "done"). +- Order: *restore verification first* — until CI runs, nothing else can be trusted to have landed; *then delete dead mass* — it shrinks every later diff; *then bugs, then truth-consolidation, then the vision builds*. +- On adoption of a phase, decompose it into ergo tasks (one per bullet-group below); this doc is the map, not the tracker. + +## §1 Phase 0 — Restore verification (URGENT, ~half day) — S7, S24, S31, S32; critic gaps 1–3 + +CI has been 100% red on main since 2026-07-13; the cause and the data-loss risk are the same object. + +1. **Push `feat/nisps-core-swap` to the `monkey-w1n5t0n/memllib` fork** (3 commits incl. pin `b37fc53` currently existing only on this disk); repoint `.gitmodules` to the fork URL; verify a fresh clone + `submodules: recursive` checkout succeeds; delete the compensating error-message paragraph in `scripts/build-firmware-arch.sh` (S7). +2. Confirm CI goes green end-to-end; only then trust any subsequent gate claims. +3. **Codegen enforcement in CI** (S24, S31): in the manifold-tests job (bun already present) run both generators + the golden test and fail on dirty diff — makes the "regenerated outputs in the same change" rule real. +4. **WASM freshness gate** (S32): before CI's own WASM build overwrites it, run the existing parity harness against the *committed* `manifold/public/nisps.{js,wasm}` so a stale committed artifact fails loudly. (The same artifact is what the VPS webhook ships to production.) +5. **Deploy gating** (critic gap 3, operator decision §7.4): today push-to-main deploys to `meml.lnfinitemonkeys.org/next/` with no gate at all. Cheapest real fix is VPS-side (webhook checks the commit's CI status before building); repo-side minimum is documenting the risk in `manifold/ONBOARDING.md`. + +## §2 Phase 1 — Dead-mass deletion sweep (~2–3 days, one commit per group) + +All verifier-checked deletions; protected exceptions noted. Rough net effect: thousands of lines removed with zero live-behaviour change. + +- **Repo root / hygiene**: retired-playground dist + root Playwright rig + root `package.json`/lockfile/`node_modules` (S23, S29, L55); `NISPS_CORE_EXTRACTION_PLAN.md` + `NISPS_CORE_TASKS.md` (L48/L36); `data/` (L36); committed `.claude/worktrees/` fragment (L32); prune stale `worktree-*` branches. +- **nisps core/ml**: `fixed_buffer.hpp` + its test (L27); `dislike_multiplier_` (L26); `copy_weights_to(span)` to drop `flat_` from FixedStorage and the per-gesture double copy (L28); perf-macro regime — delete the 3/5 dead SRAM macros, fix the one misshapen `NISPS_AUDIO_FUNC` use in `midi_io.hpp` (S21, L13, closes old ALIGNMENT #4). The 16 KB loss-history buffer (L25) waits on the telemetry decision (§7.3). +- **engines/modes**: `voice_space.hpp` (L3); no-op VoiceSpace boilerplate on the five engines without real voice spaces (L9); `SawOsc`/`SquareOsc` — **keep `SineOsc`**, the selftest uses it (L4); `input_dirty_` (L5); VerbFX dead fields/setters (L6); MEMLCelium inert feedback path after checking the upstream app for a missing write (L7). `DriverConfig` (S4) waits on §7.2. +- **wasm bridge**: dead C-API entries through the full 5-layer chain — **keep** `EXPORTED_RUNTIME` `cwrap`/heaps (parity + wasm-load tests build their API via `Module.cwrap`) (S33); `publishWeights_` 200 Hz weight-copy channel (S34); worklet loader dead scaffolding + throwing import stubs (L54). +- **manifold UI**: dead focus/altitude system — SplitStage, ReadoutStrip, InputMini, AltitudeNav, CompactAxis, keep MiniMeters and **don't touch `engine.feedback.setFocus`** (S15); decorative controls — A/B, fake seed/gradient, snapshots, master volume, bpm, training-param sliders per verifier notes (S16, absorbs L1's delete-half); `BackendAdvanced.tsx` duplicate editor table (S18); ConsoleCtx prune to consumed fields (S19); 5 dead primitives (L22); dual backend catalogues (L23); inert soloMode selector + FeedbackController vestiges (L20, L21). +- **backends**: dead protocol legs — sendState/sendWeights, legacy array format, unreceivable `/nisps/state`, unused module-output listeners (L16); delete `bridge.mjs`, keep `bridge.ts` + compiled binaries as distribution (S11). +- **vcv**: `vcv/test/` + `Makefile.dist` + tracked ELF (L33). +- **firmware**: daisysp — 41 dead .cpp per build, delete vendored tree + symlink, fix docs that call it a submodule (S8); `input_router.hpp` zero-logic layer (ST2); peripherals dead declarations + extracted `commit_and_train` helper (L14). + +## §3 Phase 2 — Behaviour bugs (~2 days) + +- **S35** Dual example store: name `kDefaultMaxExamples = 128` once in nisps, expose via `nisps_ml_describe`, align the TS mirror (currently 100) — fixes `train()`/`trainAsync()` diverging past 100 examples and the latent OOB sample-weight read. +- **S10** VCV bridged mode truncates inputs to 2-D: `EngineApi.inputVector()` returns the spine's full N-dim raw vector; VcvBackend tracks/dead-zones the full length. +- **L34/L35** VCV plugin RT-safety: worker deep-copies example vectors into staging before flagging; move full-state JSON off Rack's audio thread onto the existing worker (reuse the staged-weights pattern). +- **L18** MIDI input flooding React per CC message → notify only on binding-list changes; **L19** BackendManager dropping a switch requested mid-switch; **L24** ConsoleApp global-listener effects re-subscribing every render. +- **S30** lint-cpp: cover `nisps/pipeline/` + `core/`, strip comments before matching, extend the heap pattern set — the no-heap gate currently has proven false-negative modes; **L52** parity-check.sh unreachable FAIL branch under `set -e`. +- **L51** parity runner header/stage-list drift (4-stage/v1 documented vs 7-stage/v5 real). + +## §4 Phase 3 — Single-source-of-truth consolidation (~2–3 days) + +- **S1** Mode identity: codegen emits an `ALL_MODE_SCHEMAS` registry; `SCHEMA_MODES` becomes a mode_id→display-overlay map (labels/glyphs stay legitimately hand-curated); **L37** delete the hand-`switch`ed `modeEngineId` (route on schema `engine_id`, single named exception for sound_analysis_midi). +- **S5** codegen emits per-mode `kXSchema` ParamSchema constants (deletes 9 hand-written 12-field blocks); **S6/S25** MLP template args come from generated constexpr dims (or `static_assert` parity), killing the hand-typed-dims dual truth; **L11** ext-synth ML config folds into the same pipeline. +- **S26** Schema surface honesty: per-field wire-or-delete pass (default_learning_rate/max_iterations both-platforms-or-neither; input_channels; per-param curves) per the verifier's corrected list. +- **L38** one TS `applyCurve` (mapping.ts's spec-anchored formula survives); **ST4** one GROUP_COLOR source; **L8** extract shared `ratio_seq`/SeqClock/EventQueue for the sequencer engines; **L17** BaseBackend mirroring BaseSource for status/throttle plumbing; **ST12** shared codegen `lib.ts`; **L39** delete the clobbering seed script (git keeps it); **ST13** move `synth-midi-cc.json` under `schemas/midi_devices/`. + +## §5 Phase 4 — PlatformIO migration (vision 4) (~2–3 days) — A6, S2, L12, S9 + +One cut, no dual path. `firmware/useq-celium/` already proves the PIO + arduino-pico pattern in-repo. + +- `platformio.ini` with one `[env]` per firmware variant passing `-DMEMLNAUT_MODE_TYPE=`; selftest becomes a plain `-DNISPS_SELFTEST=1` env. Deletes: the sed/python machinery in `firmware-common.sh` that **mutates the committed .ino**, the `.ino` comment-registry, the entire `NISPS_ST_*` token-paste table (already silently missing the currently-active SLPWorkshop variant — L12), the sketch-tree symlink forest, and the global TFT_eSPI library mutation (handled via PIO lib config/build flags instead). +- **memllib consumption decision** (§7.5, depends on Phase 0.1): PIO `lib_deps` pin on the fork vs vendoring the actually-used subset. Prerequisite task: inventory which memllib surface (AudioDriver, MEMLNaut board, display/menu, MIDI) is load-bearing. +- Then **S9**: a CI job compiling 2–3 representative envs with cached toolchain — firmware enters an automated gate for the first time. + +## §6 Phase 5 — Vision-facing architecture (each item spec-first, own session) + +- **5a Mode-layer reunification (A1, A5).** The verified shape: do *not* bind monolithic mode objects into WASM and do not delete the per-primitive C API (contradicts the locked P2/P3 architecture and the two-instance RT split). Instead storage-policy the ModeBase orchestration the way P2 did MLPCore, so control-tick behaviour (jolt stepping, OU, routing, event pump) exists once in C++ and the browser stops hand-mirroring it. Needs its own spec before code. +- **5b Browser mode coverage honesty (A2).** Add an audio-topology notion (generator / audio-in-fx / event-only / analysis) so Manifold stops cataloguing 4 modes that structurally cannot run; wire mic input for the audio-in class (absorbs old ALIGNMENT #1); event-only modes need transport/MIDI-out UI, or explicit "hardware-only" labelling. +- **5c Curated/advanced split (A3, A7).** Product model first (§7.6): what is a "curated preset" — schema + backend preset + input map + trained net? Then: instrument picker rendered from the already-plumbed `ctx.modes`/`setModeId` (engine reshape-on-switch already works); per-drawer depth levels as the disclosure mechanism rather than one global boolean; `backends/presets.ts` + schemas seed the data model (add the missing `cv` backend to presets — small bug from A3's verification). +- **5d Hardware editor (A4, S14).** The repo already contains the right discipline: `useq-celium`'s C-header wire-protocol truth + TS mirror + parity test. Apply it to a MEMLNaut USB-serial protocol; give firmware an actual command surface + on-device persistence; settings/training payloads derive from schema codegen, not hand-defined tables. `InputChain`/`OutputChain` firmware wiring (L29) lands here or gets its comment softened now. +- **5e Training-health telemetry — one decision, four fragments (critic gap 5; §7.3).** Either it's a feature (wire the already-plumbed `get_layer_stats` + a loss-history ABI to the advanced mode, delete the fakes) or it isn't (delete the 16 KB buffer L25, the fabricated gradient UI L1, and old ALIGNMENT #2). Decide once; stop maintaining all four limbs. +- **5f Performance measurement (critic gap 4).** A host-side blocks-per-second benchmark for `engine_process_block` (native + WASM), and `build-firmware.sh` emitting a per-variant flash/RAM size report — makes the headline constraint enforceable instead of vibes. + +## §7 Operator decisions needed + +1. **S20 legacy feedback modes** (RandomiseOutputs/RandomiseMlp/Diffuse/on_drag): deletion reverses the explicit "keep for A/B comparison" in `docs/adr/rl-feedback-design.md` — delete (and amend the ADR) or keep? +2. **S4 DriverConfig**: wire the firmware audio driver to read it at mode start (makes mic/line settings real) or delete the contract from the concept + all 8 engines. No dead middle. +3. **Telemetry** (§6.5e): feature or delete. +4. **Deploy gating** (§1.5): gate the webhook on CI, or accept ungated deploys knowingly. +5. **memllib ownership** (§5): fork-pin vs vendored subset vs upstreaming to MusicallyEmbodiedML. +6. **Curated-preset product model** (§6.5c): what a preset bundles; where curation lives. +7. **S12 inputs composition**: mix-and-match multi-source remains an unreversed 2026-06-28 decision with groundwork deliberately laid — schedule the UI, or keep dormant (spec relabelled either way). + +## §8 Docs/specs disposition (executes alongside phases; ~1 day) + +| Doc | Disposition | +|---|---| +| `aimmersive-clone-spec.md` | Archive (`_archive/`, deprecated-by note) — specs a retired product; strip chat residue (L42) | +| `feedback-modes-port-spec.md` | Reclassify `kind: plan, status: executed`; ADR + `feedback.hpp` are the truth (L43) | +| `plans/BUILD-PLAN.md` | `status: executed`; two-line survivors note (L44) | +| `plans/playground-2.0-rewrite-plan.md` | `status: superseded` (retired target) (S3) | +| `engine-architecture.md` | Excise dead playground2 sections; keep EngineApi-seam + spine contract with supersession header (S3, L45) | +| `MAIN.md` | Fix the ≥6 contradicted claims; update registry rows (S27) | +| `manifold-parity-features-spec.md` | Reclassify plan; mark Jolt/OU executed-by-other-means; presets/pins sections stay live feeding 5c (S28) | +| `vcv-module.md` + `vcv/README.md` + `NISPS-FORMAT.md` | Prune to the current 8→16 contract; one format spec, not two (S22, L46) | +| `inputs/backends/dock` spec trio | Mark grounding sections historical; fix dead cites; drop aimmersive counterpart links (L50) | +| `MAP.md` | Flatly-false lines fixed with this commit (phantom `MEMLCelium-upstream`, exploration.ts, daisysp "submodule", pre-P5 sentence, perf-attr "globally" claim); the rest moves with the phases that change the code (L2, L15/L31/L41, L49, ST7, ST15) | +| `AGENT-REFERENCE.md` | Pre-P5 sentences updated (L49) | +| `codegen/README.md` | Rewrite or shrink to a MAP pointer; drop `port-solidjs` CI trigger (ST10) | +| Root `NISPS_CORE_*` relics | Deleted in Phase 1 (L48) | +| `ALIGNMENT.md` | Rewritten with this commit (L30, L47, ST6 fold into their code phases) | + +## §9 Effort summary + +| Phase | Cost | Depends on | +|---|---|---| +| 0 Restore verification | ~half day | — (do first, includes the only data-loss risk) | +| 1 Dead-mass deletion | 2–3 days | 0 (green gates to delete against) | +| 2 Behaviour bugs | ~2 days | 0; independent of 1 | +| 3 Truth consolidation | 2–3 days | 1 (less mass to consolidate) | +| 4 PlatformIO | 2–3 days | 0.1 (memllib remote); §7.5 | +| 5 Vision builds | spec-first, weeks, incremental | 1–4 recommended; §7 decisions | +| Docs disposition | ~1 day | interleaved | + +Phases 1–3 are safe, mostly-mechanical, and could be largely agent-driven with the existing gates. Phase 4 is bounded and proven in-repo. Phase 5 is where the product decisions live — each item deserves its own spec + session, in the order 5c → 5a → 5d (curation is the user-visible payoff; reunification derisks everything mode-shaped; the editor closes the hardware loop). diff --git a/docs/specs/recon/simplification-audit-2026-07.md b/docs/specs/recon/simplification-audit-2026-07.md new file mode 100644 index 0000000..3dc4ce0 --- /dev/null +++ b/docs/specs/recon/simplification-audit-2026-07.md @@ -0,0 +1,298 @@ +--- +kind: finding +date: 2026-07-21 +immutable: true +--- + +# Simplification Audit 2026-07 — full-repo smell/bloat/spec review vs the one-core vision + +_Dated 2026-07-21. Method: a 66-agent workflow — 11 parallel subsystem reviewers (ml-core, dsp-engines-modes, wasm-bridge, manifold-ui, manifold-backends-inputs, firmware, schemas-codegen, specs-docs, tests-build, satellites-hygiene, architecture-gap) → adversarial verification (per-finding skeptics for architectural/structural claims, batch spot-checks for local/style) → a completeness critic. 113 findings survived, 0 were refuted outright, 22 were downgraded/corrected by verifiers. Yardstick: the operator's five-bullet target vision (2026-07-20): (1) one C++20 NISPS core serving RP2350 firmware and the browser, performance-sensitive on the MCU; (2) firmware modes runnable as modes in Manifold; (3) curated-presets default with a maximalist "advanced" dev mode; (4) PlatformIO, no more .ino; (5) Manifold as interface/editor for the hardware. Actionable mitigations are phased in `../plans/simplification-plan.md`; the strategic tier is promoted into `ALIGNMENT.md` (both updated in the same commit as this finding)._ + +## Headline + +1. **CI has been 100% red on main since 2026-07-13** and nothing noticed. The memllib submodule pin (`b37fc53`, branch `feat/nisps-core-swap`) is reachable from **no remote** — GitHub's `submodules: recursive` checkout fails before any gate runs, so every P4–P6 "gates green" claim rests on local runs only, and fresh clones cannot build firmware. Verified live via `gh run list` (all recent main runs `failure`) and `git branch -r --contains b37fc53` (empty). +2. **Those three memllib commits exist only on one disk.** A single disk failure loses firmware-critical code. (The `w1n5t0n` fork remote exists but does not contain them.) +3. **Production deploys are gated by nothing.** Push to main → VPS webhook → live at `meml.lnfinitemonkeys.org/next/`, with CI red and the committed `manifold/public/nisps.wasm` having no freshness gate — what users run is whatever was last committed, unverified. +4. **Real behaviour bugs surfaced**, not just smells: the TS example-store mirror caps at 100 while the C++ store caps at 128, so `train()` and `trainAsync()` silently train on different datasets past 100 examples (plus a latent OOB read on sample weights); VCV bridged mode truncates the input vector to 2-D against a 32-input head; the VCV plugin has an audio-thread data race and does full-state JSON on the audio thread. +5. **The five-bullet vision has zero footprint in ALIGNMENT.md or the spec corpus** — nothing records PlatformIO, curated/advanced, or the hardware-editor role as goals, so nothing was steering toward them. +6. **No performance measurement exists anywhere** — the headline "performance-sensitive" constraint is enforced entirely by static discipline (no heap/virtual, section attrs) and vibes; no benchmark, no size report, no CPU-load assertion on either target. + +The codebase itself is in **better shape than its vibe-coded provenance suggests**: the `nisps/` core's storage-policy split is justified and parity-pinned, hot paths are clean, the CRTP mode scaffold genuinely centralises, the backend/input contracts are uniform. The dominant rot is *vestigial mass* (dead subsystems, decorative UI, executed-plan specs presented as live contracts, hand-maintained registries that codegen should own) — most fixes are deletions. + +## Area verdicts + +**ml-core** — The ml-core area (nisps/core, nisps/ml, nisps/pipeline) is genuinely one of the healthier parts of this codebase: the MLPCore policy split is justified (two real consumers — zero-heap firmware FixedStorage, arena-allocated browser/VCV DynamicStorage — sharing one algorithm body, pinned by a bit-parity test), hot paths are heap-free, virtual-free, .f-consistent, and RNG is deterministic per instance. Jolt/OU/replay/geo_push are small, inert-by-default, and cleanly ported. The rot is concentrated in vestigial baggage rather than wrong abstractions: feedback.hpp carries two whole legacy […] + +**dsp-engines-modes** — The dsp/engines/modes stack is in better shape than the vibe-coded provenance suggests: the DSP primitives are lean, faithful ports with no maximilian/daisysp vestiges beyond honest attribution comments; all 8 engines have real consumers on both targets; the CRTP ModeBase genuinely centralizes orchestration (inputs, jolt, OU, events) rather than hiding duplication. The biggest gap versus the vision is architectural: the modes/ layer is consumed ONLY by firmware — the WASM bindings include engines but zero mode headers and re-assemble mode behavior (jolt stepping, OU, routing) in bindings.cpp […] + +**wasm-bridge** — The wasm-bridge is in better shape than its history suggests: the P3 'SWAP POINT' duplication is genuinely resolved (jolt.ts/ou-explore.ts are deleted; exploration.ts is now a thin timer-driver over the shared C++ core), the flat C API in bindings.cpp is stylistically coherent (consistent null-guards, grouped, documented wire layouts), and worklet messaging is RT-acceptable (postMessage between render quanta, fixed-size param buffer, no allocation inside process() beyond trivial subarray views). The real problems are accretion and dead mass: roughly a dozen API entries exist across a 5-file […] + +**manifold-ui** — The manifold UI is functional and the engine wiring (FeedbackController → shared C++ core, backend manager, input layer) is genuinely live, but the console layer carries heavy sediment from its window-global JSX ancestry: a dead four-way "focus/altitude" system whose only reachable branch is 'composite' (SplitStage, ReadoutStrip, InputMini, AltitudeNav all unreachable), a large stratum of decorative state that renders real-looking controls doing nothing (learning-rate/decay/spread/tame sliders, master volume, bpm, A/B toggle, snapshots, health/gradient visuals), and a ConsoleCtx god-object […] + +**manifold-backends-inputs** — This is one of the cleaner vibe-coded areas: the OutputBackend contract is genuinely uniform (start/send/teardown/status with in-backend throttle+dead-zone), mapping.ts is a single shared truth, presets.ts is real and consumed by OutputsBackendConfig (not a stub), and midi-devices/ is proper schema→codegen with one import path. The real debts are: (1) the VCV bridged-training path is pinned to an obsolete 2-D input assumption while the engine has a 32-input head and the spine already stores the full N-dim raw vector — so VCV mode silently truncates gamepad/MIDI inputs; (2) the OSC bridge […] + +**firmware** — The firmware C++ glue itself is in decent shape — it is thin (~1.4k lines), template-based with no virtual dispatch in the audio path, the SPSC/dual-core discipline is respected, and peripherals.hpp/selftest.hpp are unusually well-commented and readable for vibe-coded work. The rot is concentrated in the build system, which is exactly what vision bullet 4 targets: the Arduino-CLI toolchain forces a symlink forest, a build script that mutates the committed .ino (variant flips pollute git history), a triple-bookkept mode list (the .ino comment block, mode_select.hpp aliases, and a […] + +**schemas-codegen** — The schemas→codegen pipeline is one of the better-built parts of this vibe-coded repo: both generators validate via ajv, emit deterministic/idempotent output, and the generated C++ is genuinely sane (inline constexpr arrays, string_views, .f literals, no heap — spot-checked against the perf contract). But the "schemas are the single source of truth" story is only about half true in practice: net dimensions are hand-duplicated in every C++ mode's MLP template args with no compile-time tie back to the generated constants (browser reshapes from schema, firmware uses the hand-written dims — the […] + +**specs-docs** — The corpus has an unusually good ontology (spec/plan/finding/ADR split, _archive dir, front-matter kinds) and MAP.md is ~90% accurate and current — but the July one-core refactor (P4–P6, landed 2026-07-14/18) rotted the registry layer faster than the doc-sync discipline caught it. MAIN.md, the corpus entry point, now makes at least six false or self-contradictory claims; two 2026-06-era specs describe the RETIRED SolidJS playground world as present tense; both plans that are done still carry status:active in ways the corpus's own ontology forbids (BUILD-PLAN) or defensibly (one-core, gated on […] + +**tests-build** — The test estate is in genuinely good shape at its core: 42 focused C++ test files with a documented hand-rolled harness, a 7-stage native-vs-WASM parity blob, committed golden baselines (impulse, ML vectors), fixed↔dynamic storage parity, VCV-adapter parity, and post-P4 pipeline-golden fixtures whose provenance and re-baselining are exemplary — the fixtures are meaningful, not frozen accidents. The problems cluster at the edges: an entirely dead root-level Playwright rig left over from the retired playground (config points at an HTML file that no longer exists anywhere in the tree, and […] + +**satellites-hygiene** — The satellites are in better shape than their paperwork. Post-P6, vcv/src/iml.hpp genuinely is a thin adapter over MLPCore (pinned bit-exact by tests/cpp/test_vcv_iml_parity.cpp, wired into CMake), and keeping the module in-repo is coherent since it builds against ../../nisps directly; the one defensible wart is the insertion-order example mirror (features_/labels_), which carries a named requirement (JSON persistence + cross-thread staging) and is acceptable. firmware/useq-celium is the cleanest corner of the repo: I byte-verified the protocol.h ↔ useq-protocol.ts mirror […] + +**architecture-gap** — Scored against the 5-bullet vision: (1) "one C++ core" is ~80% real — the algorithm layer (ML, feedback, jolt/OU, pipelines, curves) genuinely lives once in nisps/ after the P0–P6 refactor, but the MODE layer does not: nisps/modes/ never compiles to WASM, so the browser recomposes engine+ML in TS and every ModeBase behaviour (event ring, voice spaces, note_on/BPM, analysis feature-splicing, pinning) is firmware-only. (2) Consequently "firmware modes runnable in Manifold" is ~40%: 4 of the 9 catalogued browser modes are non-functional (sequencers emit events nobody drains; audio-in modes have […] + + +## Completeness-critic gaps + +_A final agent asked "what did the audit miss?". Verdict: file-tree coverage was strong; the systematic blind spot was everywhere the repo touches the outside world._ + +**CI has been entirely red on main since 2026-07-13 and no auditor noticed.** Verified live: `gh run list` shows 8/8 recent runs on main FAILED, spanning every P3–P6 push (2026-07-14 through 2026-07-18). Cause confirmed from run 29641902005: the cpp-tests job dies at the Checkout step — 'remote error: upload-pack: not our ref b37fc53…' — because ci.yml uses `submodules: recursive` and the memllib gitlink was re-pinned (commit 43ef6d5, 2026-07-13) to a commit that exists on no remote. The vcv-plugin workflow fails identically. The firmware area found the unreachable pin ('fresh clones cannot build firmware') and tests-build criticized CI *content* drift, but the compound fact is worse than either: the entire C++ gate […] + +**The unpushed memllib fork is a data-loss single point of failure, and its ownership question is unasked.** The three commits on memllib's local `feat/nisps-core-swap` branch (bf9691c 'swap memlp includes for nisps/core, inline RL utilities', 32cc831 newlib RNG fix, b37fc53 seed-helper rename) are NISPS-load-bearing source that exists ONLY in this working copy — `git ls-remote` of MusicallyEmbodiedML/memllib shows no ref containing them, and the operator has no fork remote configured. One disk failure loses firmware-critical code. The audit framed this purely as a build blocker; the trivial mitigation (push the branch to a monkey-w1n5t0n/w1n5t0n Forgejo or GitHub fork and repoint .gitmodules) was never proposed. The deeper unasked question matters […] + +**The production deploy pipeline was audited by nobody — live deploys are gated by nothing.** manifold/ONBOARDING.md:43 documents it: push to GitHub main → VPS webhook → builds manifold/ → rsyncs to the live meml.lnfinitemonkeys.org/next/. Three consequences no area examined: (a) deploys do not wait for CI — and CI is red anyway (gap 1), so every P4–P6 push went straight to the live site with zero automated gate between a nisps/ change and production; (b) the webhook builds only manifold/, so vite copies the COMMITTED manifold/public/nisps.wasm into dist — the 'stale artifact, no freshness gate' finding that tests-build scoped to browser tests actually extends to what users run in production; (c) the webhook config itself (on the […] + +**No performance measurement exists anywhere, though 'super performance-sensitive on the microcontroller' is the vision's first bullet.** Every area checked perf DISCIPLINE statically (no heap, no virtual, .f literals, section attrs) but nobody asked how the project would ever KNOW the audio path meets budget or regressed: grep across scripts/ and tests/cpp/ finds zero benchmark harness, no cycle/CPU-load assertions, no blocks-per-second smoke for engine_process_block (native or WASM), and no firmware flash/RAM budget report in any gate — the 16 KB dead loss-history buffer was found by reading, not by a size report that would have caught it mechanically. The only perf scaffolding is on-device (src/memllib/utils/perf.hpp, included by the .ino) with no automated consumer. A […] + +**Training-health telemetry is one product decision fragmented into four uncoordinated findings.** Four areas each hold one limb of the same feature: ml-core flags the 16 KB loss-history buffer in every firmware MLP (nothing reads it); specs-docs verifies wasm-worker.ts:304 fakes a 1-element lossHistory; manifold-ui flags the health/gradient visuals as decorative; architecture-gap notes the real get_layer_stats API is plumbed end-to-end and unused. Nobody asked the single question that resolves all four: IS training-health telemetry a feature of this instrument? If yes (plausible — 'interactive control visualisation' is in vision bullet 5 and 'advanced mode as preset-authoring dev tool' in bullet 3 wants training introspection), the fix […] + +**The osc-bridge release workflow already names the surviving bridge — and two of three workflows were reviewed by nobody.** tests-build audited only ci.yml, but .github/workflows/ contains three workflows. osc-bridge.yml (tag-triggered) deno-compiles ONLY manifold/osc-bridge/bridge.ts into cross-platform release binaries — that is the named external consumer that settles the manifold-backends 'two hand-maintained bridge implementations' finding: bridge.ts has a release pipeline, bridge.mjs has no consumer in any workflow, so the mjs twin is the deletion candidate. vcv-plugin.yml was also unreviewed: it fails on the same unreachable submodule pin as ci.yml (verified in the 2026-07-18 run), and still triggers on stale feat/manifold-mission / feat/vcv-dist branch […] + + +## Findings inventory + +_`confirmed` = an adversarial verifier re-read the cited code and the claim held; `downgraded` = the claim held but severity or fix needed correction (fix shown is the verifier's). IDs are referenced from `../plans/simplification-plan.md`; evidence lives at the cited lines._ + + +### Architectural (8) + +**A1. Mode layer is not shared: nisps::modes never compiles to WASM; Manifold rebuilds modes in TS** (architecture-gap, downgraded, week+) +`nisps/wasm/bindings.cpp:54`, `nisps/modes/base.hpp:113`, `manifold/src/console/model.ts:199`, `manifold/src/engine/spine.ts:1` +bindings.cpp includes nisps/engines/* and nisps/ml/* but nothing from nisps/modes/ (includes at lines 54-77; engine dispatch at 267-275 instantiates bare engines). grep for events/voice_space/note_on/update_bpm in bindings.cpp returns nothing. So ModeBase behaviours — initial inference at setup […] +*Fix:* Reunify mode BEHAVIOUR in C++ without pretending the browser can host one monolithic mode object: (1) storage-policy ModeBase exactly as P2 did for MLPCore — ModeBase, ...> with Fixed for firmware (zero-heap intact) and Dynamic for WASM (heap at construction only), so browser modes stay runtime-reshapeable; (2) split the mode C ABI along the […] + +**A2. Manifold catalogues 4 modes that structurally cannot work in the browser** (architecture-gap, downgraded, days) +`manifold/src/console/model.ts:206`, `nisps/engines/breakor.hpp:123`, `manifold/src/engine/engine-host.ts:1`, `manifold/src/engine/worklet/nisps-processor.ts:287` +SCHEMA_MODES (model.ts:199-221) lists all 9 schema modes including breakor, elysiamorf (sequencers: engine audio is silence; their whole output is pop_events, breakor.hpp:123-128) and xiasri, sound_analysis_midi (audio-in). But: (a) no WASM binding drains engine events and no manifold code […] +*Fix:* Immediate (hours, manifold-only): add a manifold-side audio-topology map keyed by engineId in model.ts — 'generator' (paf_synth, memlcelium→also slp_workshop, elysiamorf? no: event-only) vs 'audio-in-fx' (channel_strip, verb_fx, xiasri) vs 'event-only' (breakor, elysiamorf) vs 'analysis' (sound_analysis_midi) — and surface it as a 'firmware-only'/'needs audio-in' badge on […] + +**A3. No curated/advanced split exists; Manifold is 100% dev-maximalist with no preset data model to author against** (architecture-gap, downgraded, week+) +`manifold/src/settings/settings-store.ts:24`, `manifold/src/backends/presets.ts:30`, `manifold/src/console/ConsoleApp.tsx:105` +Grep for advanced/curated across manifold/src finds only BackendAdvanced.tsx (per-backend transport settings) — no global mode toggle. settings-store.ts:24-40 holds only cosmetic prefs (icon colour, input-map shape, corner radius). The only preset machinery is presets.ts: per-BACKEND output-routing […] +*Fix:* Split into three differently-sized items. (a) NOW, small bug: add `cv` to OutputPresetRow, rowsFromParams, and applyPreset in manifold/src/backends/presets.ts (three one-line additions). (b) IF/WHEN the curated-default direction is actually adopted, first record it in ALIGNMENT.md (it currently contradicts open question Q2) — then build the unified preset artifact by EXTENDING […] + +**A4. Hardware-editor (vision 5) is a facade: serial stub in the UI, zero firmware command surface, zero on-device persistence** (architecture-gap, downgraded, week+) +`manifold/src/serial/memlnaut-serial.ts:102`, `manifold/src/console/Drawers.tsx:590`, `firmware/MEMLNaut-NISPS/MEMLNaut-NISPS.ino:126` +memlnaut-serial.ts saveModel/restoreModel/getSettings are explicit no-op stubs (lines 102-132: 'TODO: real protocol', returns false/null/{}), and git log shows manifold/src/serial untouched since the June initial commit. EditorPanel is shipped live in the Settings drawer (Drawers.tsx:590) with […] +*Fix:* Drop the short-term step — the 'not wired' badge already exists in EditorPanel.tsx:88-94; at most soften the dock description in output-mode.ts:79 ('configure / save / restore — protocol not yet implemented'). Real path stands as proposed: spec the USB-serial protocol the useq-celium way (shared versioned header under firmware/, mirrored TS + frame-size unit test), add a […] + +**A5. modes/ layer has one consumer: WASM bindings bypass it and re-orchestrate modes from engines + ml primitives** (dsp-engines-modes, downgraded, days) +`nisps/wasm/bindings.cpp:54`, `nisps/wasm/bindings.cpp:844`, `nisps/modes/base.hpp:311`, `firmware/MEMLNaut-NISPS/glue/mode_select.hpp:41` +bindings.cpp includes all 8 engine headers (lines 54-62) and ml/jolt.hpp, ml/ou_noise.hpp directly, but not a single nisps/modes/*.hpp; only firmware glue (mode_select.hpp:41-50) consumes the mode classes. bindings.cpp:844-851 (nisps_ml_jolt_step) is a line-for-line duplicate of […] +*Fix:* Do NOT bind the mode classes into WASM and do NOT delete the per-primitive jolt/OU C API — both contradict the locked one-core-engine architecture (runtime-shaped browser MLP, P2; primitives-via-C-ABI, P3) and the two-WASM-instance RT split. Instead: (1) collapse the one true code duplication by moving the copy→morph→writeback sequence into the shared primitive — e.g. a […] + +**A6. Arduino-CLI/.ino build machinery: build mutates committed source, triple-bookkept mode list, symlink forest — PlatformIO migration deletes ~400 lines of hackery** (firmware, confirmed, days) +`scripts/firmware-common.sh:192-208`, `scripts/firmware-common.sh:298-337`, `firmware/MEMLNaut-NISPS/MEMLNaut-NISPS.ino:37-59`, `firmware/MEMLNaut-NISPS/glue/mode_select.hpp:90-106` +Mode selection lives in THREE hand-synced places: the .ino comment block (lines 38-54) that firmware-common.sh greps (load_firmware_variants:192-208), the mode_select.hpp using-aliases (54-85), and the NISPS_ST_* macro table (90-104). set_firmware_variant (298-337) runs an inline Python script that […] +*Fix:* Migrate to PlatformIO in one cut, no dual path: platformio.ini with maxgerhardt/platform-raspberrypi + board_build.core=earlephilhower (the FQBN solderparty_rp2350_stamp_xl proves the variant exists in arduino-pico; verify the PIO board def or add a small custom board JSON). One [env] per firmware variant with build_flags = -std=gnu++20 -DMEMLNAUT_MODE_TYPE= (keep […] + +**A7. No curated-default/advanced split and no in-UI instrument picker — UI structure fights the core vision** (manifold-ui, downgraded, days) +`manifold/src/console/ConsoleApp.tsx:355`, `manifold/src/console/ConsoleApp.tsx:61`, `manifold/src/settings/settings-store.ts:24`, `manifold/src/console/Drawers.tsx:1` +The vision requires curated presets/modes by default with maximalist settings behind an 'advanced' gate. Settings (settings-store.ts:24-38) contains only monochromeIcons/unfocusedIconColour/inputMap/cornerRadius — no advanced flag exists anywhere. The default surface is the full five-drawer console […] +*Fix:* Render the instrument-mode picker from the already-plumbed ctx.modes/setModeId (pure UI; engine reshape-on-mode-switch already works per P5; keep the window.__mf seam for e2e). Drop the global advancedMode boolean: progressive disclosure is already designed and partly built as the per-drawer depth ladder (dock-spec §0 peek/expand/FULL with FULL as the advanced page). If more […] + +**A8. The operator's target vision (PlatformIO, curated-presets default vs advanced mode, Manifold as hardware editor) has zero footprint in ALIGNMENT or the spec corpus** (specs-docs, confirmed, hours) +`ALIGNMENT.md:48`, `docs/specs/MAIN.md:140`, `MAP.md:150`, `manifold/src/serial/memlnaut-serial.ts` +No spec, plan, ALIGNMENT defect, or open question mentions PlatformIO anywhere (grep -ri platformio docs/ MAP.md ALIGNMENT.md hits only firmware/useq-celium, which already uses it — proving the toolchain is in-house viable). MAP.md:150 codifies the .ino/Arduino-CLI flow as a Convention; ALIGNMENT […] +*Fix:* Add three dated ALIGNMENT entries (PlatformIO migration — killing the .ino sketch-tree symlink hacks MAP.md:161 documents; curated-presets/advanced-mode product model — promoting Q2 from question to defect and linking the pending manifold-parity presets plan; hardware-editor role for manifold/src/serial). Specs follow when each is picked up. + + +### Structural (35) + +**S1. Mode identity is ~6 hand-maintained registries with demonstrated drift, not one source of truth** (architecture-gap, downgraded, day) — `manifold/src/console/model.ts:199`, `manifold/src/console/model.ts:324` + Adding a mode touches: schema JSON (canonical) + hand C++ mode (legit) PLUS (a) mode_select.hpp using-alias, (b) the NISPS_ST_* zero-table, […] *Fix:* Keep points (1), (4), (5) as proposed: codegen an ALL_MODE_SCHEMAS array (TS-side codegen template change; SCHEMA_MODES becomes a mode_id→overlay map, keeping hand label/glyph/cls/ordering, which are legitimately display truth); collapse […] + +**S2. Vision 4 (PlatformIO) not started; the .ino build machinery is actively hostile (symlink farm, sed-mutates-source mode selection, macro undef dance)** (architecture-gap, downgraded, days) — `scripts/firmware-common.sh:311`, `firmware/MEMLNaut-NISPS/glue/mode_select.hpp:19` + Mode selection = build scripts regex-rewriting the .ino's #define line in place (firmware-common.sh:194-205 grep/sed discovery, :311 python […] *Fix:* Migrate firmware/MEMLNaut-NISPS to one platformio.ini with an env per variant passing -DMEMLNAUT_MODE_TYPE= (grep/sed/python rewrite machinery, the .ino comment registry, and the NISPS_ST_* SelfTest grep-discovery tag all deleted; […] + +**S3. Two 'authoritative' specs still prescribe the retired SolidJS/playground2 world** (architecture-gap, downgraded, hours) — `docs/specs/engine-architecture.md:38`, `docs/specs/aimmersive-clone-spec.md:1` + engine-architecture.md (kind: spec — 'wins over code by intent' per MAIN.md §0) mandates building in a fresh `playground2/` tree with […] *Fix:* One commit, three coordinated edits. (1) engine-architecture.md: do not flatten to one page — excise the dead sections (§1 playground2 tree, §3 reuse-vs-rebuild, §4 two-entry Vite/nginx serving, §5 S0–S7, stale §6 open questions) and […] + +**S4. DriverConfig is a dead contract: required by the AudioEngine concept, implemented by all 8 engines, consumed by nobody** (dsp-engines-modes, confirmed, day) — `nisps/core/concepts.hpp:62`, `nisps/core/types.hpp:59` + Repo-wide grep for driver_config/DriverConfig finds only the struct (types.hpp:59), the concept requirement (concepts.hpp:62), and the 8 […] *Fix:* Decide once: either wire the firmware audio driver to read the active mode's engine().driver_config() at mode start (makes the mic/line settings real), or delete DriverConfig, the concept line, and all 8 implementations. Do not keep the […] + +**S5. Per-mode kSchema aggregates are hand-written 12-field boilerplate that codegen should emit** (dsp-engines-modes, confirmed, hours) — `nisps/modes/breakor.hpp:76`, `nisps/modes/memlcelium.hpp:48` + Nine mode headers each contain a mechanically identical positional ParamSchema aggregate wiring 12 generated constants (kXModeId, […] *Fix:* Move struct ParamSchema into generated/schema_types.hpp and have codegen emit 'inline constexpr ParamSchema kXSchema{...}' per mode; each mode's param_schema() becomes a one-line return of the generated constant. Delete the 9 hand-written […] + +**S6. MLP topology is a dual source of truth: hand-typed template args vs generated hidden_layers** (dsp-engines-modes, confirmed, hours) — `nisps/modes/memlcelium.hpp:28`, `nisps/modes/generated/memlcelium_schema.hpp:19` + Every mode hardcodes its net shape (e.g. ml::MLP<4u,10u,14u,18u,56u> at memlcelium.hpp:28) while the generated header carries the same dims […] *Fix:* Have codegen emit 'using MemlceliumMLP = ::nisps::ml::MLP<4u,10u,14u,18u,56u>;' (or a static_assert block comparing MLP dims to kXHiddenLayers) into each generated header, and make modes use the alias. One source of truth, compile-time […] + +**S7. memllib submodule pin is unreachable from any remote — fresh clones cannot build firmware** (firmware, confirmed, hours) — `.gitmodules:1-3`, `scripts/build-firmware-arch.sh:74-79` + git submodule status → b37fc53 (heads/feat/nisps-core-swap). Inside src/memllib, `git branch -r --contains HEAD` returns EMPTY — the pinned […] *Fix:* Push feat/nisps-core-swap to the monkey-w1n5t0n/memllib fork and change the .gitmodules URL to the fork (or upstream the changes to MusicallyEmbodiedML and re-pin). Then delete the compensating error-message paragraph in […] + +**S8. daisysp is fully dead weight: 41 .cpp files compiled into every firmware build with zero consumers, and docs mislabel it a live submodule** (firmware, confirmed, hours) — `firmware/MEMLNaut-NISPS/src/daisysp`, `nisps/dsp/pitch_shift.hpp:3` + grep for daisysp across firmware glue, the .ino, nisps/, and the symlinked memllib subdirs (audio/hardware/interface/synth/utils) finds […] *Fix:* Delete the firmware/MEMLNaut-NISPS/src/daisysp symlink and the vendored src/daisysp tree; correct MAP.md, firmware/README.md, and setup-firmware-toolchain.sh in the same commit. (Answers the focus question: yes, the granular pitch-shift […] + +**S9. Firmware compilation is in no automated gate: CI, run-all-tests.sh, and lint all skip firmware/** (firmware, confirmed, day) — `scripts/run-all-tests.sh`, `.github/workflows/ci.yml:14-15` + grep 'firmware' scripts/run-all-tests.sh → no hits. ci.yml comments that 'firmware build is documented as a manual […] *Fix:* After the PlatformIO migration, add a CI job running `pio run -e slpworkshop -e selftest -e pafsynth` with a cached toolchain (arduino-pico core download is the only heavy step). Pre-migration stopgap: a weekly/manual-dispatch arduino-cli […] + +**S10. VCV bridged mode truncates the input vector to 2-D against a 32-input engine head** (manifold-backends-inputs, downgraded, day) — `manifold/src/engine/engine-api.ts:264`, `manifold/src/backends/vcv-backend.ts:67` + engine-api.ts inputVector() returns `[this.spine.lastRawX, this.spine.lastRawY]` (a fresh 2-element array allocated on EVERY spine tick […] *Fix:* In manifold TS only: (1) change EngineApi.inputVector() to return spine.lastRawInputs (signature ArrayLike; document "live reused buffer — copy, don't retain"; VcvBackend.setInputVector already copies). (2) Make VcvBackend […] + +**S11. Two hand-maintained OSC bridge implementations (bridge.ts Deno + bridge.mjs Node)** (manifold-backends-inputs, confirmed, hours) — `manifold/osc-bridge/bridge.ts:1`, `manifold/osc-bridge/bridge.mjs:1` + bridge.mjs is not compiled output — it is a separate hand-written Node port (node:dgram + ws dependency vs Deno.listenDatagram; deno […] *Fix:* Delete bridge.mjs; keep bridge.ts as the single source and compile.sh binaries as the no-Deno distribution. Fix the spec line that calls .mjs 'compiled'. + +**S12. Inputs: spec promises source composition, code enforces exclusive single-source; composition machinery unreachable** (manifold-backends-inputs, downgraded, day) — `docs/specs/inputs-spec.md:16`, `docs/specs/inputs-spec.md:230` + inputs-spec.md §0 says the operator picks 'one source *or a combination*' and §223-230 specifies multi-source composition ('XY pad + a MIDI […] *Fix:* Do not prune the composition machinery or rewrite the spec to exclusive — mix-and-match is a recorded, unreversed operator decision (2026-06-28 memory; commit 9e59eb0 and docs/specs/recon/midi-gamepad-inputs-worklog.md both mark the […] + +**S13. Per-backend config side-channel + instanceof special-casing in the manager's hot path** (manifold-backends-inputs, downgraded, day) — `manifold/src/backends/manager.ts:69`, `manifold/src/backends/manager.ts:78` + The OutputBackend contract carries mappings/names via BackendContext, but each real backend ALSO needs an index-aligned parallel specs […] *Fix:* Two independent fixes, narrower than proposed. (a) Replace the per-tick instanceof (manager.ts:69) with an optional `setInputVector?(vec: ReadonlyArray): void` on OutputBackend, implemented only by VcvBackend — this alone removes […] + +**S14. MEMLNaut Editor (vision bullet 5) is a 237-line shell: sound connect lifecycle, zero protocol, and a third unshared Web Serial implementation** (manifold-backends-inputs, downgraded, week+) — `manifold/src/serial/memlnaut-serial.ts:102`, `manifold/src/serial/EditorPanel.tsx:72` + memlnaut-serial.ts is honestly labelled STUB: saveModel returns false, restoreModel returns null, getSettings returns {} (lines 102-133); […] *Fix:* When bullet 5 is scheduled: (1) define the wire FRAMING as a C header under firmware/ following the useq-celium discipline (header single source of truth → TS mirror → parity test asserting frame sizes/offsets) — this part of the original […] + +**S15. Dead focus/altitude system: SplitStage, ReadoutStrip, InputMini, AltitudeNav, CompactAxis are unreachable** (manifold-ui, confirmed, hours) — `manifold/src/console/ConsoleApp.tsx:105`, `manifold/src/App.tsx:78` + ConsoleApp.tsx:105 `const [focus, setFocus] = useState(initialFocus)` — setFocus is never called anywhere in the repo (grep […] *Fix:* As proposed (delete SplitStage.tsx, ReadoutStrip.tsx, InputMini.tsx, AltitudeNav, CompactAxis, the UI Focus type/prop, the focus branches + stripPinned + vacuous keyboard gates in ConsoleApp, and the barrel exports; keep MiniMeters), with […] + +**S16. Decorative/fake controls: training-param sliders, master volume, bpm, A/B toggle, snapshots and health/gradient visuals drive nothing** (manifold-ui, downgraded, day) — `manifold/src/console/ConsoleApp.tsx:113`, `manifold/src/console/Drawers.tsx:242` + Verified by grep: learningRate/decay/tame/spreadLevel (ConsoleApp.tsx:126,149-151) are read ONLY by their own Drawers.tsx:242-255 sliders — […] *Fix:* Deletion-first, with three corrections. (1) Delete the A/B machinery (ab/aRef/setHoldingA/toggleAB, the VerdictCluster props, and the '+ A/B toggle' header comment), the fake seed, seededGradient + rev/weightsRevision, the unconsumed […] + +**S17. VCV backend UI is unreachable: no OutputMode maps to BackendId 'vcv'** (manifold-ui, confirmed, hours) — `manifold/src/console/output-mode.ts:40`, `manifold/src/console/types.ts:27` + OutputMode type (types.ts:27) is 'particles'|'midi'|'osc'|'cv'|'synth'|'editor' and OUTPUT_MODES (output-mode.ts:40-83) maps these to […] *Fix:* Decide which: add `{ id: 'vcv', backend: 'vcv' }` to OUTPUT_MODES + OutputMode + MODE_ICON (three small edits) to make the path live, or delete VcvConfig/VcvChannelEditor/forwardVcvFeedback/vcv ctx state. The first is likely intended given […] + +**S18. Per-backend config editors duplicated in two files and BOTH rendered in the same expanded drawer** (manifold-ui, confirmed, hours) — `manifold/src/console/Drawers.tsx:646`, `manifold/src/console/Drawers.tsx:677` + BackendAdvanced.tsx:8-9 self-describes as 'the full-depth duplicate of the inline config in OutputsBackendConfig; both write the same […] *Fix:* Delete BackendAdvanced.tsx and the Drawers.tsx:674-679 block (keep SynthGroupNote inline in Drawers if its one paragraph is wanted). OutputsBackendConfig already covers every backend. + +**S19. ConsoleCtx god-object: ~25 of its fields have zero consumers outside ConsoleApp** (manifold-ui, downgraded, day) — `manifold/src/console/types.ts:56`, `manifold/src/console/ConsoleApp.tsx:789` + Grep across console/, dock/, serial/ (excluding ConsoleApp/types): ctx.modes, setModeId, axes, setAxis, preset, setPreset, offsetActive, […] *Fix:* Prune ConsoleCtx to the 57 fields Dock/Drawers/OutputsBackendConfig actually read (plus mode). Delete outright: the Axes type + axes/setAxis state, the preset/setPreset/offsetActive chain (permanently 'Sculpt'/false), and the dead ctx […] + +**S20. Dead legacy feedback modes: RandomiseOutputs, RandomiseMlp, AvoidStyle::Diffuse, on_drag have no product consumer** (ml-core, downgraded, day) — `nisps/ml/feedback.hpp:59-73`, `nisps/ml/feedback.hpp:258-271` + The only product callers of the feedback controller select two behaviours: firmware sets only FeedbackMode::ExploreAndPlace […] *Fix:* Treat as a spec-governed decision, not dead-code removal. First surface to the operator: deleting AvoidStyle::Diffuse reverses the explicit "keep for A/B comparison" decision in docs/adr/rl-feedback-design.md, and deleting […] + +**S21. perf.hpp SRAM-section macro regime is mostly dead; the one real NISPS_AUDIO_FUNC use is shape-mismatched; docs claim discipline that doesn't exist** (ml-core, confirmed, hours) — `nisps/core/perf.hpp:26-35`, `firmware/MEMLNaut-NISPS/glue/midi_io.hpp:72` + Repo-wide grep: NISPS_AUDIO_MEM and NISPS_APP_SRAM are used ZERO times outside perf.hpp and comments (the .ino uses memllib's own AUDIO_MEM […] *Fix:* Delete NISPS_AUDIO_MEM/NISPS_APP_SRAM/NISPS_AUDIO_FUNC from perf.hpp; change midi_io.hpp:72 to plain 'inline void ...' (or Pico's __not_in_flash_func(name) applied correctly); rewrite MAP.md:151/164 and drop ALIGNMENT #4. If SRAM placement […] + +**S22. All vcv/ user-facing docs describe the pre-P6 nisps-core-era module (2×12, v1 3D weights, dead paths)** (satellites-hygiene, confirmed, hours) — `vcv/README.md:3`, `vcv/README.md:31` + README.md:3 says '2 inputs … 12 outputs' (module is 8×16 per MEMLNaut.cpp:16-17); README.md:31+265 and BUILDING.md:29+73 reference […] *Fix:* Rewrite README.md's numbers/paths (small diff), delete the playground-sharing sections, and either rewrite NISPS-FORMAT.md against the v3 flat layout or delete it and point at docs/specs/vcv-module.md (which the /specs corpus already owns) […] + +**S23. Retired-playground build output and its root E2E harness still tracked on main** (satellites-hygiene, confirmed, hours) — `playground/dist/index.html`, `playground/dist/nisps.wasm` + git ls-files shows playground/dist/* (1.2 MB: bundled JS+sourcemaps, a second nisps.wasm and nisps.js) tracked, though the playground app […] *Fix:* git rm -r playground/ tests/e2e/ package.json package-lock.json playwright.config.js; delete root node_modules and the playground dir from disk. The archive branch already preserves the app; nothing else names these paths. + +**S24. CI never runs codegen — the 'schema changes require regenerated outputs in the same change' gate is unenforced** (schemas-codegen, confirmed, hours) — `.github/workflows/ci.yml:24`, `scripts/run-all-tests.sh:51` + ci.yml's two jobs (cpp-tests, manifold-tests) contain no codegen step at all. The golden test runs only in local run-all-tests.sh stage 5, […] *Fix:* Add a CI step (in the manifold-tests job, where bun is already set up, before typecheck): `cd codegen && bun install --frozen-lockfile && bun run generate.ts && bun run generate-midi-devices.ts && cd .. && git add -N nisps/modes/generated […] + +**S25. MLP net dimensions are a dual source of truth: hand-written template args in every C++ mode vs schema dims used by the browser** (schemas-codegen, confirmed, hours) — `nisps/modes/paf_synth.hpp:31`, `nisps/modes/memlcelium.hpp:28` + All 9 modes hand-write dims, e.g. paf_synth.hpp:31 `ml::MLP<4u, 10u, 10u, 14u, 33u>` duplicating schemas/modes/paf_synth.json ml {4, […] *Fix:* Use the generated constexpr constants as the template args directly — they are valid constant expressions today: ml::MLP […] | +| L9 | dsp-engines-modes | Dead no-op VoiceSpace boilerplate on five engines | `nisps/engines/breakor.hpp:37` | Delete the VoiceSpace enum/table/setter from the five engines with zero real voice spaces; keep them only on PAFSynth, VerbFX, ChannelStrip. ModeBase […] | +| L10 | dsp-engines-modes | VerbFXEngine recomputes the full 47-param voice-space mapping every audio sample (and its […] | `nisps/engines/verb_fx.hpp:77` | Run smoothing + voice-space mapping at a shared control rate (e.g. every 16-32 samples, same divisor on both targets so native↔WASM parity holds), […] | +| L11 | dsp-engines-modes | ExternalSynthMIDIMode hand-rolls its schema and ML config outside codegen | `nisps/modes/external_synth_midi.hpp:132` | Fold the ext-synth ML config into the schema/codegen pipeline (a shared 'ext_synth' base schema emitting the constants), or at minimum define the […] | +| L12 | firmware | NISPS_ST_ selftest token-paste table silently out of date: SLPWorkshop (the currently active […] | `firmware/MEMLNaut-NISPS/glue/mode_select.hpp:90-106` | Fold into the PlatformIO migration: delete the entire NISPS_ST_ table and the SelfTestRig tag type, and select the selftest build with […] | +| L13 | firmware | midi_io.hpp uses the misshapen NISPS_AUDIO_FUNC macro that MAP.md and ALIGNMENT.md both claim […] | `firmware/MEMLNaut-NISPS/glue/midi_io.hpp:72` | Apply ALIGNMENT #4 option A: make NISPS_AUDIO_FUNC(name) decorate a function NAME on both targets, update the handful of use sites (midi_io.hpp here; […] | +| L14 | firmware | peripherals.hpp: dead declarations, duplicated commit-and-train block, and the documented […] | `firmware/MEMLNaut-NISPS/glue/peripherals.hpp:67` | Delete kAnalogInputCount, PeripheralBindings, and the unused spread local; extract one commit_and_train(mode, feedback) helper used by both branches; […] | +| L15 | firmware | MAP.md documents a vendored firmware/MEMLCelium-upstream/ directory that does not exist on […] | `MAP.md:30-31` | Delete the MAP.md entry (or, if the snapshot lives untracked on another machine and is genuinely wanted, commit it — but name the consumer first; the […] | +| L16 | manifold-backends-inputs | Dead protocol legs from the retired playground: sendState/sendWeights, legacy array format, […] | `manifold/src/backends/osc-client.ts:116` | Delete sendState/sendWeights, the legacy-array branch in both bridges (or the one surviving bridge), […] | +| L17 | manifold-backends-inputs | Status/throttle boilerplate duplicated across all four real backends while inputs/ already has […] | `manifold/src/backends/midi-backend.ts:163` | Extract a small BaseBackend (status plumbing + throttle gate + lastSent management), mirroring BaseSource. Pure deletion-by-refactor, no behaviour […] | +| L18 | manifold-backends-inputs | MIDI input floods React with per-CC-message state updates and snapshot allocations | `manifold/src/inputs/midi-input-source.ts:224` | Only call notifyBindings on binding-LIST changes (learn capture, clearBinding, clearAllBindings); if the drawer wants live values, read them on the […] | +| L19 | manifold-backends-inputs | BackendManager.setActive silently drops a switch requested while another is in flight | `manifold/src/backends/manager.ts:131` | Store the latest requested id when switching is true and re-run setActive with it in the finally block (3-4 lines). | +| L20 | manifold-ui | Solo-mode selector offers three behaviours but soloMode is behaviourally inert | `manifold/src/console/Drawers.tsx:142` | Collapse to a single non-selectable label until train_masked lands in the C API, or disable the two unimplemented options with a 'pending core […] | +| L21 | manifold-ui | FeedbackController vestiges: unused seed/undoDepth options, never-called engine-interface […] | `manifold/src/feedback/controller.ts:57` | Delete seed/undoDepth options and maxUndo; trim ControllerEngine to the members actually called; change dislike to (output: Float32Array) and update […] | +| L22 | manifold-ui | 5 of 12 design primitives are dead (Panel, StatusLine, ControlAxis, CurvePlot, Sparkline) | `manifold/src/primitives/index.ts:1` | Delete the five unused primitives (Sparkline/CurvePlot may return with the deferred diagnostics suite — re-add then). Optionally promote […] | +| L23 | manifold-ui | Dual backend catalogues: BACKENDS duplicates OUTPUT_MODES; OutputControl/toOutputControl dead | `manifold/src/dock/output-state.ts:40` | Delete OutputControl/toOutputControl and ModeIconComponent; delete BACKENDS and use modeDesc.label/description in Drawers (or derive BACKENDS from […] | +| L24 | manifold-ui | ConsoleApp global-listener effects run without dependency arrays — re-subscribed on every […] | `manifold/src/console/ConsoleApp.tsx:684` | Hold the verdict handlers in refs (or useEffectEvent) and give both effects [] / [inputs] deps, matching Manifold's existing onMoveRef pattern. | +| L25 | ml-core | 16 KB loss-history buffer baked into every firmware MLP instance with zero product consumers | `nisps/ml/storage.hpp:200` | Decide plumb-or-delete now. Smallest: delete lh_/loss_hist_buf/loss_history()/loss_history_count_ (train already returns final loss) and the […] | +| L26 | ml-core | dislike_multiplier_ is upstream-InterfaceRL bookkeeping that drives nothing | `nisps/ml/feedback.hpp:406-407` | Delete dislike_multiplier_, its update/halving logic, the accessor, and the test line — or, if the upstream behaviour it was meant to drive is […] | +| L27 | ml-core | nisps/core/fixed_buffer.hpp is dead code | `nisps/core/fixed_buffer.hpp:1-77` | Delete fixed_buffer.hpp, test_fixed_buffer.cpp, the CMake/ctest entry, and the MAP.md:8 mention. Trivially revivable from git if a consumer ever […] | +| L28 | ml-core | get_weights() forces a permanent whole-net scratch copy (flat_buf) and double-copies on every […] | `nisps/ml/storage.hpp:199` | Add a copy_weights_to(std::span dst) (and set from span, which already exists) and switch feedback.hpp's take_snapshot/push_undo/nudge to it; […] | +| L29 | ml-core | OutputChain's firmware story is aspirational — pipeline chains have browser-only consumers | `nisps/pipeline/output_chain.hpp:12-13` | Either wire InputChain/OutputChain into the firmware control tick (peripherals/ModeBase) as the named vision-5 requirement, or soften the header […] | +| L30 | satellites-hygiene | ALIGNMENT.md declares the VCV module dead while vcv/ is actively maintained | `ALIGNMENT.md:72` | Delete the ALIGNMENT.md:72 entry (the module now DOES consume nisps/ directly — the entry's own 'if revived' condition is met and closed). While […] | +| L31 | satellites-hygiene | MAP.md documents firmware/MEMLCelium-upstream/ which does not exist on disk or in any git ref | `MAP.md:30` | Delete the MAP.md:30 entry (the port provenance is already captured in ALIGNMENT.md's geometric-dislike entry and nisps/ml file headers; upstream […] | +| L32 | satellites-hygiene | Agent-worktree fragment committed to main (.claude/worktrees/agent-ae87fe47/…) | `.claude/worktrees/agent-ae87fe47/playground/js/shapeseq […]` | git rm -r .claude/worktrees/ (the ignore rule then holds for the future). Also consider pruning the 30+ stale worktree-agent-*/worktree-wf_* local […] | +| L33 | satellites-hygiene | vcv/test/ is dead: smoke test includes a nonexistent header, tests the retired 2×12 shape, and […] | `vcv/test/smoke_test.cpp:6` | git rm -r vcv/test/ and vcv/Makefile.dist (update BUILDING.md's two references). In osc_server.hpp, delete the unreachable reply-to-sender branch and […] | +| L34 | satellites-hygiene | Data race: audio thread reads imlShadow's example vectors while the worker may be mutating them | `vcv/src/MEMLNaut.cpp:514` | Have the worker deep-copy features/labels into pending staging buffers (alongside pendingWeights, e.g. under stagingMutex) BEFORE setting […] | +| L35 | satellites-hygiene | Full-state JSON serialize/parse runs on Rack's audio thread | `vcv/src/MEMLNaut.cpp:474` | Move JSON build/parse to the existing worker thread: stage the raw string in/out with the same atomic-flag pattern already used for oscStagedJson, […] | +| L36 | satellites-hygiene | Dead root planning docs and dead data/ directory from retired eras | `NISPS_CORE_EXTRACTION_PLAN.md` | git rm both NISPS_CORE_* files (history preserves them; docs/specs/plans/ is the sanctioned home for plans) and git rm -r data/. If the CSVs matter […] | +| L37 | schemas-codegen | modeEngineId switch in manifold hand-duplicates schema engine_id and silently drops new modes […] | `manifold/src/console/model.ts:324` | Delete the switch; route on mode.engineId with the single named exception (sound_analysis_midi → 'analysis', or add an optional analysis-engine […] | +| L38 | schemas-codegen | Two divergent TS applyCurve implementations for the per-output curve knob | `manifold/src/console/model.ts:278` | Pick one formula (mapping.ts's, which is spec-anchored and has a sane 0.5=linear midpoint), delete the other, and re-export the survivor; update […] | +| L39 | schemas-codegen | seed-midi-devices.ts silently clobbers the canonical hand-tunable device schemas it created | `codegen/seed-midi-devices.ts:11` | Delete the script (its purpose is served; it lives in git history and synth-midi-cc.json keeps provenance), or make it skip existing files. Drop the […] | +| L40 | schemas-codegen | Mode schema content is partially placeholder: 20 params across paf_synth/channel_strip/xiasri […] | `schemas/modes/paf_synth.json:16` | Accept as dated debt but record it (ALIGNMENT.md entry): name the placeholder slots during the first preset-curation pass per mode, or shrink […] | +| L41 | specs-docs | MAP.md documents a phantom vendored firmware (firmware/MEMLCelium-upstream/) that exists […] | `MAP.md:30` | Delete the MAP.md:30 bullet (or, if the snapshot exists untracked on another machine and is genuinely wanted, commit it — but the smallest fix is […] | +| L42 | specs-docs | aimmersive-clone-spec.md specs a retired product (SolidJS clone of a-immersive) and still […] | `docs/specs/aimmersive-clone-spec.md:7` | Move to docs/specs/_archive/ with deprecated-by front matter (or reclassify kind: finding, dated, noting it describes the deployed vanilla app); […] | +| L43 | specs-docs | feedback-modes-port-spec.md is an executed port plan masquerading as an evolving spec, with […] | `docs/specs/feedback-modes-port-spec.md:6` | Archive it (kind: plan, status: executed, deprecated-by: docs/adr/rl-feedback-design.md + code). The ADR + feedback.hpp are the surviving truth; […] | +| L44 | specs-docs | BUILD-PLAN.md still status:active though executed, with stale locked-decision facts | `docs/specs/plans/BUILD-PLAN.md:3` | Flip to status: executed, add a two-line 'what survives' note (non-snap-node gotcha → MAP), update the MAIN registry row. | +| L45 | specs-docs | engine-architecture.md's 'One Engine, Two Skins' framing is superseded but the file carries no […] | `docs/specs/engine-architecture.md:7` | Add a supersession header pointing at one-core-engine-refactor §0, retitle/trim to what remains authoritative: the browser-side EngineApi seam […] | +| L46 | specs-docs | vcv-module.md is a three-era palimpsest whose 'retained for reference' sections state stale […] | `docs/specs/vcv-module.md:169` | Prune pass: delete the 2→12-era architecture/prerequisite/Phase-0 text (git keeps it), keep the current 8→16 contract + threading/persistence […] | +| L47 | specs-docs | ALIGNMENT.md staleness: dead VCV deferred-debt entry, mitigation pointing at a deleted file, […] | `ALIGNMENT.md:72` | Delete :72; repoint #5's source reference to upstream MusicallyEmbodiedML memlp (or the archive branch); prune the 2026-04-29 resolved entries. | +| L48 | specs-docs | 818 lines of dead nisps-core extraction relics at repo root | `NISPS_CORE_EXTRACTION_PLAN.md` | git rm both files (history retains them; the executed outcome is already narrated in ALIGNMENT's resolved log). | +| L49 | specs-docs | MAP.md and AGENT-REFERENCE.md still carry pre-P5 'modes slice the default 126 outputs / TS […] | `MAP.md:155` | Update the three sentences to past-tense schema-real reality (or delete the convention bullet — MAP's manifold section already states it correctly). | +| L50 | specs-docs | backends/dock/inputs spec trio grounds itself in a deleted world (playground paths, a-app.js […] | `docs/specs/inputs-spec.md:14` | One pass across the three files: mark the audit/grounding sections as historical ('as of 2026-06-27, pre-refactor'), fix the handful of dead-path […] | +| L51 | tests-build | Parity runner headers document 4 stages / blob v1 while the code implements 7 stages / v5 | `tests/cpp/parity_check.cpp:14-45` | Rewrite both file headers' payload/stage lists to match the v5 blob (or replace the duplicated prose lists with a one-line pointer to the stage […] | +| L52 | tests-build | parity-check.sh FAIL branch is unreachable under set -e | `scripts/parity-check.sh:18` | Wrap: `if node "$TESTS_DIR/parity_diff.mjs" ...; then echo PASS; else status=$?; echo FAIL; exit $status; fi` — or delete the dead branch and let the […] | +| L53 | wasm-bridge | fetch + indirect-eval glue loading duplicated in two files; build could emit an ES module […] | `manifold/src/engine/wasm-iml.ts:56` | Add `-sEXPORT_ES6=1` (and `-sENVIRONMENT=web,worker` stays) to build-wasm.sh, replace both eval blocks with a single shared `await import(/* […] | +| L54 | wasm-bridge | Worklet WASM loader carries dead scaffolding and a silent-stub failure trap | `manifold/src/engine/worklet/nisps-processor.ts:104` | Delete the unused `imports` object, the 'c' branch, `exMap`, and the duplicate second loop. Replace the generic `() => 0` stub with a throwing stub […] | +| L55 | wasm-bridge | Retired playground's dist/ build artifacts still tracked in git | `playground/dist` | git rm -r playground/dist and remove the leftover untracked directory; add playground/ to .gitignore or delete the folder entirely. | + +### Style (15) + +| ID | Area | Finding | Where | Fix | +|----|------|---------|-------|-----| +| ST1 | dsp-engines-modes | Engine header comments describe implementations that don't exist (doc drift cluster) | `nisps/engines/breakor.hpp:4` | Rewrite the four comment blocks to describe the actual code; drop stream-plan tense. | +| ST2 | firmware | input_router.hpp is a zero-logic speculative layer with one consumer | `firmware/MEMLNaut-NISPS/glue/input_router.hpp:18-21` | Delete input_router.hpp; have the .ino call bind_peripherals directly (or rename bind_peripherals to wire_inputs inside peripherals.hpp). Drop the […] | +| ST3 | manifold-backends-inputs | Stale comments/docs: 'inputs-spec.md is absent', 'fixed WASM head = 2', 'Editor → BackendId' | `manifold/src/inputs/types.ts:10` | Delete/correct the three claims; one small comment pass. | +| ST4 | manifold-ui | GROUP_COLOR maps duplicated in four files and keyed on obsolete JSX-era group names | `manifold/src/console/shared-ui.tsx:83` | One shared map in model.ts (or a hash-to-palette function over arbitrary group strings, which would actually work for schema groups); delete the […] | +| ST5 | manifold-ui | Minor prop/doc lint: duplicate onReroll/onRandomise, unused CompositeStage mode prop, probe […] | `manifold/src/console/ConsoleApp.tsx:1092` | Collapse to one onReroll prop; drop the mode prop; rewrite the probe header to describe the actual surface. | +| ST6 | ml-core | feedback.hpp header comment asserts the retracted 'geometric push NOT ported' claim, […] | `nisps/ml/feedback.hpp:8-12` | Rewrite the header's mode summary to match the four current modes and the Geometric default (fold into the dead-modes deletion commit). | +| ST7 | ml-core | MAP.md and math.hpp still describe the pre-P3/P4 world (interim TS jolt/OU, playground curve […] | `MAP.md:79-81` | Update MAP.md:79-81 to describe exploration.ts as a thin timer-driver over the WASM core, and replace math.hpp's playground-mirror paragraph with the […] | +| ST8 | satellites-hygiene | License story is self-contradictory: MPL-2.0 repo, 'proprietary' VCV plugin manifest | `LICENSE:1` | Set plugin.json license to MPL-2.0 (or GPL-3.0-or-later per VCV convention) and drop the 'in spirit' hedge in iml.hpp — one licence, stated once. | +| ST9 | schemas-codegen | schema.json's own description states the direction of truth backwards | `schemas/schema.json:5` | Rewrite the description sentence: schemas are the canonical contract; codegen emits C++ and TS; provenance notes live in params_notes.md. | +| ST10 | schemas-codegen | codegen/README.md is wholesale stale (playground paths, phantom temp-dir, resolved stream-1 […] | `codegen/README.md:6` | Rewrite codegen/README.md against current reality (or shrink it to a pointer at MAP.md §codegen); drop port-solidjs from ci.yml triggers. | +| ST11 | schemas-codegen | codegen/templates/ are dead 'reference' files already drifting from the real emitters | `codegen/templates/cpp_schema.hpp.template:4` | Delete codegen/templates/ and the README line pointing at it. | +| ST12 | schemas-codegen | Duplicated helper block across the two codegen scripts, justified by a false rationale comment | `codegen/generate-midi-devices.ts:17` | Extract a tiny codegen/lib.ts with the shared helpers and correct the comment (keep both drivers). Or just fix the comment and accept the 40 lines. | +| ST13 | schemas-codegen | synth-midi-cc.json lives at repo root; its only machine consumer is the seed script | `synth-midi-cc.json:1` | Move to schemas/midi_devices/synth-midi-cc.sources.json (one-line path update in the seed script — moot if that script is deleted per the clobber […] | +| ST14 | tests-build | test_helpers.hpp documents NISPS_EXPECT as non-fatal but it throws; NISPS_ASSERT is a plain […] | `tests/cpp/test_helpers.hpp:10-11` | Smallest fix: correct the comment to say both are fatal and delete the NISPS_ASSERT alias (or keep the alias, one line). Only implement true […] | +| ST15 | wasm-bridge | Doc/comment drift lagging the P1–P4 refactors | `MAP.md:79` | One doc-sync pass: fix MAP.md:79, rewrite the nisps/wasm/README paragraph and either complete or delete its API table (point at bindings.cpp as the […] | \ No newline at end of file