chore(build): P0 plumbing — WASM build/parity retarget to manifold/public
- build-wasm.sh emits to manifold/public/ (transitional copy to playground/public/ until P1 retires the playground) - parity-check.sh + parity_wasm.mjs read the manifold artifact - fix stale MLP<2,...> arity in AGENT-REFERENCE.md + nisps/wasm/README.md - gitignore .claude/worktrees/ - plan one-core-engine-refactor.md: P0 marked landed Gate: run-all-tests green; parity PASS from manifold artifact (2.4e-7); manifold builds against freshly-built nisps.wasm.
This commit is contained in:
parent
c936bf75c9
commit
29dc88be3a
10 changed files with 43 additions and 28 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -50,3 +50,6 @@ node_modules/
|
||||||
bun.lock
|
bun.lock
|
||||||
playwright-report/
|
playwright-report/
|
||||||
test-results/
|
test-results/
|
||||||
|
|
||||||
|
# Agent worktrees
|
||||||
|
.claude/worktrees/
|
||||||
|
|
|
||||||
4
MAP.md
4
MAP.md
|
|
@ -40,7 +40,7 @@ MEMLNaut-NISPS — Neural Interactive Shaping of Parameter Spaces. One C++20 cod
|
||||||
- `playground/src/input/pipeline.ts`, `playground/src/output/pipeline.ts`, `playground/src/output/curves.ts` — pure-fn pipelines (deadzone→zoom→curve→smoothing→momentum, then global curve→smoothing→slew→freeze).
|
- `playground/src/input/pipeline.ts`, `playground/src/output/pipeline.ts`, `playground/src/output/curves.ts` — pure-fn pipelines (deadzone→zoom→curve→smoothing→momentum, then global curve→smoothing→slew→freeze).
|
||||||
- `playground/src/features/` — additional feature modules (heatmap sampling, snapshot stack, A/B compare, region pin, param pin, trail, weight health, etc.).
|
- `playground/src/features/` — additional feature modules (heatmap sampling, snapshot stack, A/B compare, region pin, param pin, trail, weight health, etc.).
|
||||||
- `playground/src/debug/probe.ts` — synchronous `window.__nisps` debug probe for Playwright.
|
- `playground/src/debug/probe.ts` — synchronous `window.__nisps` debug probe for Playwright.
|
||||||
- `playground/public/nisps.{wasm,js}`, `c15.wasm`, `c15-glue.js` — compiled WASM artifacts (built by `scripts/build-wasm.sh`).
|
- `playground/public/nisps.{wasm,js}`, `c15.wasm`, `c15-glue.js` — WASM artifacts. `nisps.*` are a transitional copy from `scripts/build-wasm.sh` (canonical output is `manifold/public/`; copy dies with playground at P1).
|
||||||
- `playground/tests/e2e/` — Playwright specs (`ml-engine`, `modes`, `persistence`, `ui-interactions`) + `helpers.ts`.
|
- `playground/tests/e2e/` — Playwright specs (`ml-engine`, `modes`, `persistence`, `ui-interactions`) + `helpers.ts`.
|
||||||
- `playground/playwright.config.ts` — Vite preview server setup.
|
- `playground/playwright.config.ts` — Vite preview server setup.
|
||||||
|
|
||||||
|
|
@ -113,7 +113,7 @@ the "BUILD DELTAS" block at the top of `docs/specs/vcv-module.md`). `src/MEMLNau
|
||||||
|
|
||||||
### `scripts/` — build + verify entry points
|
### `scripts/` — build + verify entry points
|
||||||
- `build-firmware.sh`, `flash-firmware.sh`, `build-and-flash-firmware.sh`, `firmware-common.sh` — Arduino-CLI wrapper for RP2350 target with C++20 flag.
|
- `build-firmware.sh`, `flash-firmware.sh`, `build-and-flash-firmware.sh`, `firmware-common.sh` — Arduino-CLI wrapper for RP2350 target with C++20 flag.
|
||||||
- `build-wasm.sh` — Emscripten compile producing `playground/public/nisps.{wasm,js}`.
|
- `build-wasm.sh` — Emscripten compile producing `manifold/public/nisps.{wasm,js}` (+ transitional copy to `playground/public/` until P1).
|
||||||
- `build-cpp-tests.sh` — CMake configure + build + ctest (Ninja).
|
- `build-cpp-tests.sh` — CMake configure + build + ctest (Ninja).
|
||||||
- `parity-check.sh` — runs native + WASM and diffs binary outputs.
|
- `parity-check.sh` — runs native + WASM and diffs binary outputs.
|
||||||
- `lint-cpp.sh` — `.f` literal warn + heap/`Arduino.h` violation fail.
|
- `lint-cpp.sh` — `.f` literal warn + heap/`Arduino.h` violation fail.
|
||||||
|
|
|
||||||
|
|
@ -124,9 +124,9 @@ Two WASM instances at runtime:
|
||||||
1. **Main thread** (`playground/src/ml/wasm-iml.ts`): ML inference + sync training + RL primitives. Update store after each call. Async training via disposable Web Worker (`wasm-worker.ts`).
|
1. **Main thread** (`playground/src/ml/wasm-iml.ts`): ML inference + sync training + RL primitives. Update store after each call. Async training via disposable Web Worker (`wasm-worker.ts`).
|
||||||
2. **AudioWorklet** (`playground/src/audio/worklet/nisps-processor.ts`): runs engine `process_block` per audio block. Loads `nisps.wasm` directly via `WebAssembly.compile` (no Emscripten glue in worklet). Bytes posted from main thread.
|
2. **AudioWorklet** (`playground/src/audio/worklet/nisps-processor.ts`): runs engine `process_block` per audio block. Loads `nisps.wasm` directly via `WebAssembly.compile` (no Emscripten glue in worklet). Bytes posted from main thread.
|
||||||
|
|
||||||
C API is in `nisps/wasm/bindings.cpp`. Build: `bash scripts/build-wasm.sh` (~94KB output to `playground/public/`).
|
C API is in `nisps/wasm/bindings.cpp`. Build: `bash scripts/build-wasm.sh` (~94KB output to `manifold/public/`, transitional copy to `playground/public/` until P1).
|
||||||
|
|
||||||
The WASM target is fixed at `MLP<2, 10, 14, 18, 126>`. Modes with smaller `output_size` use the first N outputs only.
|
The WASM target is fixed at `MLP<32u, 10u, 14u, 18u, 126u>` (`nisps_ml_create` ignores requested dims — see `docs/specs/plans/one-core-engine-refactor.md` P2). Modes with smaller `input_size`/`output_size` use a slice.
|
||||||
|
|
||||||
### Known limitations
|
### Known limitations
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -74,13 +74,13 @@ skin) still applies inside manifold; this plan adds the vertical one.
|
||||||
Each phase ends green on its test gate and is independently landable. File phases as **ergo** tasks
|
Each phase ends green on its test gate and is independently landable. File phases as **ergo** tasks
|
||||||
(one per phase, sub-tasks per bullet); do not start a phase before its predecessor's gate is green.
|
(one per phase, sub-tasks per bullet); do not start a phase before its predecessor's gate is green.
|
||||||
|
|
||||||
### P0 — Plumbing hygiene (hours, no behaviour change)
|
### P0 — Plumbing hygiene (hours, no behaviour change) — ✅ landed 2026-07-13
|
||||||
|
|
||||||
- `scripts/build-wasm.sh` emits to `manifold/public/` (keep `playground/public/` copy only until P1 lands).
|
- ✅ `scripts/build-wasm.sh` emits to `manifold/public/` (keep `playground/public/` copy only until P1 lands).
|
||||||
- `scripts/parity-check.sh` reads the manifold artifact.
|
- ✅ `scripts/parity-check.sh` (and `tests/cpp/parity_wasm.mjs`) read the manifold artifact.
|
||||||
- Fix stale doc: `MAP.md` §WASM still says `MLP<2,…>`; code is `MLP<32u,10u,14u,18u,126u>`.
|
- ✅ Fix stale doc: the `MLP<2,…>` line was in `docs/AGENT-REFERENCE.md` + `nisps/wasm/README.md` (MAP.md was already correct); both now say `MLP<32u,10u,14u,18u,126u>`.
|
||||||
- Delete `ml-debug.log`, `graphify-out/` from tree root; gitignore `.claude/worktrees/`.
|
- ✅ `ml-debug.log`/`graphify-out/` were already absent from the tree; `.claude/worktrees/` gitignored.
|
||||||
- **Gate:** `run-all-tests.sh` green; manifold builds against a freshly-built (not copied) `nisps.wasm`.
|
- **Gate met:** `run-all-tests.sh` green (Playwright leg via the BUILD-PLAN non-snap-node VPS runner); parity PASS reading `manifold/public/` (max delta 2.4e-7); manifold typecheck+build green against the freshly-built artifact.
|
||||||
|
|
||||||
### P1 — Retire playground, single TS home (≈1 day)
|
### P1 — Retire playground, single TS home (≈1 day)
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -1,11 +1,13 @@
|
||||||
# nisps/wasm
|
# nisps/wasm
|
||||||
|
|
||||||
Emscripten target that exposes `nisps/ml` (MLP) and `nisps/engines` (audio
|
Emscripten target that exposes `nisps/ml` (MLP) and `nisps/engines` (audio
|
||||||
engines) to the SolidJS playground via a flat C ABI.
|
engines) to the browser apps via a flat C ABI.
|
||||||
|
|
||||||
This directory is a leaf — it does not export headers for inclusion by
|
This directory is a leaf — it does not export headers for inclusion by
|
||||||
other C++ code. The only artifact is `bindings.cpp` plus the build script
|
other C++ code. The only artifact is `bindings.cpp` plus the build script
|
||||||
that turns it into `playground/public/nisps.{wasm,js}`.
|
that turns it into `manifold/public/nisps.{wasm,js}` (with a transitional
|
||||||
|
copy to `playground/public/` until P1 of
|
||||||
|
`docs/specs/plans/one-core-engine-refactor.md` retires the playground).
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
|
|
@ -18,11 +20,11 @@ Requires `emcc` (Emscripten). The script defaults to
|
||||||
|
|
||||||
Output:
|
Output:
|
||||||
|
|
||||||
- `playground/public/nisps.wasm` — the compiled module.
|
- `manifold/public/nisps.wasm` — the compiled module.
|
||||||
- `playground/public/nisps.js` — Emscripten glue (factory function
|
- `manifold/public/nisps.js` — Emscripten glue (factory function
|
||||||
`createNispsModule`, MODULARIZE=1).
|
`createNispsModule`, MODULARIZE=1).
|
||||||
|
|
||||||
Both files are committed (so the playground works from a fresh clone
|
Both files are committed (so the browser apps work from a fresh clone
|
||||||
without a C++ toolchain). Re-run `build-wasm.sh` after changes to
|
without a C++ toolchain). Re-run `build-wasm.sh` after changes to
|
||||||
`nisps/{core,ml,engines,wasm}`.
|
`nisps/{core,ml,engines,wasm}`.
|
||||||
|
|
||||||
|
|
@ -32,9 +34,9 @@ The MLP class template is parametrised on `(input_size, hidden1, hidden2,
|
||||||
hidden3, output_size)`. WASM cannot recompile templates at runtime, so
|
hidden3, output_size)`. WASM cannot recompile templates at runtime, so
|
||||||
this build instantiates exactly ONE configuration:
|
this build instantiates exactly ONE configuration:
|
||||||
|
|
||||||
nisps::ml::MLP<2, 10, 14, 18, 126>
|
nisps::ml::MLP<32, 10, 14, 18, 126>
|
||||||
|
|
||||||
That serves the playground use case (2-D joystick → up to 126 synth
|
That serves the browser use case (up to 32 input axes → up to 126 synth
|
||||||
parameters). `nisps_ml_create()` accepts caller-supplied dimensions for
|
parameters). `nisps_ml_create()` accepts caller-supplied dimensions for
|
||||||
forward compatibility but currently ignores them — see comment at the top
|
forward compatibility but currently ignores them — see comment at the top
|
||||||
of `bindings.cpp`. The schemas in `schemas/modes/*.json` use up to
|
of `bindings.cpp`. The schemas in `schemas/modes/*.json` use up to
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# scripts/build-wasm.sh — compile nisps/wasm/bindings.cpp via Emscripten and
|
# scripts/build-wasm.sh — compile nisps/wasm/bindings.cpp via Emscripten and
|
||||||
# write the result to playground/public/.
|
# write the result to manifold/public/.
|
||||||
#
|
#
|
||||||
# Requires emcc. Defaults to /usr/lib/emscripten/emcc; override via the EMCC
|
# Requires emcc. Defaults to /usr/lib/emscripten/emcc; override via the EMCC
|
||||||
# env var. Sample invocation:
|
# env var. Sample invocation:
|
||||||
|
|
@ -8,14 +8,17 @@
|
||||||
# EMCC=$(which emcc) scripts/build-wasm.sh
|
# EMCC=$(which emcc) scripts/build-wasm.sh
|
||||||
#
|
#
|
||||||
# Output:
|
# Output:
|
||||||
# playground/public/nisps.js — Emscripten glue, MODULARIZE factory
|
# manifold/public/nisps.js — Emscripten glue, MODULARIZE factory
|
||||||
# playground/public/nisps.wasm — the compiled module
|
# manifold/public/nisps.wasm — the compiled module
|
||||||
|
#
|
||||||
|
# A copy is also written to playground/public/ until P1 of
|
||||||
|
# docs/specs/plans/one-core-engine-refactor.md retires the playground.
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
EMCC="${EMCC:-$(command -v emcc || echo /usr/lib/emscripten/emcc)}"
|
EMCC="${EMCC:-$(command -v emcc || echo /usr/lib/emscripten/emcc)}"
|
||||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
OUT="$ROOT/playground/public"
|
OUT="$ROOT/manifold/public"
|
||||||
SRC="$ROOT/nisps/wasm/bindings.cpp"
|
SRC="$ROOT/nisps/wasm/bindings.cpp"
|
||||||
|
|
||||||
if [[ ! -x "$EMCC" && ! -f "$EMCC" ]]; then
|
if [[ ! -x "$EMCC" && ! -f "$EMCC" ]]; then
|
||||||
|
|
@ -82,3 +85,10 @@ set -x
|
||||||
|
|
||||||
echo "[build-wasm] wrote $OUT/nisps.js + $OUT/nisps.wasm"
|
echo "[build-wasm] wrote $OUT/nisps.js + $OUT/nisps.wasm"
|
||||||
ls -lh "$OUT/nisps.js" "$OUT/nisps.wasm"
|
ls -lh "$OUT/nisps.js" "$OUT/nisps.wasm"
|
||||||
|
|
||||||
|
# Transitional copy for the playground; removed when P1 deletes playground/.
|
||||||
|
PLAYGROUND_OUT="$ROOT/playground/public"
|
||||||
|
if [[ -d "$PLAYGROUND_OUT" ]]; then
|
||||||
|
command cp -f "$OUT/nisps.js" "$OUT/nisps.wasm" "$PLAYGROUND_OUT/"
|
||||||
|
echo "[build-wasm] copied artifacts to $PLAYGROUND_OUT (transitional, until P1)"
|
||||||
|
fi
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
#
|
#
|
||||||
# Prerequisites:
|
# Prerequisites:
|
||||||
# 1. scripts/build-cpp-tests.sh has run (need nisps_parity_check binary).
|
# 1. scripts/build-cpp-tests.sh has run (need nisps_parity_check binary).
|
||||||
# 2. scripts/build-wasm.sh has run (need playground/public/nisps.{js,wasm}).
|
# 2. scripts/build-wasm.sh has run (need manifold/public/nisps.{js,wasm}).
|
||||||
#
|
#
|
||||||
# This script can run either step on demand if the artifacts are missing.
|
# This script can run either step on demand if the artifacts are missing.
|
||||||
# Skip auto-build with NISPS_PARITY_NO_BUILD=1.
|
# Skip auto-build with NISPS_PARITY_NO_BUILD=1.
|
||||||
|
|
@ -21,8 +21,8 @@ ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
TESTS_DIR="$ROOT/tests/cpp"
|
TESTS_DIR="$ROOT/tests/cpp"
|
||||||
BUILD_DIR="${NISPS_BUILD_DIR:-$ROOT/nisps/build}"
|
BUILD_DIR="${NISPS_BUILD_DIR:-$ROOT/nisps/build}"
|
||||||
NATIVE_BIN="$BUILD_DIR/nisps_parity_check"
|
NATIVE_BIN="$BUILD_DIR/nisps_parity_check"
|
||||||
WASM_GLUE="$ROOT/playground/public/nisps.js"
|
WASM_GLUE="$ROOT/manifold/public/nisps.js"
|
||||||
WASM_MOD="$ROOT/playground/public/nisps.wasm"
|
WASM_MOD="$ROOT/manifold/public/nisps.wasm"
|
||||||
NATIVE_OUT="$TESTS_DIR/parity_native.bin"
|
NATIVE_OUT="$TESTS_DIR/parity_native.bin"
|
||||||
WASM_OUT="$TESTS_DIR/parity_wasm.bin"
|
WASM_OUT="$TESTS_DIR/parity_wasm.bin"
|
||||||
TOL="${NISPS_PARITY_TOL:-1e-5}"
|
TOL="${NISPS_PARITY_TOL:-1e-5}"
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
* parity_check.cpp against the WASM build of nisps and writes a binary blob
|
* parity_check.cpp against the WASM build of nisps and writes a binary blob
|
||||||
* with identical layout. The shell wrapper compares the two blobs.
|
* with identical layout. The shell wrapper compares the two blobs.
|
||||||
*
|
*
|
||||||
* The WASM module is loaded from playground/public/nisps.{js,wasm} —
|
* The WASM module is loaded from manifold/public/nisps.{js,wasm} —
|
||||||
* scripts/build-wasm.sh must have run first.
|
* scripts/build-wasm.sh must have run first.
|
||||||
*
|
*
|
||||||
* Output blob format matches parity_check.cpp:
|
* Output blob format matches parity_check.cpp:
|
||||||
|
|
@ -48,7 +48,7 @@ const SYNTH_FRAMES = 128;
|
||||||
const PROBE_IDX = [0, 5, 19, 31, 73, 137, 251, 491, 999, 1583, 2401, 3289];
|
const PROBE_IDX = [0, 5, 19, 31, 73, 137, 251, 491, 999, 1583, 2401, 3289];
|
||||||
|
|
||||||
async function loadWasm() {
|
async function loadWasm() {
|
||||||
const wasmGluePath = resolve(repoRoot, 'playground', 'public', 'nisps.js');
|
const wasmGluePath = resolve(repoRoot, 'manifold', 'public', 'nisps.js');
|
||||||
try {
|
try {
|
||||||
await access(wasmGluePath, fsConstants.R_OK);
|
await access(wasmGluePath, fsConstants.R_OK);
|
||||||
} catch {
|
} catch {
|
||||||
|
|
@ -59,7 +59,7 @@ async function loadWasm() {
|
||||||
// The Emscripten glue is generated with MODULARIZE=1, which writes
|
// The Emscripten glue is generated with MODULARIZE=1, which writes
|
||||||
// var createNispsModule = (() => ...)();
|
// var createNispsModule = (() => ...)();
|
||||||
// if (typeof exports==='object' && typeof module==='object') module.exports = ...;
|
// if (typeof exports==='object' && typeof module==='object') module.exports = ...;
|
||||||
// It lives in playground/public/, which is a sub-package with
|
// It lives in manifold/public/, which is a sub-package with
|
||||||
// "type":"module" in its parent package.json — so neither `require()` nor
|
// "type":"module" in its parent package.json — so neither `require()` nor
|
||||||
// `import()` can extract the factory cleanly. We work around this by
|
// `import()` can extract the factory cleanly. We work around this by
|
||||||
// reading the file as text and evaluating it inside a thin shim that
|
// reading the file as text and evaluating it inside a thin shim that
|
||||||
|
|
@ -77,7 +77,7 @@ async function loadWasm() {
|
||||||
console.error('[parity_wasm] could not locate createNispsModule in glue');
|
console.error('[parity_wasm] could not locate createNispsModule in glue');
|
||||||
process.exit(2);
|
process.exit(2);
|
||||||
}
|
}
|
||||||
const wasmBinaryPath = resolve(repoRoot, 'playground', 'public', 'nisps.wasm');
|
const wasmBinaryPath = resolve(repoRoot, 'manifold', 'public', 'nisps.wasm');
|
||||||
const wasmBinary = await readFile(wasmBinaryPath);
|
const wasmBinary = await readFile(wasmBinaryPath);
|
||||||
const Module = await factory({ wasmBinary });
|
const Module = await factory({ wasmBinary });
|
||||||
return Module;
|
return Module;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue