2dedc35turing-sphere: reaction-diffusion on the sphere, spectral solver on WebGPUJeremy Magland 1/**
35d91faDelete the TypeScript solver; the .m models are the only implementationJeremy Magland 2 * Browser validation, in the environment the demo actually ships to.
3 *
4 * Runs the same three check modules as `npm run test:node` — so both GPU stacks
5 * (Dawn on the desktop, the browser's own here) get the same guarantees — plus a
6 * long soak that only makes sense in a page.
7 *
2dedc35turing-sphere: reaction-diffusion on the sphere, spectral solver on WebGPUJeremy Magland 8 * Results are posted to window.__RESULTS__ for the headless runner.
9 */
35d91faDelete the TypeScript solver; the .m models are the only implementationJeremy Magland 10import { requestShtDevice } from '../src/sht/sht.ts';
11import { ModelSession } from '../src/mgpu/session.ts';
12import { mModels, defaultParams } from '../src/mgpu/registry.ts';
13import { transformChecks } from './transformChecks.ts';
14import { analyticChecks } from './analyticChecks.ts';
15import { modelChecks } from './modelChecks.ts';
17declare global {
18 interface Window {
19 __RESULTS__?: { ok: boolean; fatal?: string; lines: string[] };
20 }
21}
23const logEl = document.getElementById('log')!;
24const lines: string[] = [];
25let failures = 0;
27function log(s: string): void {
28 lines.push(s);
29 logEl.textContent = lines.join('\n');
30 console.log(s);
31}
33function check(name: string, ok: boolean, detail: string): void {
34 log(`${ok ? 'PASS' : 'FAIL'} ${name} ${detail}`);
35 if (!ok) failures++;
36}
35d91faDelete the TypeScript solver; the .m models are the only implementationJeremy Magland 39 * Solver-only soak, selected with ?soak=<steps>&lmax=<n>. No rendering, so it
40 * isolates the compiled .m and the transforms from three.js.
42async function soak(steps: number, lmax: number): Promise<void> {
43 const device = await requestShtDevice();
35d91faDelete the TypeScript solver; the .m models are the only implementationJeremy Magland 44 const model = mModels[0];
45 const session = await ModelSession.create({
46 device,
47 model,
48 params: defaultParams(model),
49 lmax,
50 });
51 session.seed(5);
52 log(
53 `soak: ${steps} steps at lmax ${lmax} ` +
54 `(grid ${session.cfg.nlat}x${session.cfg.nphi}), solver only`,
55 );
e7bcd70Add soak, live-check and solver-only soak toolingJeremy Magland 56
35d91faDelete the TypeScript solver; the .m models are the only implementationJeremy Magland 57 const BATCH = 25;
e7bcd70Add soak, live-check and solver-only soak toolingJeremy Magland 58 const t0 = performance.now();
35d91faDelete the TypeScript solver; the .m models are the only implementationJeremy Magland 59 for (let s = 0; s < steps; s += BATCH) {
60 session.step(Math.min(BATCH, steps - s));
61 const u = await session.read(model.species[0]);
62 let lo = Infinity;
63 let hi = -Infinity;
64 for (const v of u) {
65 if (v < lo) lo = v;
66 if (v > hi) hi = v;
67 }
68 if ((s + BATCH) % 100 === 0) {
69 const mem = (performance as Performance & { memory?: { usedJSHeapSize: number } })
70 .memory;
35d91faDelete the TypeScript solver; the .m models are the only implementationJeremy Magland 72 ` step ${session.steps} u in [${lo.toFixed(4)}, ${hi.toFixed(4)}]` +
e7bcd70Add soak, live-check and solver-only soak toolingJeremy Magland 73 (mem ? ` heap ${(mem.usedJSHeapSize / 1048576).toFixed(1)} MB` : ''),
74 );
75 // yield so the page stays responsive and the runner can poll
76 await new Promise((r) => setTimeout(r, 0));
77 }
78 }
79 const ms = (performance.now() - t0) / steps;
81 const final = await session.read(model.species[0]);
35d91faDelete the TypeScript solver; the .m models are the only implementationJeremy Magland 83 for (const v of final) if (!Number.isFinite(v)) finite = false;
e7bcd70Add soak, live-check and solver-only soak toolingJeremy Magland 84 check(`soak: ${steps} steps survived`, finite, `${ms.toFixed(1)} ms/step`);
86 session.destroy();
e7bcd70Add soak, live-check and solver-only soak toolingJeremy Magland 87 window.__RESULTS__ = { ok: failures === 0, lines };
88 log(failures === 0 ? 'ALL PASS' : `${failures} FAILURE(S)`);
89}
2dedc35turing-sphere: reaction-diffusion on the sphere, spectral solver on WebGPUJeremy Magland 91async function main(): Promise<void> {
e7bcd70Add soak, live-check and solver-only soak toolingJeremy Magland 92 const q = new URLSearchParams(location.search);
93 if (q.has('soak')) {
94 return soak(Number(q.get('soak')) || 500, Number(q.get('lmax')) || 63);
95 }
2dedc35turing-sphere: reaction-diffusion on the sphere, spectral solver on WebGPUJeremy Magland 96 const device = await requestShtDevice();
35d91faDelete the TypeScript solver; the .m models are the only implementationJeremy Magland 98 await transformChecks(device, check, log);
99 await analyticChecks(device, check, log);
100 await modelChecks(device, check, log);
2dedc35turing-sphere: reaction-diffusion on the sphere, spectral solver on WebGPUJeremy Magland 102 window.__RESULTS__ = { ok: failures === 0, lines };
103 log(failures === 0 ? 'ALL PASS' : `${failures} FAILURE(S)`);
104}
106main().catch((e) => {
107 const msg = e instanceof Error ? `${e.message}\n${e.stack ?? ''}` : String(e);
108 log(`fatal: ${msg}`);
109 window.__RESULTS__ = { ok: false, fatal: msg, lines };
110});