memlnaut-nisps/manifold/tests/engine-notification-hot-path.test.ts
2026-07-25 17:11:24 +02:00

65 lines
2 KiB
TypeScript

import { expect, test } from 'bun:test';
import { Spine } from '../src/engine/spine';
import type { WasmIML } from '../src/engine/wasm-iml';
import { InputLayer } from '../src/inputs/input-layer';
import { XYPadSource } from '../src/inputs/xy-pad-source';
function makeFakeIml(outputSize = 20): WasmIML {
return {
architecture: {
inputSize: 2,
hidden: [0, 0, 0] as [number, number, number],
outputSize,
numLayers: 3,
},
setInputConfig: () => {},
setOutputConfig: () => {},
setOutputFreezeMask: () => {},
resetInput: () => {},
resetOutput: () => {},
processInput: (x: number, y: number) => ({ x, y, frozen: false }),
setInput: () => {},
processInto: (buf: Float32Array) => buf.fill(0.25),
processOutput: () => {},
} as unknown as WasmIML;
}
test('live inference publishes through the output channel without notifying state subscribers', () => {
const spine = new Spine();
spine.attach(makeFakeIml(), null);
spine.setState({ outputSize: 20 });
let stateNotifications = 0;
let outputNotifications = 0;
spine.subscribe(() => stateNotifications++);
spine.subscribeOutputs(() => outputNotifications++);
spine.setInputs([0.2, 0.8]);
spine.setInputs([0.3, 0.7]);
expect(stateNotifications).toBe(0);
expect(outputNotifications).toBe(1);
expect(spine.outputVersion()).toBe(2);
expect(Array.from(spine.outputs())).toEqual(new Array(20).fill(0.25));
});
test('InputLayer reuses its reduced input vector across animation frames', () => {
const layer = new InputLayer();
const source = new XYPadSource();
const writes: ReadonlyArray<number>[] = [];
layer.attach({
architecture: { inputSize: 2 },
setInputs: (values) => writes.push(values),
});
layer.setSources([source]);
source.pushAxes(0.2, 0.8);
layer.frame();
source.pushAxes(0.3, 0.7);
layer.frame();
expect(writes).toHaveLength(2);
expect(writes[0]).toBe(writes[1]);
expect(Array.from(writes[1])).toEqual(
Array.from(new Float32Array([0.3, 0.7])),
);
});