New SLP-Workshop firmware variant (Synth Library Portland), built on the MEMLCelium engine + MLP shape. Ports the two post-fork learning-algorithm changes from upstream memllib InterfaceRL into the shared nisps/ml core, runtime-configurable (no compile-time switch), inert by default: - nisps/ml/jolt.hpp: Jolt — held continuous weight morph over the flat weight buffer + post-release LR ramp (kJolt* constants verbatim). - nisps/ml/ou_noise.hpp: OUNoise<N> — Ornstein-Uhlenbeck exploration walk on the output vector (theta=0.02, dt=0.001, kMaxAmplitude=0.65). Both wired into ModeBase so every mode gains jolt_press/jolt_release/ jolt_lr_scale + set_explore_intensity; gated so existing modes stay bit-identical (parity + golden tests green). Firmware surfaces them on TogB1 (Jolt) and RVX1 (explore). New SLPWorkshopMode mode + schema + codegen; firmware alias + .ino variant; playground mode registration. Tests: jolt + OU unit tests, ModeBase learning integration incl. an inert-parity test proving SLP-Workshop == MEMLCelium with features off. Verified: cpp tests, wasm build, native↔wasm parity, lint, codegen golden, playground typecheck. Firmware compile/e2e/hardware are environment-bound (no arduino-cli/submodules/browser here). Refs ergo 019f0fca.
93 lines
3.4 KiB
TypeScript
93 lines
3.4 KiB
TypeScript
/**
|
|
* Mode-cycling smoke tests.
|
|
*
|
|
* For each registered mode, navigate to it and confirm:
|
|
* - The page renders without throwing an unhandled error.
|
|
* - No console errors fired.
|
|
* - The mode shell paints something visible.
|
|
*
|
|
* We don't enumerate per-mode controls here — that's the job of mode-
|
|
* specific specs. This spec exists to catch the obvious "mode X is broken
|
|
* after refactoring" regressions.
|
|
*/
|
|
import { test, expect } from '@playwright/test';
|
|
import { loadApp } from './helpers';
|
|
|
|
// Keep in lockstep with playground/src/modes/index.ts MODE_REGISTRY. If
|
|
// this list drifts, the test will fail loud — that's the desired UX.
|
|
const MODE_IDS = [
|
|
'paf_synth',
|
|
'channel_strip',
|
|
'xiasri',
|
|
'verb_fx',
|
|
'memlcelium',
|
|
'slp_workshop',
|
|
'breakor',
|
|
'elysiamorf',
|
|
'sound_analysis_midi',
|
|
'c15',
|
|
];
|
|
|
|
for (const modeId of MODE_IDS) {
|
|
test(`mode "${modeId}" loads without console errors`, async ({ page }) => {
|
|
const errors: string[] = [];
|
|
page.on('pageerror', (err) => errors.push(String(err)));
|
|
page.on('console', (msg) => {
|
|
if (msg.type() === 'error') errors.push(`[${msg.type()}] ${msg.text()}`);
|
|
});
|
|
|
|
// Pre-seed the active mode in localStorage so ModesPage selects it on
|
|
// mount. The mode-store key is `nisps-mode-store` (see mode-store.ts).
|
|
// If the store key changes, this test fails with the active mode just
|
|
// being whatever ModeSwitcher picks first — surface that failure rather
|
|
// than papering over it.
|
|
await page.addInitScript((id) => {
|
|
try {
|
|
localStorage.clear();
|
|
localStorage.setItem('nisps-mode-store', JSON.stringify({ activeModeId: id }));
|
|
} catch {
|
|
/* private mode etc — ignore */
|
|
}
|
|
}, modeId);
|
|
|
|
// Don't block on WASM readiness — we're testing that the mode TSX
|
|
// renders without throwing, not that ML is live. WASM bring-up is
|
|
// covered by ml-engine.spec.ts.
|
|
await loadApp(page, { route: '/modes', waitForReady: false });
|
|
|
|
// Filter out benign noise — Vite preview emits livereload warnings
|
|
// sometimes, and Emscripten can log harmless messages during init.
|
|
// Stream 7's WASM glue currently fails ESM resolution in production —
|
|
// mute that until stream 10 fixes it.
|
|
const realErrors = errors.filter((e) =>
|
|
!/sourcemap/i.test(e) &&
|
|
!/favicon/i.test(e) &&
|
|
!/livereload/i.test(e) &&
|
|
!/module factory/i.test(e) &&
|
|
!/wasm-iml/i.test(e)
|
|
);
|
|
expect(realErrors, `${modeId} errors:\n${realErrors.join('\n')}`).toEqual([]);
|
|
});
|
|
}
|
|
|
|
test('cycling through all modes leaves probe alive', async ({ page }) => {
|
|
// Smoke test: visit each mode in sequence within one tab; if any mode
|
|
// tears down the WASM or breaks the probe, the final assertion fails.
|
|
await loadApp(page, { waitForReady: false });
|
|
for (const id of MODE_IDS) {
|
|
await page.evaluate((mid) => {
|
|
try {
|
|
localStorage.setItem('nisps-mode-store', JSON.stringify({ activeModeId: mid }));
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
}, id);
|
|
await page.reload();
|
|
await page.waitForFunction(() => typeof window.__nisps !== 'undefined', { timeout: 15_000 });
|
|
}
|
|
const ready = await page.evaluate(() => window.__nisps!.__ready);
|
|
// Probe should reinitialise across reloads. If __ready is `false`, the
|
|
// probe's lazyInit promise is still pending, but that's still a passing
|
|
// test — we just care that nothing exploded.
|
|
expect(typeof ready).toBe('boolean');
|
|
});
|