docs(recon): memllib usage inventory — the §7.5 prerequisite, with a wrinkle

Plan §5 names an inventory of the load-bearing memllib surface as the
prerequisite for the vendor-vs-fork-pin decision. Built it from the firmware
LINKER MAP rather than grep — the same method that would have caught daisysp
(S8) mechanically instead of by reading.

Result: all 24 compiled translation units contribute kept sections to the image;
no discarded-only TU exists. memllib is not a daisysp situation. "The
load-bearing subset" is ~1.8 MB across 84 files — essentially all of memllib
minus examples/. There is no small subset to lift; synth/ alone is 1.3 MB and
all three of its TUs link.

The wrinkle, which was not visible when §7.5 was decided: src/memllib is not a
third-party dependency, it is the LAB's shared library
(MusicallyEmbodiedML/memllib), and our fork is 3 commits ahead but 31 commits
BEHIND — +2034/-153 across exactly the files we link, including "new staticmlp",
"jolts", "noise", "verb". Vendoring as-is freezes that gap permanently.

Recommendation recorded: rebase the three small NISPS commits onto upstream main
FIRST, then vendor. Same self-contained end state, but it does not silently
discard work that sounds directly relevant, and it is the only moment that merge
is cheap — after vendoring there is no upstream history to merge against.

Flagged for an operator call before the PlatformIO cut, which this gates.
This commit is contained in:
monkey-w1n5t0n 2026-07-21 14:12:55 +02:00
parent b953681157
commit 7a30da91a8
2 changed files with 93 additions and 1 deletions

View file

@ -100,7 +100,15 @@ DESCRIPTIVE — it documents that engine voice spaces already square the value i
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.
- **memllib consumption decision** (§7.5): the prerequisite inventory is DONE —
`../recon/memllib-usage-inventory.md`, built from the firmware linker map rather than grep. Result:
all 24 compiled TUs are linked, so "the load-bearing subset" is ~1.8 MB / 84 files — essentially
all of memllib minus `examples/`. There is no small subset to lift. It also surfaced a
consequence that was not visible when §7.5 was decided: memllib is the LAB's shared library and
our fork is 3 commits ahead but **31 behind** (+2034/153 across the files we link, including
`new staticmlp`, `jolts`, `verb`). Vendoring freezes that gap permanently. Recommended: rebase the
three NISPS commits onto upstream `main` first, THEN vendor — it is the only moment that merge is
cheap. Operator call before the cut.
- Then **S9**: a CI job compiling 23 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)

View file

@ -0,0 +1,84 @@
---
kind: finding
date: 2026-07-21
---
# memllib usage inventory — the prerequisite for the §7.5 ownership decision
_Prerequisite named by `../plans/simplification-plan.md` §5: "inventory which memllib surface
(AudioDriver, MEMLNaut board, display/menu, MIDI) is load-bearing" before choosing between a
fork-pin and vendoring. The operator's §7.5 decision is **vendor the parts we use** ("everything
should be self contained in this repo"). This document is the evidence that decision needs, plus one
consequence that was not visible when it was made._
## Method
Not grep. The firmware was built for real (`arduino-cli`, `rp2040:rp2040:solderparty_rp2350_stamp_xl`,
SLPWorkshop variant) and the **linker map** was read: `MEMLNaut-NISPS.ino.map` distinguishes
translation units that merely compiled from those contributing sections to the final image. This is
the same method that would have caught the daisysp finding (S8) mechanically instead of by reading.
## Result: the compile surface is 24 TUs, and all 24 are linked
| subdir | .cpp linked / present | headers | size |
|---|---|---|---|
| `audio/` | 4 / 4 | 12 | 132K |
| `hardware/` | 7 / 7 | 21 | 176K |
| `interface/` | 8 / 8 | 8 | 108K |
| `synth/` | 3 / 3 | 10 | 1.3M |
| `utils/` | 2 / 2 | 9 | 80K |
| **total** | **24 / 24** | **60** | **~1.8M** |
Every translation unit reachable through the sketch-tree symlinks contributes kept sections to the
image — **no discarded-only TU exists**. memllib is therefore *not* a daisysp situation (41 .cpp
compiled, zero consumers). It is genuinely load-bearing.
The library has 30 `.cpp` in total; the 6 not counted above live outside the five symlinked subdirs
(e.g. `examples/`) and are never compiled by the firmware build. Those are the only part safe to
drop outright.
Firmware's direct include surface is just six entry points — `audio/AudioDriver.hpp`,
`hardware/memlnaut/MEMLNaut.hpp`, `hardware/memlnaut/Pins.hpp`,
`hardware/memlnaut/display/{View,SingleSelectView}.hpp`, `interface/MIDIInOut.hpp`, `PicoDefs.hpp`,
`utils/perf.hpp` — but the transitive closure is the whole of those five subdirs.
**Consequence for vendoring: "the load-bearing subset" is ~1.8 MB / 84 files, i.e. essentially all
of memllib minus `examples/`.** There is no small subset to lift. `synth/` alone is 1.3 MB (mostly
`maximilian.cpp`) and all three of its TUs link.
## The consequence that was not visible when §7.5 was decided
`src/memllib` is not a third-party dependency. It is **the lab's shared library**
(`MusicallyEmbodiedML/memllib`), and our fork is:
- **3 commits ahead**`bf9691c` (swap memlp includes for nisps/core, inline RL utilities),
`32cc831` (newlib RNG fix), `b37fc53` (seed-helper rename). These are the NISPS-specific changes.
- **31 commits behind** — including `770a990 new staticmlp`, `9fcd459 jolts`, `d0d8a72 noise`,
`616b8e7 cc select`, `a8bf255 verb`, `671abbe screen ordering`, `e291192 l r input swap`.
Diffed against the five subdirs we link: **30 files changed, +2034 / 153**.
Vendoring freezes that gap permanently and converts "we are behind the lab" into "we have our own
divergent copy". That may be exactly what is wanted — self-containment is a real goal, and the lab's
`main` is not obviously a branch we track — but it is a fork of a colleague-shared library, not a
snapshot of a vendor drop, and the 31 commits contain work (`staticmlp`, `jolts`) that sounds
directly relevant to this project.
## Options, with what each costs
1. **Vendor as decided.** Copy the five subdirs into the repo, drop the submodule and the symlink
forest, delete `examples/`. Repo grows ~1.8 MB. Self-contained, no submodule init, PlatformIO
gets a plain `lib/` — this materially simplifies the §5 migration. Cost: the 31 upstream commits
become a manual merge, forever.
2. **Vendor, but first rebase our 3 commits onto upstream `main`.** Same end state, except the
snapshot is current rather than 31 commits stale. Costs one merge now (the three commits are
small and mechanical), and it is the only moment when that merge is cheap.
3. **Fork-pin (status quo + Phase 0's fix).** Already works: the pin is reachable, CI is green,
fresh clones build. Not self-contained, and keeps the submodule friction PlatformIO would rather
not have.
**Recommendation: option 2.** The operator's goal (self-contained) is satisfied identically by 1 and
2, but 2 does not silently discard `staticmlp`/`jolts`/`verb`. Doing the rebase after vendoring
means doing it against a copy that no longer has upstream history — i.e. never.
This decision gates the PlatformIO migration (plan §5), which otherwise has to keep the submodule
and its symlink workaround.