One-core-engine P4.3/P4.4: the input/output pipeline processing and the curve
catalog now live in the C++/WASM core (nisps/pipeline/*, nisps/core/math.hpp).
The TS ports are deleted and the browser drives the WASM chains.
Engine:
- WasmIML owns a nisps_pipeline_create handle + bridge buffers and exposes
setInputConfig (TS InputConfig → 15-float wire), processInput, resetInput,
setOutputConfig (Infinity slew → 0), setOutputFreezeMask, processOutput
(in place), resetOutput, curveApply, curveApplyBatch (chunked). Handle +
buffers created in init_, freed in dispose, output-sized buffers realloc'd
on reshape.
- Spine routes setInputs through iml.processInput/processOutput (state lives
C++-side); config source-of-truth stays TS-side and is pushed on attach /
setInputConfig / setOutputConfig. Preserves ?debug=1 fixed-dt determinism
(same dt fed to the WASM calls). EngineApi gains setInputConfig/
setOutputConfig/curveApply/curveApplyBatch.
- New types-only modules: pipeline-types.ts (InputConfig/OutputConfig +
defaults + wire int mappers) and curve-catalog.ts (CurveName + name→id).
types.ts declares the pipeline/curve C ABI. engine barrel updated.
- DELETED src/engine/{input-pipeline,output-pipeline,curves}.ts.
Tests (P4.4 gate — recorded-gesture regression):
- pipeline-golden.test.ts now loads the built WASM (indirect-eval shim,
tests/wasm-load.ts) and drives the frozen gesture/output fixtures through the
C++ chains, honouring the per-event dt clock contract. Tolerance 1e-5
(non-momentum drift <5e-7). The 3 momentum configs carry 1e-2: proven-inherent
f32 drift (a byte-faithful f32 port of the exact original algorithm matches
the WASM to <6e-8 while both diverge from the f64 capture by ~7-9e-3), NOT a
core bug.
- curves-golden.json: linear/square/sqrt/centered_power kept as the original
f64 captures (C++ matches within <3e-8); exp/log/sigmoid/cubic RE-BASELINED
from the WASM (deliberate switch to firmware-exact k=1 exp/log, slope-6
sigmoid, true cubic x^3). Provenance recorded in-file.
- _generate.ts rebuilt as the WASM curve re-baseline tool; pipeline-golden-lib
trimmed to pure data builders.
Docs: fixtures/README.md + manifold/ONBOARDING.md updated.
Gates: typecheck, bun test (9), vite build, playwright e2e (27) all green.
|
||
|---|---|---|
| .. | ||
| _generate.ts | ||
| curves-golden.json | ||
| gesture-trace.json | ||
| input-pipeline-golden.json | ||
| output-pipeline-golden.json | ||
| README.md | ||
Pipeline golden fixtures
Originally captured 2026-07-13 from the TypeScript engine implementations,
before the P4 "one core engine" migration
(docs/specs/plans/one-core-engine-refactor.md §P4). As of 2026-07-18 that
migration has landed: the curve / input-pipeline / output-pipeline maths now
live solely in the C++/WASM core (nisps/pipeline/*, nisps/core/math.hpp),
and the TS curves.ts / input-pipeline.ts / output-pipeline.ts are deleted.
These fixtures are the recorded-gesture pre/post-migration regression P4's gate
calls for: "same pointer trace → same routed output pre/post migration." The
test (../pipeline-golden.test.ts) now drives the WASM chains against them.
What is here
| File | What it pins | Driven through (now) |
|---|---|---|
gesture-trace.json |
One canonical synthetic pointer trace (288 events, fixed 120 Hz dt) over the input pipeline's native [0,1]² domain: h/v sweeps, diagonal, spiral, figure-eight, dwell + abrupt corner jumps. Pure formula — no Math.random, no Date.now. |
(input to the input chain) |
curves-golden.json |
The curve catalog, 129 samples of x ∈ [0,1] inclusive. See "Curves re-baselined" below. |
nisps_curve_apply (WASM) |
input-pipeline-golden.json |
The gesture trace run through the input pipeline under 14 representative configs (default, deadzone, zoom, sticky anchor, per-axis, curves, smoothing, invert, momentum gentle/strong, frozen axis, fully frozen, combined). Records {x, y, frozen} per event. Configs embedded. FROZEN pre-migration capture. |
nisps_input_* (WASM) |
output-pipeline-golden.json |
A deterministic raw-output sequence (120 vectors × 8 channels of offset sines, quantised to f32) run through the output pipeline under 8 configs (default, curves, smoothing, slew, global-freeze toggled mid-sequence, per-output freeze mask, combined). Records the processed vector per step. Configs embedded. FROZEN pre-migration capture. | nisps_output_* (WASM) |
The regression guard
../pipeline-golden.test.ts (bun test) loads the built WASM
(../../public/nisps.{js,wasm}) via the indirect-eval shim (../wasm-load.ts,
same technique as tests/cpp/parity_wasm.mjs), creates a pipeline handle, and
re-runs the committed fixtures through the C++ chains. The fixtures are
authoritative: the trace, raw sequence, and per-run configs are read FROM the
JSON, so the config lists in pipeline-golden-lib.ts cannot mask a regression.
Tolerances (f32 WASM vs f64-captured fixtures)
- Input / output pipelines:
1e-5. Measured max non-momentum drift<5e-7. - Momentum configs (
momentum-gentle/momentum-strong/combined):1e-2. This is proven-inherent f32 drift, not a core bug. The velocity ring's window-membership test (now − t ≤ window) is a DISCRETE boundary that f32 rounding can flip during fast gestures, shifting which sample is the window's oldest by a whole frame (~8 ms) → a step change in the measured speed → integrated by the momentum-zoom IIR. A byte-faithful f32 port of the exact original TS algorithm reproduces the WASM to<6e-8while both diverge from the f64 capture by the same~7–9e-3(measured max8.6e-3on momentum-strong). Reconciling it would require f64 momentum maths, which would break firmware parity — so the momentum runs are guarded at1e-2(the core is still tightly pinned to the algorithm by the<6e-8faithful-f32 identity; a real behavioural regression would blow far past1e-2).
Curves re-baselined (2026-07-18)
curves-golden.json is now a MIX, recorded in its provenance field:
linear/square/sqrt/centered_power— the ORIGINAL 2026-07-13 f64 TS captures, kept unchanged. The C++ core reproduces them within1e-5(measured<3e-8), proving no behaviour change for these curves.exp/log/sigmoid/cubic— RE-BASELINED from the WASM. The browser deliberately adopted the canonical firmware-exact maths: the old TScurves.tsusedk=4exp/log, slope-8 sigmoid, and a smoothstep "cubic"; the canonicalnisps/core/math.hppcatalog usesk=1-normalised exp/log, a slope-6 sigmoid, and a true cubicx³. The test asserts WASM stability against these regenerated values.
Contracts you must reproduce to consume these
State contract (both chains are stateful, C++-side per pipeline handle)
- Input: EMA-smoothed x/y, a velocity ring, and a momentum-zoom multiplier.
- Output:
prev+smoothedbuffers driving slew/freeze.
Each config run resets state (nisps_input_reset / nisps_output_reset)
at step 0. Runs are independent; do not carry state between them.
Clock contract (input pipeline only)
The C++ input chain accumulates its own clock from the per-call dt (seconds)
for the momentum velocity window; it takes no wall clock. To reproduce the
capture, the test feeds each event's dt = the per-event t_ms delta in
seconds, with the first event's dt = 0 (matching the original TS capture,
which pinned performance.now() to each event's t_ms). The output chain uses
a constant per-step dt of 1000/60 ms (→ seconds).
JSON encodings
slewRate: nullin an output spec meansInfinity(JSON has noInfinity); it maps to the wire'sslew_rate <= 0 ⇒ unlimited.- Output raw values are pre-quantised with
Math.froundso they equal exactly what aFloat32Arrayholds.
Re-capture
bun tests/fixtures/_generate.ts regenerates gesture-trace.json (pure data)
and re-baselines the 4 changed curves in curves-golden.json from the WASM
(preserving the 4 unchanged f64 entries + provenance). It does not rewrite
the input/output pipeline goldens — those are the frozen pre-migration capture
the regression is measured against; there is no TS pipeline left to capture
from. scripts/build-wasm.sh must have run first.