docs(audit): simplification audit 2026-07 — recon findings, phased plan, ALIGNMENT rewrite
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.
This commit is contained in:
parent
fa3704765c
commit
fd0aee2354
5 changed files with 496 additions and 59 deletions
121
ALIGNMENT.md
121
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<Storage>`); 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<Storage>`; browser runtime-shaped, firmware zero-heap fixed).
|
||||
|
|
|
|||
19
MAP.md
19
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<DynamicStorage>`, 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<DynamicStorage>`, 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
|
||||
|
|
|
|||
|
|
@ -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`. |
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
115
docs/specs/plans/simplification-plan.md
Normal file
115
docs/specs/plans/simplification-plan.md
Normal file
|
|
@ -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=<alias>`; 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).
|
||||
298
docs/specs/recon/simplification-audit-2026-07.md
Normal file
298
docs/specs/recon/simplification-audit-2026-07.md
Normal file
|
|
@ -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<Storage> 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<DynamicStorage> (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<Derived, EngineT, MLCore<Storage>, ...> 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=<alias> (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=<alias> (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<number>; 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<number>): 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<Focus>(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<generated::kPafSynthMLConfig.input_size, generated::kPafSynthHiddenLayers[0], generated::kPafSynthHiddenLayers[1], […]
|
||||
|
||||
**S26. Large fraction of the schema surface has zero consumers — the single-source-of-truth claim is ~half freight** (schemas-codegen, downgraded, day) — `schemas/schema.json:45`, `codegen/generate.ts:195`
|
||||
Verified by repo-wide grep excluding generated dirs: (a) ml.input_channels names — read nowhere (input_router.hpp:4 admits it may 'grow […] *Fix:* Keep the per-field wire-or-delete structure, with three corrections. (1) default_learning_rate/default_max_iterations: wire BOTH platforms in one change or delete from both — if wiring, firmware glue passes […]
|
||||
|
||||
**S27. MAIN.md (corpus entry point) carries at least six claims the code or its own table contradicts** (specs-docs, confirmed, hours) — `docs/specs/MAIN.md:42`, `docs/specs/MAIN.md:67`
|
||||
(1) :67 'TS schema emission is dormant since P1; it returns at P5' — P5 landed 2026-07-18; codegen/generate.ts:8-12 writes […] *Fix:* Keep the original editing pass over docs/specs/MAIN.md (fix §1.2 superset claim per ALIGNMENT #1 — same eight nisps engines on both targets, C15 archived, mic input unwired; fix §2 to state TS emission is live at […]
|
||||
|
||||
**S28. manifold-parity-features-spec.md is part-executed, part-pending, but still labelled 'no implementation authorised' with ground-truth references deleted from main** (specs-docs, confirmed, day) — `docs/specs/manifold-parity-features-spec.md:9`, `docs/specs/manifold-parity-features-spec.md:20`
|
||||
Front matter: aspirational; body :9-11 'Status: draft — awaiting review... no implementation is authorised'. But its §0 principle 1 (bind […] *Fix:* Reclassify as kind: plan; mark §on Jolt/OU as executed-by-other-means (one-core P3, core bindings — its own preferred route); repoint playground references to the archive branch/tag; keep presets/pins/control-surface sections as the active […]
|
||||
|
||||
**S29. Root Playwright rig is dead: tests a page that no longer exists, invoked by nothing** (tests-build, confirmed, hours) — `playwright.config.js:13-22`, `package.json:1-13`
|
||||
playwright.config.js webServer serves cwd './playground' and testDir './tests/e2e'; tests/e2e/helpers.js:19 does […] *Fix:* Delete playwright.config.js, package.json, package-lock.json, and tests/e2e/ at the repo root; rm -rf root node_modules/, playwright-report/, test-results/ (already gitignored). No consumer exists: CI, run-all-tests.sh, MAP.md's entry […]
|
||||
|
||||
**S30. lint-cpp.sh heap audit has proven false-negative modes and never covers nisps/pipeline/** (tests-build, confirmed, hours) — `scripts/lint-cpp.sh:121`, `scripts/lint-cpp.sh:122`
|
||||
(1) Line 121 hardcodes subdirs dsp/engines/ml/modes — nisps/pipeline/ (the P4 control-rate hot path) and nisps/core/ are never scanned for […] *Fix:* In lint-cpp.sh: add "$NISPS_DIR/pipeline" and "$NISPS_DIR/core" to the subdirs array; replace the `grep -v ' *//'` post-filter with comment-stripping before matching (same perl strip already used by audit_float_suffix at lines 78-98); […]
|
||||
|
||||
**S31. CI re-implements run-all-tests.sh and has drifted: codegen golden test not gated in CI** (tests-build, confirmed, hours) — `.github/workflows/ci.yml:24-131`, `scripts/run-all-tests.sh:3`
|
||||
run-all-tests.sh:3 says 'Designed to be the single command CI invokes', but ci.yml never calls it — it re-lists the stages as discrete […] *Fix:* Add one step to the manifold-tests job (bun is already installed): `working-directory: codegen; run: bun install --frozen-lockfile && bun run tests/golden_test.ts`. Fix the false 'single command CI invokes' header comment in […]
|
||||
|
||||
**S32. Committed manifold/public/nisps.{js,wasm} has no freshness gate — all browser tests validate a potentially stale artifact** (tests-build, downgraded, day) — `.github/workflows/ci.yml:88-122`, `manifold/tests/wasm-load.ts:48-49`
|
||||
manifold/public/nisps.{js,wasm} are tracked build artifacts, last rebuilt/committed at 1672fe3 (P4). The manifold-tests CI job […] *Fix:* Replace the byte-diff with a toolchain-agnostic behavioral freshness gate: in the cpp-tests job, BEFORE the 'Build WASM' step (i.e. while manifold/public/ still holds the committed artifact), run the existing wasm parity harness against it […]
|
||||
|
||||
**S33. Dead API entries threaded through the full 5-layer registration chain** (wasm-bridge, downgraded, day) — `nisps/wasm/bindings.cpp:643`, `nisps/wasm/bindings.cpp:868`
|
||||
Grepped all of manifold/src, manifold/tests, e2e, and tests/cpp/parity_wasm.mjs (the WASM parity driver — I extracted its full list of […] *Fix:* Delete the dead entries at every layer as proposed, with two corrections. (1) EXPORTED_RUNTIME must remain ["HEAP32","HEAPU8","HEAPF32","cwrap"] — tests/cpp/parity_wasm.mjs (parity-check.sh gate) and manifold/tests/wasm-load.ts […]
|
||||
|
||||
**S34. publishWeights_ channel: full weight-vector copy at up to 200 Hz into a field nothing reads** (wasm-bridge, confirmed, hours) — `manifold/src/engine/wasm-iml.ts:1093`, `manifold/src/engine/wasm-iml.ts:929`
|
||||
publishWeights_() (wasm-iml.ts:1093-1096) runs getWeights() — a C→heap copy plus a fresh Float32Array allocation — then sink.setWeights, […] *Fix:* Delete EngineSink.setWeights, Spine.setWeights/weights()/liveWeights, and all publishWeights_ call sites (keep getWeights for persistence and the debug probe). If a weight-health view ever materialises, it should pull getLayerStats on […]
|
||||
|
||||
**S35. Dual example store with mismatched caps: train() and trainAsync() diverge past 100 examples; latent OOB read on sample weights** (wasm-bridge, downgraded, hours) — `manifold/src/engine/wasm-iml.ts:167`, `manifold/src/engine/dataset.ts:81`
|
||||
WasmIML keeps a TS Dataset mirror (default cap 100, wasm-iml.ts:167) AND pushes every example into the C++ FIFO ring (DynamicStorage […] *Fix:* Keep the cap-alignment core of the proposal: name the constant once in nisps (e.g. constexpr kDefaultMaxExamples = 128 shared by storage.hpp:55 and dynamic_storage.hpp:43 defaults), expose it through nisps_ml_describe (extend the dims […]
|
||||
|
||||
|
||||
### Local (55)
|
||||
|
||||
| ID | Area | Finding | Where | Fix |
|
||||
|----|------|---------|-------|-----|
|
||||
| L1 | architecture-gap | Console health UI shows fabricated gradient data while the real layer-stats API sits plumbed […] | `manifold/src/console/model.ts:305` | Delete seededGradient; wire the health readout to engine.getLayerStats() (already reactive via the version counter). Separately land the half-day […] *(verifier: All cited lines verify: seededGradient (model.ts:305-319) fabricates norms/statuses from the rev counter, is […])* |
|
||||
| L2 | architecture-gap | MAP.md drift: swap points and TS prototypes described as live are already swapped/deleted | `MAP.md:79` | Fix the two MAP.md entries (exploration = thin WASM wrappers, controller = thin driver, drop rng.ts and the phantom files). Delete the dead […] |
|
||||
| L3 | dsp-engines-modes | nisps/modes/voice_space.hpp is entirely dead code | `nisps/modes/voice_space.hpp:27` | Delete the file. |
|
||||
| L4 | dsp-engines-modes | Dead oscillators: SineOsc/SawOsc/SquareOsc have no consumers | `nisps/dsp/osc.hpp:28` | Delete the three classes; keep detail::kPi/kTwoPi, PAFOperator, FMOp. *(verifier: SawOsc and SquareOsc are genuinely dead, but SineOsc has a live consumer the reviewer's grep scope missed: […])* |
|
||||
| L5 | dsp-engines-modes | ModeBase::input_dirty_ is write-only state | `nisps/modes/base.hpp:165` | Delete the flag, or actually gate tick_control's inference on it (measure first; deletion is the safe default). |
|
||||
| L6 | dsp-engines-modes | VerbFXEngine dead fields and setters: delay_to_verb_ computed 12 times, never read; […] | `nisps/engines/verb_fx.hpp:242` | Delete delay_to_verb_, enable_reverb_, enable_delay_to_reverb_, and the unused set_enable_* setters (keep the ones a named consumer appears for). If […] |
|
||||
| L7 | dsp-engines-modes | MEMLCeliumEngine feedback path is inert: feedback_ never written in process() | `nisps/engines/memlcelium.hpp:180` | Check the original MEMLCeliumAudioApp: if it writes feedback, port the missing write; if not, delete feedback_, fbzm1_, fb_smooth_alpha_ and the […] |
|
||||
| L8 | dsp-engines-modes | Sequencer machinery duplicated across engines: ratio_seq, event queue, bar/MIDI-clock phasors, […] | `nisps/engines/breakor.hpp:178` | Extract dsp/ratio_seq.hpp (free function template), a small SeqClock struct (bar phasor + MIDI clock + counter + update_bpm), and EventQueue<E,64> […] |
|
||||
| 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<float> 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 […] |
|
||||
Loading…
Reference in a new issue