The §7.5 decision was "rebase our commits onto upstream, then vendor". On
inspection there was no rebase to do, and the inventory that recommended it
(7a30da9) was wrong on two counts — both corrected in this commit.
All three fork commits touch only `examples/`. That directory is not in the
sketch symlink forest (firmware/MEMLNaut-NISPS/src/ links exactly audio,
hardware, interface, synth, utils, PicoDefs.hpp), so it is never compiled.
They existed to let the RL code build against nisps/core *while it was being
ported*, and that port is done — nisps/ml/{jolt,ou_noise,feedback,geo_push}.hpp
cite the upstream sources directly. Two of the 31 commits I flagged as
"work we are missing", `9fcd459 jolts` and `d0d8a72 noise`, are precisely the
ones already absorbed.
So the question was never rebase-or-not but which snapshot to vendor. Current
upstream, because the staleness was already costing us:
- upstream has DisplayDriver::NavigateToView; the pin does not. The SelfTest
variant called it and had been failing to compile (worked around in
b953681). It was written against a newer memllib, not against nothing.
- e291192 "l r input swap" is a hardware bug fix: the physical L/R input
sockets are wired to opposite codec ADC channels. Every mode on the old
pin sees its stereo input backwards.
Verified by building all three variants with arduino-cli:
SLPWorkshop 145348 flash (+320) 87388 RAM (+4)
PAFSynth 145300 flash (+312) 107060 RAM (+4)
SelfTest 141840 flash (+320) 12028 RAM (+4)
Exactly one compile error: the .ino used kSampleRate in a constexpr, and
upstream 1997699 made it a runtime `extern size_t` so a mode can pick its own
rate. constexpr -> const; it is a once-per-second diagnostic print. The uniform
+316 is the AudioDriver/DisplayDriver deltas — the bulky new upstream code
(GrainDelayI16, ReverbI16, ModFXI16, CCSelectView, RLView, VUMeterView,
PSRAMManager) is header-only and unreferenced, so the linker drops all of it.
.gitmodules points at upstream again: with the pin on an upstream commit, the
fork holds nothing the firmware compiles. Phase 0 pointed it at the fork only
because b37fc53 existed on no remote. The fork's feat/nisps-core-swap branch
stays pushed; nothing is destroyed.
Refs: plan §5, §7.5; ALIGNMENT Q4.
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.
MAP.md: codegen's generated/ now owns ParamSchema and the per-mode MLP aliases;
new shared headers (dsp/ratio_seq.hpp, dsp/seq_clock.hpp, core/event_queue.hpp
with a note on why it is deliberately not RingBuffer); backends/base-backend.ts;
the seed script and codegen/templates bullets removed; the synth-midi-cc.json
path corrected to its sources/ subdir.
Plan §4 burned down. S26 is NOT done — it is now an operator decision with the
per-field inventory that makes it cheap, recorded in the plan: default_spread is
already wired (the audit's "zero consumers" was a quarter wrong);
default_learning_rate/default_max_iterations are unread but every schema carries
exactly the values already hardcoded, so wiring them is numerically a no-op
today; input_channels is codegen-time-validated and carries real meaning for
sound_analysis_midi; and per-param curve is a trap — it is descriptive of
squaring the engines already do internally, so wiring it would double-apply on
35 params.
Plan §3 marked burned down, noting where execution departed from the written
plan and why: L35 deleted rather than relocated (zero consumers), and S35's
describe ABI widening forcing two out-of-scope buffer fixes. Also records the
package.json test-glob bug found en route — new unit tests were silently not
being run.
ALIGNMENT defect 5's trailing sentence updated: the behaviour bugs it listed as
pending are fixed.
The three top-level orienting docs are each falsified by several of the eight
preceding commits, so they land here as one sync rather than being split
across commits that would each leave them half-true. Same phase, same push.
MAP.md — removed fixed_buffer.hpp, voice_space.hpp, the src/daisysp entry and
its symlink from the sketch-tree list, input_router.hpp/wire_inputs (inputs are
wired by bind_peripherals now), test_fixed_buffer.cpp, SplitStage/ReadoutStrip/
InputMini, BackendAdvanced.tsx, and feedback/rng.ts; corrected the primitives
count 12 -> 7; replaced the "engine LIFTED from playground/src" provenance;
rewrote the perf-attribute convention and deleted the NISPS_AUDIO_FUNC gotcha
(both macros and gotcha are gone).
docs/AGENT-REFERENCE.md — the memory-section-attribute instruction now names
only the macros that exist; input_router.hpp dropped from the firmware tree;
symlink list and the submodule-init note no longer mention daisysp.
ALIGNMENT.md — defect 5 (dead mass and registry sprawl) rewritten: the deletion
half is done, so the entry now scopes to what actually remains, which is the
registry/dual-truth half (Phase 3) plus the stale specs (docs pass). Per the
ALIGNMENT convention this is a rewrite-to-current, not a checkbox.
Phase 0 of the 2026-07 simplification audit (plan §1). CI has been 100% red on
main since 2026-07-13 and every "gates green" claim since rested on local runs.
- S7 / critic gap 2: push memllib `feat/nisps-core-swap` (3 commits incl. the
pin b37fc53) to monkey-w1n5t0n/memllib and repoint .gitmodules at the fork.
Those commits existed on exactly one disk; `git ls-remote` now resolves the
pin, so `submodules: recursive` checkout and fresh clones work again. Drops
the compensating unreachable-pin error paragraph in build-firmware-arch.sh.
- S24 / S31: the manifold-tests job regenerates from schemas/, runs the codegen
golden test, and fails on a dirty diff — the "schema changes ship with both
generated outputs" rule is now enforced rather than assumed.
- S32: a WASM freshness gate runs the parity harness against the *committed*
manifold/public/nisps.{js,wasm} before the CI rebuild overwrites it. That
artifact is what the webhook ships to production, so a stale commit now fails
loudly instead of shipping.
- critic gap 3 / operator decision §7.4: the VPS webhook
(~/.config/webhooks/meml-deploy.sh, not in this repo) waits for the `CI`
workflow to conclude success on the pushed SHA before building. Fail-closed;
MEML_SKIP_CI_GATE=1 for an emergency hand-deploy. Verified the gate query
returns `failure` for fa37047, i.e. it would have blocked that deploy.
- S31: corrected run-all-tests.sh's false "single command CI invokes" header.
Docs moved with the code: ALIGNMENT defect 1 deleted (resolved) and the rest
renumbered; MAP.md's unreachable-pin warning replaced with the fork pin and a
pointer to the §7.5 vendoring decision; ONBOARDING documents the deploy gate
and the tracked-WASM-ships-to-prod hazard; plan §1 marked burned down.
Gates: scripts/run-all-tests.sh ALL GREEN (ctest 4/4, parity 1273 floats within
1e-5, lint, typecheck, 33 Playwright specs).
Replace the vendored runtime MLP in vcv/src/iml.hpp (DetRng + 3D-weight-store
MLP + Dataset + IML) with a THIN, Rack-free adapter over the shared core:
nisps::ml::MLPCore<nisps::ml::DynamicStorage> (8->[16,24,16]->16, the P2 dynamic
case), nisps::Rng, and the core MLP's own FIFO dataset. Behaviour changes from
the vendored approximation to core-exact firmware/WASM semantics.
- MEMLNaut.cpp: staged/pending weight buffers and patch JSON now use the core's
flat [weights..][biases..] vector (nisps::IML<float>::Weights); patch version
bumped to 3. Double-buffer / single-writer threading discipline unchanged.
- New ctest tests/cpp/test_vcv_iml_parity.cpp: seeded train/infer/move_weights
session through the adapter is memcmp-equal to a bare MLPCore<DynamicStorage>.
- Docs: vcv-module.md delta #5 marked CLOSED (2026-07-18); MAP.md vcv/ updated.
Closes vcv-module.md delta #5.
- plan P2 marked landed (hardware timing spot-check deferred to the
chokepoint-B session on physical MEMLNaut)
- ALIGNMENT: defect #3 resolved by the runtime-shaped browser MLP
- MAP/AGENT-REFERENCE/nisps-wasm-README: describe the honoured-dims
create + reshape ABI; note per-mode dims become schema-real at P5
- fresh wasm artifacts from the gate run
Operator-approved ABI change (P2 stop-point). The WASM MLP is now
MLPCore<DynamicStorage>:
- nisps_ml_create(input, output, hidden[3], n, seed) honours its args;
non-positive/null fall back to the historical 32→[10,14,18]→126, so
every pre-P2 caller (manifold, worker, parity harness) stays
bit-identical. Invalid/oversized dims (>4096) → null.
- NEW nisps_ml_reshape(ml, in, out, hidden, n, spread): fresh net at the
new dims, warm-started via nisps/ml/warm_start.hpp (overlapping region
copied; rest keeps spread init); feedback controller re-created (state
resets — reset-on-reshape modal is the front-end contract). Failure
leaves the old net untouched.
- nisps_ml_describe(ml, out): takes the handle; null reports defaults.
- FeedbackController got the same storage split: algorithms in
FeedbackControllerCore<FbStorage>; FixedFeedbackStorage keeps firmware/
tests source-identical via the old alias; DynamicFeedbackStorage (one
arena) sizes to the runtime net. Firmware .text unchanged (122692).
- MLHandle: per-instance scratch vectors; dropped the dead 2MB
batch_out_scratch.
- TS: types.ts decls (+_nisps_ml_reshape), wasm-iml re-describes the
created instance, worker carries a shape-contract note for P2.3.
Verified: ctest 4/4 incl. new warm-start grow/shrink test; reshape ABI
smoke (dims honoured, overlap survives, invalid rejected, outputs
bounded); parity PASS unchanged (2.4e-7); lint clean; manifold 9 unit +
20 e2e green; firmware .text 122692 (+0.30% vs pre-P2 baseline).
Algorithms (forward, backprop/SGD, init, move_weights, diagnostics) now live
once in MLPCore<Storage> (nisps/ml/mlp.hpp). Storage models:
- FixedStorage (storage.hpp): template-sized std::array, zero heap. The
classic MLP<NIn,H1,H2,H3,NOut,...> is an alias preserving kInput/kHidden*/
kOutput/kNumLayers/weight_count() constexpr — firmware + bindings + modes
compile unchanged.
- DynamicStorage (dynamic_storage.hpp): runtime dims, ONE arena allocation
at construction, nothing per-call. #error under NISPS_TARGET_EMBEDDED
(new macro in core/perf.hpp); sole lint-cpp.sh heap-allowlist entry, plus
a lint check that fails if the #error guard disappears.
Verification:
- new ctest test_mlp_storage_parity: fixed↔dynamic BIT-identical across
init/draw/inference/train(FIFO)/move_weights(pin mask)/eval_loss/
layer_stats/set_weights/infer_batch/reset; invalid+moved-from inert
- golden ML vectors (pre-refactor constants) pass → bit-stable refactor
- native↔WASM parity PASS, max delta unchanged (2.4e-7)
- chokepoint B compile: PAFSynth .text 122324→122692 (+0.30%, ±1% budget);
RAM +416B (eval scratch)
- fix: firmware-common.sh used bare 'python' (absent here) → ${PYTHON:-python3}
Part of one-core-engine-refactor P2. nisps_ml_create ABI untouched (P2.2 is
an operator stop-point).
Consolidate docs/slp-workshop/SPEC.md into docs/specs/slp-workshop-
firmware.md so the project has a single SLP-Workshop spec. The unified
spec is now two parts:
- Part I (§1-8, shipped/stable): the mode + Jolt & OU-explore gestures.
- Part II (§9-13, planned): the output-mode evolution — Continuous/Rhythm
stream model, gate sequences, CV/MIDI output config, Manifold UX, and
the locked operator decisions (2026-06-28).
Frontmatter bumped stable -> evolving (the doc now spans shipped +
planned). Donor docs/slp-workshop/SPEC.md removed; README.md row and
MAP.md ## Specs note updated. No code change.
Self-contained stable reference spec crystallizing the SLP-Workshop
firmware: the MEMLCelium-based mode, the two adaptive-learning gestures
ported from upstream InterfaceRL (Jolt held weight-morph, OU output
walk) with exact constants, the runtime-not-compile-time + inert-by-
default design, the ModeBase integration incl. the GCC -Wstringop-
overflow workaround, control mappings, schema/codegen, and the
browser-parity caveat. Adds the spec to docs/specs/README.md and a
## Specs section to MAP.md per the specs-skill config format.
Refs commits 4e60d01, 57c9ede (merged at 527b8fc).
Spec-only (firmware fork is WIP, built by another agent). Captures: memlcelium
is a single shared-net hybrid (14 ratio-seq + 42 synth outputs); the two-stream
model (Continuous + Rhythm); firmware 3 compile-time modes + browser's 4th
split-net hybrid; gate-sequence config for CV & MIDI (counts + shared/separate
net toggle; XY-pad→2nd pad / gamepad→double-stick); CV jack allocation
(11 CV + 3 gate, swap CV→gate); RatioSeq gate generation; no wire-protocol change.
Restores the April-2026 "uSEQ-Celium" functionality (browser → uSEQ
hardware + CV expander over USB Web Serial) as a first-class Manifold
Outputs backend, and re-vendors the RP2040 firmware into the repo.
- protocol v2 (uSEQ-CV): firmware/useq-celium/shared/protocol.h is the
single source of truth, mirrored by manifold/src/backends/useq-protocol.ts.
26-byte OUTPUT frame, 11×u16 CV (12-bit) + 3-gate bitfield + XOR; fixed
topology; host-agnostic so the MEMLNaut RP2350 can emit identical bytes.
Spec in docs/useq-celium/protocol.md.
- firmware/useq-celium/{main,expander}: PlatformIO RP2040 firmware rewritten
to v2 from the real April pin maps (expander I2C addr 0x10).
- UseqCvBackend (id cvgate): Web Serial connect/identify/disconnect, 100 Hz
stream, per-channel dead-zone, gate thresholding; modeled on midi-backend.
Per-output CvSpec (channel + gateThreshold) on MFParam; config UI in
OutputsBackendConfig + BackendAdvanced; new "CV / uSEQ" top-dock mode.
- bun-test for the protocol frame layout; MAP.md updated.
Wire the modular input layer into the Console and reshape the browser
engine so input axes are genuine independent dimensions.
Inputs (manifold/src/inputs/):
- gamepad-source: emit press+release edges with standard-mapping labels
(enables hold-and-move); single/double-stick already present.
- midi-input-source: single-device selection + batch "MIDI Learn"
(every CC swept while armed becomes an axis); notes stay discrete.
- input-layer: compose() forwards each axis 1:1 (no mean-blend);
add onReducedInput so the manifold tracks gamepad/MIDI position.
- types: InputAction.phase, InputMode.
Console (manifold/src/console/):
- ConsoleApp: bind gamepad buttons to verdicts (RB up / LB down /
X randomise / Y nudge / B undo / A-hold reposition); mirror composed
position onto the manifold.
- Drawers: rebuilt Inputs drawer (source picker, gamepad legend, MIDI
device picker + batch-learn flow, learned-control meters).
Engine (nisps/wasm, manifold/src/engine):
- DefaultMLP widened MLP<2,..> -> MLP<32,..> (32 = MAX_AXES); each
active axis gets a dedicated slot, unused slots held at 0 (inert).
Rebuilt nisps.wasm (playground + manifold).
- spine/engine-api: setInputs writes the full N-D vector (was dropping
arr[2+]); primary pair keeps the 2-D pipeline; process() re-ticks the
whole vector via spine.reprocess().
Tests:
- parity_check/parity_wasm: ParityMLP -> 32 inputs, widen example bufs.
- CMakeLists: build parity binary with -ffp-contract=off so native
matches FMA-free WASM (training amplified the gap past 1e-5).
Inputs dock is still an exclusive picker; mixing toggles, reshape modal,
and the >2-D slider view (inputs-spec.md) are groundwork-laid but not
yet wired. See docs/redesign/midi-gamepad-inputs-worklog.md.
Source: archive/playground-redesign-2026-snapshot branch (commit 287e3a1).
Reference-only — playground is being rewritten in SolidJS, so the redesign's
implementation will not be merged, but its design intent is preserved here
for the rewrite team.