1// Runs one configuration headlessly and prints the status every half second.
2// node --experimental-strip-types scripts/probe.ts "duration=1&E=1.6e11&..." [--seed S --index I (parameters of a dataset clip, plus overrides after &)] [--wav out.wav]
3import { chromium } from "playwright";
4import { createServer } from "node:http";
5import { readFileSync } from "node:fs";
6import { join, extname, dirname } from "node:path";
7import { fileURLToPath } from "node:url";
8import { writeFileSync } from "node:fs";
9import { drawSample, toQuery, wavBytes } from "./generate.ts";
11const root = join(dirname(fileURLToPath(import.meta.url)), "..");
12const argv = process.argv.slice(2);
13const arg = (n: string, d: string) => { const k = argv.indexOf(`--${n}`); return k >= 0 ? argv[k + 1] : d; };
14let query = argv.find((a) => !a.startsWith("--") && !/^\d+$/.test(a)) ?? "";
15if (argv.includes("--index")) {
16 const s = drawSample(parseInt(arg("seed", "1")), parseInt(arg("index", "0")));
17 query = toQuery(s.params) + (query ? "&" + query : "");
18} else query = "autostart=1&" + query;
19const MIME: Record<string, string> = { ".html": "text/html", ".js": "text/javascript", ".css": "text/css" };
20const server = createServer((req, res) => {
21 const path = decodeURIComponent((req.url ?? "/").split("?")[0]);
22 try { const f = join(root, "dist", path === "/" ? "index.html" : path); res.writeHead(200, { "content-type": MIME[extname(f)] ?? "application/octet-stream" }); res.end(readFileSync(f)); } catch { res.writeHead(404); res.end(); }
23});
24await new Promise<void>((r) => server.listen(0, "127.0.0.1", () => r()));
25const port = (server.address() as { port: number }).port;
26const browser = await chromium.launch({ headless: true, args: ["--enable-unsafe-webgpu", "--enable-features=Vulkan", "--use-angle=vulkan", "--ignore-gpu-blocklist", "--no-sandbox"] });
27const page = await browser.newPage();
28page.on("pageerror", (e) => console.log("pageerror", e.message));
29page.on("console", (m) => { if (m.type() === "error") console.log("console.error", m.text()); });
30console.log(query);
31await page.goto(`http://127.0.0.1:${port}/?${query}`);
32const t0 = Date.now();
33for (;;) {
34 await page.waitForTimeout(500);
35 const st = await page.evaluate<Record<string, unknown> | null>("window.steelsim ? window.steelsim.status() : null");
36 const noGpu = await page.evaluate<string | null>("window.steelsim ? window.steelsim.noGpu() : null");
37 if (noGpu) { console.log("no gpu:", noGpu); break; }
38 if (!st) continue;
39 const stick = st.stick as { x: number; v: number; force: number; maxForce: number } | null;
40 console.log(`${((Date.now() - t0) / 1000).toFixed(1)}s phase ${st.phase} t ${(st.t as number).toFixed(3)} ke ${(st.ke as number).toExponential(2)} wmax ${((st.wmax as number) * 1e3).toFixed(2)} mm peak ${(st.peakPressure as number).toFixed(3)} Pa ${(st.stepsPerSec as number / 1e3).toFixed(0)} k/s ${st.nx}x${st.ny}` + (stick ? ` stick x ${(stick.x * 1e3).toFixed(1)} mm v ${stick.v.toFixed(2)} F ${stick.force.toFixed(1)} Fmax ${stick.maxForce.toFixed(1)}` : ""));
41 if (st.phase === "done") break;
42}
43if (argv.includes("--wav")) {
44 const audio = new Float32Array(await page.evaluate<number[]>("window.steelsim.audio()"));
45 let peak = 0;
46 for (const v of audio) peak = Math.max(peak, Math.abs(v));
47 writeFileSync(arg("wav", "probe.wav"), wavBytes(audio, 48000, peak > 0 ? 0.99 / peak : 0));
48 console.log(`wrote ${arg("wav", "probe.wav")}, peak ${peak.toFixed(3)} Pa`);
49}
50await browser.close();
51server.close();