Phase 2 (S30, L52, L51). These are the gates that are supposed to protect the
core's headline constraints, so the fix is demonstrated rather than asserted.
- S30: lint-cpp.sh's heap audit hardcoded dsp/engines/ml/modes, so nisps/core/
and nisps/pipeline/ — the P4 control-rate hot path — were NEVER scanned. Its
comment handling also post-filtered with `grep -v ' *//'`, which misses
trailing comments. Coverage is now exclusion-based (everything under nisps/
except wasm/, tests/, build/), comments are STRIPPED before matching using the
same perl strip audit_float_suffix already used, and the pattern set is
extended. dynamic_storage.hpp remains deliberately allowlisted — it is a real,
documented heap user that #errors on RP2350.
PROOF (run by me, not just reported): planted `new float[4]` in
nisps/pipeline/input_chain.hpp plus a std::malloc in the trailing-comment form
the old filter skipped.
old lint: "[lint-cpp] clean", exit 0 <- the false negative, live
new lint: FAIL, both lines named, exit 1
Plants removed; lint clean and exit 0 again; tree verified unmodified.
- L52: parity-check.sh's FAIL branch was unreachable — under `set -e` the script
died at the diff command before it could print anything. A mismatch therefore
failed silently. The diff now runs inside the `if` condition, where set -e does
not apply. Verified by forcing NISPS_PARITY_TOL=0: old script exited silently,
new one prints FAIL with the exit code.
- L51: parity_check.cpp and parity_wasm.mjs headers documented 4 stages / blob
v1 against a real 7-stage / v5 implementation. Comment-only rewrite; parity
behaviour unchanged (re-ran the gate: PASS, 1273 floats within 1e-5).
Note for future work: the lint now permanently scans nisps/core/ and
nisps/pipeline/, so changes there are enforced that were not before.
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.