Record JamesDSP integration verification results

This commit is contained in:
Jonathan Sykes
2026-10-09 21:08:58 +08:00
parent 6bb3271431
commit 7591c2d14c
3 changed files with 27595 additions and 2 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -10,7 +10,7 @@ import {createRequire} from 'node:module';
import {chromium,webkit} from 'playwright'; import {chromium,webkit} from 'playwright';
const repo=resolve(import.meta.dirname,'..'),args=process.argv.slice(2); const repo=resolve(import.meta.dirname,'..'),args=process.argv.slice(2);
const opt=(k,d)=>args.includes(k)?args[args.indexOf(k)+1]:d; const opt=(k,d)=>args.includes(k)?args[args.indexOf(k)+1]:d;
const oldTag='1175f1a1d2c1',target=opt('--target','main'),engine=opt('--browser','all'); const oldTag='1175f1a1d2c1',target=opt('--target','HEAD'),engine=opt('--browser','all');
assert.ok(['all','chromium','webkit'].includes(engine)); assert.ok(['all','chromium','webkit'].includes(engine));
execFileSync('bash',[join(repo,'perf/make-shell-fixture.sh'),'b77938a',oldTag],{cwd:repo,stdio:'inherit'}); execFileSync('bash',[join(repo,'perf/make-shell-fixture.sh'),'b77938a',oldTag],{cwd:repo,stdio:'inherit'});
const scratch=mkdtempSync(join(repo,'perf/.tmp/settings-')); const scratch=mkdtempSync(join(repo,'perf/.tmp/settings-'));
@@ -70,7 +70,9 @@ async function run(name){
const {migrate}=require(join(scratch,'new/frontend/eq-parameters.js'));const expected=structuredClone(result.old);if(expected.eq?.g?.length===5)expected.eq.g=migrate(expected.eq.g); const {migrate}=require(join(scratch,'new/frontend/eq-parameters.js'));const expected=structuredClone(result.old);if(expected.eq?.g?.length===5)expected.eq.g=migrate(expected.eq.g);
for(const key of new Set([...Object.keys(result.old),...Object.keys(result.upgraded)]))result.diff.push({key,before:result.old[key],after:result.upgraded[key],equal:JSON.stringify(result.old[key])===JSON.stringify(result.upgraded[key]),expectedMigration:key==='eq'&&result.old.eq?.g?.length===5}); for(const key of new Set([...Object.keys(result.old),...Object.keys(result.upgraded)]))result.diff.push({key,before:result.old[key],after:result.upgraded[key],equal:JSON.stringify(result.old[key])===JSON.stringify(result.upgraded[key]),expectedMigration:key==='eq'&&result.old.eq?.g?.length===5});
assert.deepEqual(result.upgraded,expected,'all persisted settings preserved except deliberate 5→10 EQ migration');await behaviour('upgraded'); assert.deepEqual(result.upgraded,expected,'all persisted settings preserved except deliberate 5→10 EQ migration');await behaviour('upgraded');
await page.evaluate(()=>{data.settings.jamesDsp={enabled:false,preset:'warm',params:{'eq.band0':3,'tube.drive':1.5}};persist();});await page.reload();await page.waitForFunction(()=>typeof data!=='undefined');await openSettings();await page.locator('[data-section="playback"]').click(); await openSettings();await page.locator('[data-section="playback"]').evaluate(e=>e.click());await page.locator('#setDspPreset').evaluate(e=>{e.value='warm';e.dispatchEvent(new Event('change',{bubbles:true}));});
for(const [key,value] of [['eq.band0','3'],['tube.drive','1.5']])await page.locator(`[data-dsp-param="${key}"]`).evaluate((e,v)=>{e.value=v;e.dispatchEvent(new Event('input',{bubbles:true}));},value);
await sleep(600);await page.reload();await page.waitForFunction(()=>typeof data!=='undefined');await openSettings();await page.locator('[data-section="playback"]').evaluate(e=>e.click());
const james=await page.evaluate(()=>data.settings.jamesDsp);assert.deepEqual(james,{enabled:false,preset:'warm',params:{'eq.band0':3,'tube.drive':1.5}});assert.equal(await page.locator('#setSoundEngine').inputValue(),'standard');assert.equal(await page.locator('#setDspPreset').inputValue(),'warm');result.jamesDspSettingsPersisted=true; const james=await page.evaluate(()=>data.settings.jamesDsp);assert.deepEqual(james,{enabled:false,preset:'warm',params:{'eq.band0':3,'tube.drive':1.5}});assert.equal(await page.locator('#setSoundEngine').inputValue(),'standard');assert.equal(await page.locator('#setDspPreset').inputValue(),'warm');result.jamesDspSettingsPersisted=true;
await page.evaluate(()=>{data.settings.jamesDsp.enabled=false;persist();}); await page.evaluate(()=>{data.settings.jamesDsp.enabled=false;persist();});
const local=await settings(),remote={...local,saveBeforePlay:false,autoPreload:false,layout:'classic',quality:'auto'}; const local=await settings(),remote={...local,saveBeforePlay:false,autoPreload:false,layout:'classic',quality:'auto'};

View File

@@ -0,0 +1,29 @@
# JamesDSP integration report — 2026-10-09
Phases 1–5 are implemented in five focused commits. Standard remains the default and its EQ construction is byte-for-byte checked against the pre-refactor fixture. JamesDSP lives only in the lazy `feature:jamesdsp` group; the service worker shell has no DSP assets. The shared `AudioGraph` owns the sole context and one source per media element. Enabling JamesDSP creates one worklet and one WASM heap; errors restore Standard routing. iOS remains gated by `liveAudioIOS`. Uploaded impulse responses are session-only, and Liveprog/DDC/arbitrary text effects are excluded. The worklet uses a static, pinned relative module import because AudioWorkletGlobalScope rejects dynamic import.
Verification:
| Check | Result |
|---|---|
| Frontend tests | 266 passed, 0 failed |
| Server suite | Passed with the integration test venv on PATH |
| Offline completion | Chromium + WebKit: 109/109 runtime files, zero missing/failures; all four layouts |
| Lazy / hidden timers | Both engines pass; hidden-timers has no page errors |
| UI geometry / icons | 384 geometry cases and 24 icon cases pass |
| Settings persistence | 24 settings keys unchanged through old-client update; Chromium OPFS save/play passes before and after update. Linux WebKit lacks native OPFS. DSP preset persists while disabled. |
| DSP harness | Both engines pass all seven presets, Standard comparison, A/B bypass, track switch, offline reload and corrupt-WASM fallback. One heap, one worklet, two pre-existing media sources. |
| Migration | Both engines pass unthrottled: one banner/reload, 142 cached entries, offline reload and playback guard. Lossy Chromium repeatedly times out waiting for the new stamped page after the legacy user-triggered refresh, despite candidate install and `CACHE_STATUS ready:true`; WebKit lossy was not reached. |
Three-run cold medians versus `plans/integration-report.md`:
| Engine/profile | FCP before → after | Boot before → after | Cold wire before → after | Positive `app.js` bodies |
|---|---:|---:|---:|---:|
| Chromium LTE | 1360 → 1360 ms | 6457 → 6677 ms | 648 KB → 1.82 MB | 1 |
| Chromium lossy | 1992 → 1936 ms | 10407 → 10582 ms | 648 KB → 1.82 MB | 1 |
| WebKit LTE | 895 → 904 ms | 2307 → 2374 ms | 976 KB → 2.14 MB | 1 |
| WebKit lossy | 2744 → 2751 ms | 5642 → 5757 ms | 983 KB → 2.15 MB | 1 |
FCP/boot are within 4% of the integrated build. Cold-session wire grows by the approximately 1.2 MB upstream WASM, fetched after paint by the required completion job so JamesDSP remains available offline. This is a real first-install transfer cost. The cold results are in `perf/results/dsp-cold-check.json`.
No real iPhone, locked-screen playback, thermal/CPU timing, real-ear preset tuning, crossfade, P2P, or active-DSP saved/OPFS playback was verified. Phase 7 beta push/deploy was intentionally not run: this task explicitly forbids push/deploy. Do not merge or enable the engine by default until the lossy migration handoff is resolved and an iPhone beta check validates lock-screen expectations and CPU cost.