refactor(wasm): delete 12 dead C-API entries and the weights-publish channel

Phase 1 group 4 (S33, S34, L54).

- S33: removed 12 dead entries across the full 5-layer registration chain
  (bindings.cpp KEEPALIVE -> EXPORTED_FUNCTIONS -> the NispsModule declaration
  table -> the WasmIML wrapper -> the EngineApi facade): nisps_ml_reset,
  example_count, move_weights, feedback_learning_paused, feedback_drag,
  jolt_lr_scale, jolt_tick_lr_ramp, pipeline_state_size/save_state/load_state,
  feedback_placing and feedback_state. Each was grepped against manifold/src,
  manifold/tests, the e2e specs, manifold/tests/wasm-load.ts and
  tests/cpp/parity_wasm.mjs — the parity gate builds its own API via cwrap and
  is a real consumer, so it counts.
  KEPT deliberately: EXPORTED_RUNTIME's heap views + ccall/cwrap (the parity
  harness and wasm-load.ts depend on them), and nisps_ml_feedback_static_output,
  whose C export IS called directly by parity_wasm.mjs even though no TS
  wrapper reaches it. Also dropped parity_wasm.mjs's moveWeights cwrap, which
  was declared but never invoked.
- S34: deleted the publishWeights_ channel — EngineSink.setWeights,
  Spine.setWeights/weights()/liveWeights and every call site. It ran a C->heap
  copy plus a fresh Float32Array allocation at up to 200 Hz into a field
  nothing read. getWeights survives for persistence and the debug probe.
- L54: worklet loader — deleted the unused imports object, the 'c' branch,
  exMap and the duplicate second loop, and replaced the silent `() => 0` stub
  with one that throws, so a missing import fails loudly instead of returning
  plausible zeros into the audio path.

manifold/public/nisps.{js,wasm} rebuilt with the trimmed export list (emcc
3.1.69, the CI-pinned version) and committed — the freshness gate added in
Phase 0 requires it, and the webhook ships this artifact to production.

Gates: run-all-tests.sh ALL GREEN, parity 1273 floats within 1e-5.
This commit is contained in:
monkey-w1n5t0n 2026-07-21 12:48:50 +02:00
parent ea588e79cd
commit c98d25c255
12 changed files with 33 additions and 267 deletions

File diff suppressed because one or more lines are too long

Binary file not shown.

View file

@ -26,16 +26,12 @@ export interface EngineFeedbackApi {
thumbsUp(): number;
/** Negative feedback (thumbs-down). Returns the FeedbackAction int. */
thumbsDown(speed?: number, spread?: number, pinMask?: Uint8Array): number;
/** Drag (continuous perturbation) tick. */
drag(): number;
setMode(mode: FeedbackMode): void;
getMode(): FeedbackMode;
/** Restrict feedback to a subset of outputs (solo / column-freeze). */
setFocus(mask: Uint8Array | null): void;
/** True while the controller is exploring (perturbed). */
exploring(): boolean;
/** True while the controller has paused learning. */
learningPaused(): boolean;
// ---- ExploreAndPlace lifecycle (shared C++ core; mode 'explore_and_place') --
/** Idle→Exploring: snapshot the real net, randomise a scratchpad. */
@ -54,10 +50,6 @@ export interface EngineFeedbackApi {
commitPlace(): void;
/** Placing→Exploring: back out without storing. */
cancelPlace(): void;
/** True while Placing (the frozen output is held). */
placing(): boolean;
/** ExploreState int: 0=Idle 1=Exploring 2=Placing. */
exploreState(): number;
/** Scratchpad undo-ring depth available to pop. */
undoDepth(): number;
/** The frozen placed / just-committed output (null if none). */
@ -92,9 +84,6 @@ export interface EngineExploreApi {
/** Release: freeze the weights where they landed (permanent). */
joltRelease(): void;
joltActive(): boolean;
/** Post-release LR-ramp multiplier (0 held → 1 over ~5 s of ticks). */
joltLrScale(): number;
joltTickLrRamp(): void;
/** Exploration amount in [0,1]; 0 disables (inert — parity-safe). */
setExploreIntensity(level: number): void;
exploreIntensity(): number;
@ -169,12 +158,10 @@ export class EngineApi {
this.spine.routedOutput() ?? this.spine.outputs(),
pinMask,
),
drag: () => this.iml.feedbackDrag(),
setMode: (mode) => this.iml.feedbackSetMode(mode),
getMode: () => this.iml.feedbackGetMode(),
setFocus: (mask) => this.iml.feedbackSetFocus(mask),
exploring: () => this.iml.feedbackExploring(),
learningPaused: () => this.iml.feedbackLearningPaused(),
enterExplore: (spread = this.spread_) => this.iml.feedbackEnterExplore(spread),
exitExplore: () => this.iml.feedbackExitExplore(),
reroll: (spread = this.spread_) => this.iml.feedbackReroll(spread),
@ -183,8 +170,6 @@ export class EngineApi {
like: () => this.iml.feedbackLike(),
commitPlace: () => this.iml.feedbackCommitPlace(),
cancelPlace: () => this.iml.feedbackCancelPlace(),
placing: () => this.iml.feedbackPlacing(),
exploreState: () => this.iml.feedbackState(),
undoDepth: () => this.iml.feedbackUndoDepth(),
placedOutput: () => this.iml.feedbackPlacedOutput(),
dislikeGeometric: (heardVec?: Float32Array, lr = 0) =>
@ -200,8 +185,6 @@ export class EngineApi {
joltStep: () => this.iml.joltStep(),
joltRelease: () => this.iml.joltRelease(),
joltActive: () => this.iml.joltActive(),
joltLrScale: () => this.iml.joltLrScale(),
joltTickLrRamp: () => this.iml.joltTickLrRamp(),
setExploreIntensity: (level) => this.iml.setExploreIntensity(level),
exploreIntensity: () => this.iml.exploreIntensity(),
exploreApply: (inout) => this.iml.exploreApply(inout),

View file

@ -28,8 +28,6 @@ export interface EngineSink {
setState(patch: EngineStatePatch): void;
/** Publish a fresh output vector (already copied; caller may keep it). */
setOutputs(out: Float32Array): void;
/** Publish a fresh flat weight array. */
setWeights(w: Float32Array): void;
/** Emit a named engine event (`ml.*`, `mode.*`, …) with an optional payload. */
emit(event: string, payload?: unknown): void;
}
@ -38,6 +36,5 @@ export interface EngineSink {
export const noopSink: EngineSink = {
setState() {},
setOutputs() {},
setWeights() {},
emit() {},
};

View file

@ -52,9 +52,9 @@ export interface SpineState {
/**
* The spine doubles as the `EngineSink` consumed by `WasmIML`. WasmIML calls
* `setState/setOutputs/setWeights/emit`; the spine merges into its state,
* stashes the live output/weight buffers, and bumps the version counter so
* `useSyncExternalStore` consumers re-read.
* `setState/setOutputs/emit`; the spine merges into its state, stashes the
* live output buffer, and bumps the version counter so `useSyncExternalStore`
* consumers re-read.
*/
export class Spine implements EngineSink {
private state_: SpineState = {
@ -93,9 +93,8 @@ export class Spine implements EngineSink {
private mlBuf: F32 = new Float32Array(126);
private routedBuf: F32 | null = null;
// Last live output (post-ML, pre-routing) and weights, read imperatively.
// Last live output (post-ML, pre-routing), read imperatively.
private liveOutputs: F32 = new Float32Array(126);
private liveWeights: F32 = new Float32Array(0);
// Optional post-output transform, applied to the routed buffer AFTER the
// output pipeline and BEFORE backend.send (exploration noise — see
@ -149,11 +148,6 @@ export class Spine implements EngineSink {
this.bump_();
}
setWeights(w: Float32Array): void {
this.liveWeights = w;
this.bump_();
}
emit(event: string, payload?: unknown): void {
const set = this.eventListeners.get(event);
if (set) for (const fn of set) fn(payload);
@ -333,10 +327,6 @@ export class Spine implements EngineSink {
return this.routedBuf;
}
weights(): Float32Array {
return this.liveWeights;
}
// ---- useSyncExternalStore plumbing ---------------------------------
subscribe = (cb: () => void): (() => void) => {

View file

@ -32,7 +32,6 @@ export interface NispsModule {
// non-positive/null args fall back to the compiled defaults (32→[10,14,18]→126).
_nisps_ml_create(input_size: number, output_size: number, hidden_ptr: number, n_hidden: number, seed: number): number;
_nisps_ml_destroy(ml: number): void;
_nisps_ml_reset(ml: number): void;
// Reshape = new net at the new dims, warm-started with the overlapping
// weights; feedback state resets. Returns 1 on success (0 = no change).
_nisps_ml_reshape(ml: number, input_size: number, output_size: number, hidden_ptr: number, n_hidden: number, spread: number): number;
@ -50,14 +49,12 @@ export interface NispsModule {
// ML examples.
_nisps_ml_clear_examples(ml: number): void;
_nisps_ml_example_count(ml: number): number;
// ML weights.
_nisps_ml_weight_count(ml: number): number;
_nisps_ml_get_weights(ml: number, out_ptr: number): void;
_nisps_ml_set_weights(ml: number, in_ptr: number): void;
_nisps_ml_draw_weights(ml: number, spread: number): void;
_nisps_ml_move_weights(ml: number, speed: number, spread: number, mask_ptr: number): void;
_nisps_ml_get_layer_stats(ml: number, out_ptr: number): void;
// Null ml reports the DEFAULT shape; a handle reports its runtime shape.
_nisps_ml_describe(ml: number, out_ptr: number): void;
@ -68,7 +65,6 @@ export interface NispsModule {
_nisps_ml_feedback_set_mode(ml: number, mode: number): void;
_nisps_ml_feedback_get_mode(ml: number): number;
_nisps_ml_feedback_exploring(ml: number): number; // 1 = exploring
_nisps_ml_feedback_learning_paused(ml: number): number; // 1 = paused
_nisps_ml_feedback_set_focus(ml: number, mask_ptr: number, n: number): void;
_nisps_ml_feedback_down(
ml: number,
@ -78,7 +74,6 @@ export interface NispsModule {
pin_mask_ptr: number,
): number;
_nisps_ml_feedback_up(ml: number): number;
_nisps_ml_feedback_drag(ml: number): number;
// Returns 1 if `out` holds a static-bypass vector (skip process()); else 0.
_nisps_ml_feedback_static_output(ml: number, out_ptr: number): number;
@ -94,8 +89,6 @@ export interface NispsModule {
_nisps_ml_feedback_like(ml: number): void; // Exploring→Placing (freeze output)
_nisps_ml_feedback_commit_place(ml: number): void; // Placing→Idle (restore real net)
_nisps_ml_feedback_cancel_place(ml: number): void; // Placing→Exploring
_nisps_ml_feedback_placing(ml: number): number; // 1 = Placing
_nisps_ml_feedback_state(ml: number): number; // 0=Idle 1=Exploring 2=Placing
_nisps_ml_feedback_undo_depth(ml: number): number;
// Writes placed/committed output (outputSize floats) into out; returns 1 if written.
_nisps_ml_feedback_placed_output(ml: number, out_ptr: number): number;
@ -123,8 +116,6 @@ export interface NispsModule {
_nisps_ml_jolt_step(ml: number): void;
_nisps_ml_jolt_release(ml: number): void;
_nisps_ml_jolt_active(ml: number): number; // 1 = held/active
_nisps_ml_jolt_lr_scale(ml: number): number; // post-release LR ramp multiplier
_nisps_ml_jolt_tick_lr_ramp(ml: number): void;
_nisps_ml_explore_intensity(ml: number, level: number): void;
_nisps_ml_explore_get_intensity(ml: number): number;
_nisps_ml_explore_apply(ml: number, inout_ptr: number, n: number): void;
@ -145,9 +136,6 @@ export interface NispsModule {
// In place: processes the first n floats of inout_ptr.
_nisps_output_process(p: number, inout_ptr: number, n: number, dt_s: number): void;
_nisps_output_reset(p: number): void;
_nisps_pipeline_state_size(p: number): number;
_nisps_pipeline_save_state(p: number, out_ptr: number): void;
_nisps_pipeline_load_state(p: number, in_ptr: number, n: number): void;
// Curve catalog (one-core-engine P4). ids 0..6 = nisps::Curve (param ignored);
// id 7 = centred power (param = exponent). nisps/core/math.hpp is the single

View file

@ -8,7 +8,7 @@
* `mlStore.__setState(produce(...))` / `mlStore.__setOutputs(...)` /
* `mlStore.__setWeights(...)` / `coreBus.emit(...)`, this class calls the
* injected {@link EngineSink} (`sink.setState({...})` with a PLAIN patch
* object no `produce` mutator, `sink.setOutputs/setWeights/emit`).
* object no `produce` mutator, `sink.setOutputs/emit`).
* - Glue + WASM URLs resolve via `import.meta.env.BASE_URL` (not `/nisps.*`).
* - The `nisps_ml_feedback_*` C ABI (already exported by the WASM build) is
* now bound and surfaced via the `feedback*` methods. The playground never
@ -233,7 +233,6 @@ export class WasmIML {
ready: true,
});
this.sink.setOutputs(new Float32Array(this.arch_.outputSize));
this.publishWeights_();
this.tryLoadFromStorage_();
}
@ -396,7 +395,6 @@ export class WasmIML {
lossHistory: [],
});
this.sink.setOutputs(new Float32Array(this.arch_.outputSize));
this.publishWeights_();
this.sink.emit('ml.reshaped', {
inputSize: this.arch_.inputSize,
outputSize: this.arch_.outputSize,
@ -616,7 +614,6 @@ export class WasmIML {
// The C++ MLP stores per-iter history but it isn't exposed via the WASM
// bindings yet, so this is a single-element array.
this.sink.setState({ lastLoss: loss, lossHistory: [loss] });
this.publishWeights_();
this.sink.emit('ml.trained', { loss });
this.scheduleSave_();
return loss;
@ -677,18 +674,10 @@ export class WasmIML {
randomiseWeights(spread = 0.6): void {
this.module._nisps_ml_draw_weights(this.mlHandle, spread);
this.publishWeights_();
this.sink.emit('ml.delta_update', { reason: 'randomise' });
this.scheduleSave_();
}
moveWeights(speed: number, spread: number, pinMask?: Uint8Array): void {
const maskPtr = this.writePinMask_(pinMask);
this.module._nisps_ml_move_weights(this.mlHandle, speed, spread, maskPtr);
this.publishWeights_();
this.sink.emit('ml.delta_update', { reason: 'thumbs_down' });
}
private writePinMask_(pinMask?: Uint8Array): number {
if (!pinMask) return 0;
const sz = Math.min(pinMask.length, this.arch_.outputSize);
@ -717,10 +706,6 @@ export class WasmIML {
return this.module._nisps_ml_feedback_exploring(this.mlHandle) === 1;
}
feedbackLearningPaused(): boolean {
return this.module._nisps_ml_feedback_learning_paused(this.mlHandle) === 1;
}
/** Restrict feedback to a subset of outputs (solo / focus). null clears it. */
feedbackSetFocus(mask: Uint8Array | null): void {
if (!mask || mask.length === 0) {
@ -735,7 +720,6 @@ export class WasmIML {
/** Positive feedback (thumbs-up). Returns the FeedbackAction int. */
feedbackUp(): number {
const action = this.module._nisps_ml_feedback_up(this.mlHandle);
this.publishWeights_();
this.sink.emit('feedback.up', { action });
this.scheduleSave_();
return action;
@ -755,19 +739,11 @@ export class WasmIML {
}
const maskPtr = this.writePinMask_(pinMask);
const action = this.module._nisps_ml_feedback_down(this.mlHandle, outPtr, speed, spread, maskPtr);
this.publishWeights_();
this.sink.emit('feedback.down', { action });
this.scheduleSave_();
return action;
}
/** Drag (continuous perturbation) tick. Returns the FeedbackAction int. */
feedbackDrag(): number {
const action = this.module._nisps_ml_feedback_drag(this.mlHandle);
this.publishWeights_();
return action;
}
/**
* If a static bypass vector is active, copies it into `out` and returns true
* (the caller should NOT call process()); otherwise returns false.
@ -784,38 +760,32 @@ export class WasmIML {
// ---- ExploreAndPlace lifecycle (shared C++ core; mode 'explore_and_place') --
// The C++ core owns the weight snapshot / scratchpad / undo ring; THIS class
// only forwards calls + republishes weights. Example-storage + training stay
// with the caller (FeedbackController.ts), preserving the "caller owns
// training" contract.
// only forwards calls. Example-storage + training stay with the caller
// (FeedbackController.ts), preserving the "caller owns training" contract.
/** Idle→Exploring: snapshot the real net, randomise a scratchpad. */
feedbackEnterExplore(spread: number): void {
this.module._nisps_ml_feedback_enter_explore(this.mlHandle, spread);
this.publishWeights_();
}
/** Exploring→Idle: restore the real net, discard the scratchpad. */
feedbackExitExplore(): void {
this.module._nisps_ml_feedback_exit_explore(this.mlHandle);
this.publishWeights_();
}
/** Exploring scratchpad op: re-randomise (undoable). */
feedbackReroll(spread: number): void {
this.module._nisps_ml_feedback_reroll(this.mlHandle, spread);
this.publishWeights_();
}
/** Exploring scratchpad op: small bounded perturbation (undoable). */
feedbackNudge(amount: number): void {
this.module._nisps_ml_feedback_nudge(this.mlHandle, amount);
this.publishWeights_();
}
/** Exploring scratchpad op: undo the last reroll/nudge. */
feedbackUndo(): void {
this.module._nisps_ml_feedback_undo(this.mlHandle);
this.publishWeights_();
}
/** Exploring→Placing: freeze the scratchpad output at its current input. */
@ -826,7 +796,6 @@ export class WasmIML {
/** Placing→Idle: restore the real net. Caller then stores +1 + trains. */
feedbackCommitPlace(): void {
this.module._nisps_ml_feedback_commit_place(this.mlHandle);
this.publishWeights_();
}
/** Placing→Exploring: back out without storing. */
@ -834,15 +803,6 @@ export class WasmIML {
this.module._nisps_ml_feedback_cancel_place(this.mlHandle);
}
feedbackPlacing(): boolean {
return this.module._nisps_ml_feedback_placing(this.mlHandle) === 1;
}
/** ExploreState: 0=Idle 1=Exploring 2=Placing. */
feedbackState(): number {
return this.module._nisps_ml_feedback_state(this.mlHandle);
}
feedbackUndoDepth(): number {
return this.module._nisps_ml_feedback_undo_depth(this.mlHandle);
}
@ -867,7 +827,7 @@ export class WasmIML {
* Geometric dislike: push the current mapping away from the liked centroid.
* `heardVec` is the kDefaultOutputs vector the user is HEARING (post-pipeline
* with a null/raw vector the cold-start has a zero MSE derivative and is inert).
* `lr <= 0` uses the C++ controller default. Mutates weights republishes.
* `lr <= 0` uses the C++ controller default. Mutates weights in place.
* Returns the FeedbackAction int (14=GeometricPush, 15=GeometricColdStart).
*/
feedbackDislikeGeometric(heardVec?: Float32Array, lr = 0): number {
@ -879,7 +839,6 @@ export class WasmIML {
outPtr = this.feedbackBuf.ptr;
}
const action = this.module._nisps_ml_feedback_dislike_geometric(this.mlHandle, outPtr, lr);
this.publishWeights_();
this.sink.emit('feedback.down', { action });
this.scheduleSave_();
return action;
@ -925,10 +884,9 @@ export class WasmIML {
}
/** One ~200 Hz morph tick while held (no-op when inactive). C-side getglide
* set of the flat weights; republish so weight-health views + persistence follow. */
* set of the flat weights; scheduleSave_ persists the result. */
joltStep(): void {
this.module._nisps_ml_jolt_step(this.mlHandle);
this.publishWeights_();
this.scheduleSave_();
}
@ -941,15 +899,6 @@ export class WasmIML {
return this.module._nisps_ml_jolt_active(this.mlHandle) === 1;
}
/** Post-release LR-ramp multiplier (0 while held → 1 over ~5 s of ticks). */
joltLrScale(): number {
return this.module._nisps_ml_jolt_lr_scale(this.mlHandle);
}
joltTickLrRamp(): void {
this.module._nisps_ml_jolt_tick_lr_ramp(this.mlHandle);
}
/** Exploration amount in [0,1]; 0 disables (inert — parity-safe). */
setExploreIntensity(level: number): void {
this.module._nisps_ml_explore_intensity(this.mlHandle, level);
@ -987,7 +936,6 @@ export class WasmIML {
}
this.weightsBuf.view.set(w as Float32Array, 0);
this.module._nisps_ml_set_weights(this.mlHandle, this.weightsBuf.ptr);
this.publishWeights_();
}
getLayerStats(): LayerStats[] {
@ -1010,20 +958,6 @@ export class WasmIML {
return new Float32Array(this.statsBuf.view);
}
// -------------------------------------------------------------------
// Misc
// -------------------------------------------------------------------
reset(): void {
this.module._nisps_ml_reset(this.mlHandle);
this.dataset.clear();
this.lastLoss_ = null;
this.sink.setState({ exampleCount: 0, lastLoss: null, lossHistory: [] });
this.publishWeights_();
this.sink.emit('ml.examples_cleared', undefined);
this.scheduleSave_();
}
// -------------------------------------------------------------------
// Persistence
// -------------------------------------------------------------------
@ -1089,9 +1023,4 @@ export class WasmIML {
console.warn('[wasm-iml] tryLoadFromStorage failed:', err);
}
}
private publishWeights_(): void {
const w = this.getWeights();
this.sink.setWeights(w);
}
}

View file

@ -102,13 +102,6 @@ class NispsProcessor extends AudioWorkletProcessor {
*/
private async init_(bytes: ArrayBuffer, sampleRate: number): Promise<void> {
const memory = new WebAssembly.Memory({ initial: 128, maximum: 4096, shared: false });
const imports: WebAssembly.Imports = {
// Emscripten import "a" group; field names match the generated JS.
a: {
a: () => { throw new Error('wasm aborted'); },
b: () => false, // _emscripten_resize_heap returning 0 disables growth
},
};
const compiled = await WebAssembly.compile(bytes);
// Discover the actual import shape from the module — names like "a",
@ -119,13 +112,20 @@ class NispsProcessor extends AudioWorkletProcessor {
if (!reshaped[desc.module]) reshaped[desc.module] = {} as WebAssembly.ModuleImports;
const mod = reshaped[desc.module] as WebAssembly.ModuleImports;
if (desc.kind === 'function') {
if (desc.name === 'c') {
// unused
if (desc.name === 'a') {
// __abort_js
mod[desc.name] = () => { throw new Error('wasm aborted'); };
} else if (desc.name === 'b') {
// _emscripten_resize_heap: refuse growth in the worklet.
mod[desc.name] = (_size: number) => 0;
} else {
// Unknown import — fail loudly instead of silently returning 0, so a
// missing/renamed Emscripten import surfaces immediately instead of
// manifesting as silent audio corruption.
mod[desc.name] = (..._args: unknown[]) => {
throw new Error(`worklet: missing wasm import "${desc.module}.${desc.name}"`);
};
}
mod[desc.name] = (() => {
// Generic stub: log + return 0.
return (..._args: unknown[]) => 0;
})();
} else if (desc.kind === 'memory') {
mod[desc.name] = memory;
} else if (desc.kind === 'table') {
@ -134,34 +134,12 @@ class NispsProcessor extends AudioWorkletProcessor {
mod[desc.name] = new WebAssembly.Global({ value: 'i32', mutable: true }, 0);
}
}
// For known-needed Emscripten imports, supply real implementations.
for (const desc of importDesc) {
const mod = reshaped[desc.module] as WebAssembly.ModuleImports;
// __abort_js
if (desc.name === 'a' && desc.kind === 'function') {
mod[desc.name] = () => { throw new Error('wasm aborted'); };
}
// _emscripten_resize_heap
if (desc.name === 'b' && desc.kind === 'function') {
mod[desc.name] = (_size: number) => 0; // refuse growth in worklet
}
}
void imports; // silence unused
const wasmInst = await WebAssembly.instantiate(compiled, reshaped);
// Many Emscripten exports use single-letter mangled names. Discover
// by reading the export descriptors.
const exDesc = WebAssembly.Module.exports(compiled);
const exMap = new Map<string, string>(); // logical name → mangled
for (const e of exDesc) {
// The exports list includes both the original (with leading
// underscore for C funcs) and the mangled single-letter alias used
// in the import section. We only see the export side here, but
// Emscripten in modern versions also re-exports the C names with
// their leading-underscore form. Walk both.
exMap.set(e.name, e.name);
}
const exports = wasmInst.exports as Record<string, WebAssembly.ExportValue>;
function pickFn(...names: string[]): (...args: number[]) => number {

View file

@ -52,10 +52,10 @@ See `bindings.cpp` for the full list. Summary:
| Group | Functions |
|-----------|------------------------------------------------------------------------------|
| ML life | `nisps_ml_create`, `nisps_ml_destroy`, `nisps_ml_reset`, `nisps_ml_reshape` |
| ML life | `nisps_ml_create`, `nisps_ml_destroy`, `nisps_ml_reshape` |
| ML I/O | `nisps_ml_set_input`, `nisps_ml_process`, `nisps_ml_outputs`, `nisps_ml_infer_batch` |
| Training | `nisps_ml_add_example`, `nisps_ml_train`, `nisps_ml_eval_loss`, `nisps_ml_clear_examples`, `nisps_ml_example_count` |
| Weights | `nisps_ml_weight_count`, `nisps_ml_get_weights`, `nisps_ml_set_weights`, `nisps_ml_draw_weights`, `nisps_ml_move_weights` |
| Training | `nisps_ml_add_example`, `nisps_ml_train`, `nisps_ml_eval_loss`, `nisps_ml_clear_examples` |
| Weights | `nisps_ml_weight_count`, `nisps_ml_get_weights`, `nisps_ml_set_weights`, `nisps_ml_draw_weights` |
| Diag | `nisps_ml_get_layer_stats`, `nisps_ml_describe` |
| Engines | `nisps_engine_create`, `nisps_engine_destroy`, `nisps_engine_set_params`, `nisps_engine_process_block` |

View file

@ -548,18 +548,6 @@ void nisps_ml_draw_weights(void* ml, float spread) {
h->mlp.draw_weights(spread);
}
EMSCRIPTEN_KEEPALIVE
void nisps_ml_move_weights(void* ml, float speed, float spread,
const uint8_t* output_pin_mask) {
if (!ml) return;
auto* h = static_cast<MLHandle*>(ml);
std::span<const std::uint8_t> mask;
if (output_pin_mask) {
mask = std::span<const std::uint8_t>(output_pin_mask, h->n_out());
}
h->mlp.move_weights(speed, spread, mask);
}
// ---------------------------------------------------------------------------
// ML feedback — the "Down Action" state machine (Avoid / RandomiseOutputs /
// RandomiseMlp). The controller decides WHAT transition happened (returns a
@ -601,12 +589,6 @@ int nisps_ml_feedback_exploring(void* ml) {
return static_cast<MLHandle*>(ml)->feedback.exploring() ? 1 : 0;
}
EMSCRIPTEN_KEEPALIVE
int nisps_ml_feedback_learning_paused(void* ml) {
if (!ml) return 0;
return static_cast<MLHandle*>(ml)->feedback.learning_paused() ? 1 : 0;
}
EMSCRIPTEN_KEEPALIVE
void nisps_ml_feedback_set_focus(void* ml, const uint8_t* mask, int n) {
if (!ml) return;
@ -640,13 +622,6 @@ int nisps_ml_feedback_up(void* ml) {
return static_cast<int>(h->feedback.on_up(h->mlp));
}
EMSCRIPTEN_KEEPALIVE
int nisps_ml_feedback_drag(void* ml) {
if (!ml) return 0;
auto* h = static_cast<MLHandle*>(ml);
return static_cast<int>(h->feedback.on_drag(h->mlp));
}
// If returns 1, `out` (n_out floats) holds the static bypass vector
// and the caller should NOT call nisps_ml_process(); if 0, run process().
EMSCRIPTEN_KEEPALIVE
@ -738,20 +713,6 @@ void nisps_ml_feedback_cancel_place(void* ml) {
h->feedback.cancel_place();
}
// 1 if currently Placing (audition is the frozen vector), else 0.
EMSCRIPTEN_KEEPALIVE
int nisps_ml_feedback_placing(void* ml) {
if (!ml) return 0;
return static_cast<MLHandle*>(ml)->feedback.placing() ? 1 : 0;
}
// ExploreState int: 0=Idle 1=Exploring 2=Placing.
EMSCRIPTEN_KEEPALIVE
int nisps_ml_feedback_state(void* ml) {
if (!ml) return 0;
return static_cast<int>(static_cast<MLHandle*>(ml)->feedback.explore_state());
}
// Scratchpad undo-ring depth currently available to pop.
EMSCRIPTEN_KEEPALIVE
int nisps_ml_feedback_undo_depth(void* ml) {
@ -863,20 +824,6 @@ int nisps_ml_jolt_active(void* ml) {
return static_cast<MLHandle*>(ml)->jolt.active() ? 1 : 0;
}
// Post-release learning-rate ramp: multiply the training LR by this (0 while
// held, ramps back to 1 over ~5 s of ticks).
EMSCRIPTEN_KEEPALIVE
float nisps_ml_jolt_lr_scale(void* ml) {
if (!ml) return 1.f;
return static_cast<MLHandle*>(ml)->jolt.lr_scale();
}
EMSCRIPTEN_KEEPALIVE
void nisps_ml_jolt_tick_lr_ramp(void* ml) {
if (!ml) return;
static_cast<MLHandle*>(ml)->jolt.tick_lr_ramp();
}
// Exploration amount in [0,1]; 0 disables (inert — parity-safe).
EMSCRIPTEN_KEEPALIVE
void nisps_ml_explore_intensity(void* ml, float level) {
@ -914,15 +861,6 @@ void nisps_ml_get_layer_stats(void* ml, float* out_stats) {
}
}
// Extra helper: lets JS query the example count without having to
// shadow-track it. Useful when restoring from snapshot.
EMSCRIPTEN_KEEPALIVE
int nisps_ml_example_count(void* ml) {
if (!ml) return 0;
auto* h = static_cast<MLHandle*>(ml);
return static_cast<int>(h->mlp.example_count());
}
EMSCRIPTEN_KEEPALIVE
void nisps_ml_clear_examples(void* ml) {
if (!ml) return;
@ -930,13 +868,6 @@ void nisps_ml_clear_examples(void* ml) {
h->mlp.clear_examples();
}
EMSCRIPTEN_KEEPALIVE
void nisps_ml_reset(void* ml) {
if (!ml) return;
auto* h = static_cast<MLHandle*>(ml);
h->mlp.reset();
}
// Architecture introspection — writes [in, h1, h2, h3, out, n_layers] into a
// caller-supplied int buffer. Always 6 ints. With a null handle it reports
// the DEFAULT shape (what create() yields for non-positive args); with a
@ -1070,34 +1001,6 @@ void nisps_output_reset(void* p) {
static_cast<PipelineHandle*>(p)->output.reset();
}
// Persistence: [input state (3)] + [output state (1 + 2*count)].
EMSCRIPTEN_KEEPALIVE
int nisps_pipeline_state_size(void* p) {
if (!p) return 0;
auto* h = static_cast<PipelineHandle*>(p);
return static_cast<int>(h->input.state_size() + h->output.state_size());
}
EMSCRIPTEN_KEEPALIVE
void nisps_pipeline_save_state(void* p, float* out) {
if (!p || !out) return;
auto* h = static_cast<PipelineHandle*>(p);
const std::size_t in_n = h->input.state_size();
h->input.save_state(std::span<float>(out, in_n));
h->output.save_state(std::span<float>(out + in_n, h->output.state_size()));
}
EMSCRIPTEN_KEEPALIVE
void nisps_pipeline_load_state(void* p, const float* in, int n) {
if (!p || !in || n <= 0) return;
auto* h = static_cast<PipelineHandle*>(p);
const std::size_t in_n = h->input.state_size();
const std::size_t total = static_cast<std::size_t>(n);
if (total < in_n) return;
h->input.load_state(std::span<const float>(in, in_n));
h->output.load_state(std::span<const float>(in + in_n, total - in_n));
}
// ---------------------------------------------------------------------------
// Curve catalog (one-core-engine P4): nisps/core/math.hpp is the single
// source of truth; the browser samples it instead of mirroring the maths.

View file

@ -39,32 +39,31 @@ mkdir -p "$OUT"
# function is missing.
EXPORTED_FUNCS='[
"_malloc","_free",
"_nisps_ml_create","_nisps_ml_destroy","_nisps_ml_reset","_nisps_ml_reshape",
"_nisps_ml_create","_nisps_ml_destroy","_nisps_ml_reshape",
"_nisps_ml_set_input","_nisps_ml_process","_nisps_ml_outputs","_nisps_ml_infer_batch",
"_nisps_ml_add_example","_nisps_ml_train","_nisps_ml_eval_loss",
"_nisps_ml_clear_examples","_nisps_ml_example_count",
"_nisps_ml_clear_examples",
"_nisps_ml_weight_count","_nisps_ml_get_weights","_nisps_ml_set_weights",
"_nisps_ml_draw_weights","_nisps_ml_move_weights",
"_nisps_ml_draw_weights",
"_nisps_ml_feedback_set_mode","_nisps_ml_feedback_get_mode",
"_nisps_ml_feedback_exploring","_nisps_ml_feedback_learning_paused",
"_nisps_ml_feedback_exploring",
"_nisps_ml_feedback_set_focus","_nisps_ml_feedback_down",
"_nisps_ml_feedback_up","_nisps_ml_feedback_drag","_nisps_ml_feedback_static_output",
"_nisps_ml_feedback_up","_nisps_ml_feedback_static_output",
"_nisps_ml_feedback_enter_explore","_nisps_ml_feedback_exit_explore",
"_nisps_ml_feedback_reroll","_nisps_ml_feedback_nudge","_nisps_ml_feedback_undo",
"_nisps_ml_feedback_like","_nisps_ml_feedback_commit_place","_nisps_ml_feedback_cancel_place",
"_nisps_ml_feedback_placing","_nisps_ml_feedback_state","_nisps_ml_feedback_undo_depth",
"_nisps_ml_feedback_undo_depth",
"_nisps_ml_feedback_placed_output",
"_nisps_ml_feedback_dislike_geometric","_nisps_ml_feedback_store_positive",
"_nisps_ml_feedback_positive_count","_nisps_ml_feedback_negative_count",
"_nisps_ml_feedback_set_avoid_style",
"_nisps_ml_jolt_press","_nisps_ml_jolt_step","_nisps_ml_jolt_release",
"_nisps_ml_jolt_active","_nisps_ml_jolt_lr_scale","_nisps_ml_jolt_tick_lr_ramp",
"_nisps_ml_jolt_active",
"_nisps_ml_explore_intensity","_nisps_ml_explore_get_intensity","_nisps_ml_explore_apply",
"_nisps_pipeline_create","_nisps_pipeline_destroy",
"_nisps_input_set_config","_nisps_input_process","_nisps_input_reset",
"_nisps_output_set_config","_nisps_output_set_freeze_mask",
"_nisps_output_process","_nisps_output_reset",
"_nisps_pipeline_state_size","_nisps_pipeline_save_state","_nisps_pipeline_load_state",
"_nisps_curve_apply","_nisps_curve_apply_batch",
"_nisps_ml_get_layer_stats","_nisps_ml_describe",
"_nisps_engine_create","_nisps_engine_destroy",

View file

@ -100,7 +100,6 @@ function bind(Module) {
weightCount: cwrap('nisps_ml_weight_count', 'number', ['number']),
getWeights: cwrap('nisps_ml_get_weights', null, ['number','number']),
drawWeights: cwrap('nisps_ml_draw_weights', null, ['number','number']),
moveWeights: cwrap('nisps_ml_move_weights', null, ['number','number','number','number']),
feedbackSetMode: cwrap('nisps_ml_feedback_set_mode', null, ['number','number']),
feedbackDown: cwrap('nisps_ml_feedback_down', 'number', ['number','number','number','number','number']),
feedbackUp: cwrap('nisps_ml_feedback_up', 'number', ['number']),