memlnaut-nisps/manifold/tests/fixtures/README.md
monkey-w1n5t0n 846a0c373a feat(manifold)!: route input/output pipelines + curves through the WASM core (P4.3/P4.4)
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.
2026-07-18 12:21:35 +02:00

92 lines
5.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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-8` while both diverge from
the f64 capture by the same `~79e-3` (measured max `8.6e-3` on
momentum-strong). Reconciling it would require f64 momentum maths, which would
break firmware parity — so the momentum runs are guarded at `1e-2` (the core
is still tightly pinned to the algorithm by the `<6e-8` faithful-f32 identity;
a real behavioural regression would blow far past `1e-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 within `1e-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 TS
`curves.ts` used `k=4` exp/log, slope-8 sigmoid, and a smoothstep "cubic"; the
canonical `nisps/core/math.hpp` catalog uses `k=1`-normalised exp/log, a
slope-6 sigmoid, and a true cubic `x³`. 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` + `smoothed` buffers 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: null` in an output spec means `Infinity` (JSON has no `Infinity`);
it maps to the wire's `slew_rate <= 0 ⇒ unlimited`.
- Output raw values are pre-quantised with `Math.fround` so they equal exactly
what a `Float32Array` holds.
## 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.