One cut, no dual path. Closes ALIGNMENT defect 3 ("Arduino-CLI build
machinery is actively hostile") and vision bullet 4.
platformio.ini carries 16 [env:], one per variant, each passing
-DMEMLNAUT_MODE_TYPE; selftest passes -DNISPS_SELFTEST=1 instead. The env list
IS the registry now — the .ino comment-registry and the NISPS_ST_* token-paste
table are deleted rather than migrated. L12 noted that table was already
silently missing the currently-shipped SLPWorkshop variant, which is the whole
argument against having a second list.
Also deleted: the Python/sed machinery that rewrote the COMMITTED .ino on every
build, the sketch symlink forest, the global TFT_eSPI User_Setup.h mutation
(now -D flags — TFT_eSPI's own documented PlatformIO recipe), the UF2
boot-mount detection stack (upload_protocol=picotool talks to the bootloader
directly), and build-firmware-arch.sh entirely. Scripts 683 -> 435 lines.
memllib is vendored at lib/memllib/ from upstream e291192; no submodules
remain. VENDORED.md records provenance and the re-sync procedure.
S9: a firmware-build CI job compiles three representative envs against a cached
toolchain and reports per-variant flash/RAM. Firmware is in an automated gate
for the FIRST time. The old ci.yml comment justified excluding it as "low
verification value" — an assessment that did not survive contact, since the
SelfTest variant sat broken for an unknown period calling a DisplayDriver
method that did not exist at the pinned memllib commit, and nothing noticed
because nothing built it.
Verified: all 16 envs build from an empty cache, each within ~520 bytes of the
arduino-cli binary it replaces, flash and RAM. Measured as .text+.rodata /
.data+.bss+vector+uninitialized — NOT PlatformIO's console line, which
double-counts .data on this board. This does not prove the hardware boots; no
flash+smoke test was possible and that stays an operator chokepoint.
slpworkshop 248232/145028 pafsynth 256880/149716 selftest 216228/17960
(all 16 in the CI log format; none exceeds 2% of a 16 MB flash)
Two traps recorded so nobody rediscovers them: vendoring memllib's subdirs
without a src/ wrapper makes PlatformIO's library builder silently compile
NOTHING while still linking; and project build_flags land BEFORE the
framework's own -std=gnu++17 -Os, so build_unflags is required.
CORRECTION carried in this commit: the firmware sizes in c19d846's message and
the first version of the memllib recon doc were wrong — SLPWorkshop 145348,
PAFSynth 145300, SelfTest 141840. They came from building variants in sequence
through a SHARED incremental arduino-cli build directory, which reused stale
objects and under-reported by ~75 KB. Clean-cache rebuilds of the identical
commit give 216736/18492 for SelfTest. The real cost of the memllib upstream
bump is +216 bytes flash, not +316. Never measure firmware size through a
reused build dir.
HISTORY NOTE: this commit and the docs commit before it were rebuilt (force-push,
2026-07-21) so that each contains only what its message describes. The first
versions had the firmware deletions stranded in the docs commit by a shared-index
race between concurrent agents; content is byte-identical to the originals.
Gates: run-all-tests.sh ALL GREEN (nisps/ untouched by this change beyond
include paths); 16/16 pio envs build.
Phase 1 group 8 (S8, ST2, L14).
- S8: the vendored src/daisysp tree (96 files, 41 .cpp compiled into every
firmware build) and its sketch-tree symlink. Zero consumers — nisps replaced
daisysp's PitchShifter with a custom granular implementation. Corrected
firmware/README.md and setup-firmware-toolchain.sh, which called it a live
submodule. The attribution comment in nisps/dsp/pitch_shift.hpp stays: it is
an honest provenance note about a port, not a dependency.
Noted while verifying: src/memllib/examples/KassiaAudioApp includes
../../daisysp/..., but examples/ is not symlinked into the sketch tree and is
never compiled by the firmware build, so the deletion stands.
- ST2: input_router.hpp was a zero-logic speculative layer with one consumer;
the .ino now calls bind_peripherals directly.
- L14: peripherals.hpp — deleted kAnalogInputCount, PeripheralBindings and the
unused spread local, and extracted the duplicated commit-and-train block into
one helper. The audit's suggested commit_and_train(mode, feedback) signature
could NOT be behaviour-identical: repositioning() implies placing() (both
live in ExploreState::Placing), so dispatching on feedback state inside the
helper would silently reroute a TogB2 press mid-reposition from commit_place
to commit_reposition, which differs (no snapshot restore, clears
reposition_). Implemented as commit_and_train(mode, feedback, bool
reposition) with the branch decided at the call site, preserving behaviour.
Firmware is not compiled by any gate yet (that arrives with the PlatformIO
migration, plan §5/S9), so this group is verified by reading and grep only.
Gates: run-all-tests.sh ALL GREEN.
Move the Arduino sketch into firmware/MEMLNaut-NISPS/ and bridge the
hardware (memllib) to the platform-agnostic nisps/ library through a
slim glue layer. Delete the legacy root-level *AudioApp.hpp,
modes/MEMLNautMode*.hpp, voicespaces/, IMLInterface.hpp, XiasriAnalysis,
and the src/memlp submodule.
Glue layout (firmware/MEMLNaut-NISPS/glue/):
audio_driver.hpp - bridge memllib block callback to Mode::process
via per-Mode templated trampoline (no virtual dispatch)
peripherals.hpp - joystick/pots/buttons -> Mode::set_input + ML primitives
midi_io.hpp - MIDI in -> mode.note_on/update_bpm/set_playing,
drains mode ControlEvent ring -> MIDI UART
mode_select.hpp - using-aliases mapping MEMLNautMode<Name> to
nisps::modes::*Mode (build script rewrites the
#define MEMLNAUT_MODE_TYPE line)
input_router.hpp / output_router.hpp - top-level wire/drain entry points
The sketch tree uses src/{memllib,daisysp,nisps} symlinks because
Arduino-CLI rejects ".." in include paths from sketch-tree headers.
mode_select.hpp #undefs Arduino's sq/min/max/abs/round macros before
including nisps headers (some nisps engines use those identifiers as
method names). The audio bridge struct is extern in the header and
defined in the .ino because inline + __not_in_flash section attribute
collide at link time.
Verification: arduino-cli compile succeeds for PAFSynth, ChannelStrip,
and BreakOr (rp2040:rp2040:solderparty_rp2350_stamp_xl:opt=Optimize3,
-std=gnu++20). Host C++ tests under nisps/build still pass (3 binaries,
110+ tests). Build script (scripts/build-firmware.sh) updated to point
at the new sketch path; mode-rewrite logic unchanged.
Closes meml-gkm.