Perf profile
STANDARDIZED PERFORMANCE PROFILE: drives scripted segments through the main gameplay regimes in headless Chrome or Edge (real GPU) and writes one metrics file per segment, so two runs are comparable number for number.
Start the dev server first (npm run dev, Vite’s default port 5173), then:
node scripts/perf-profile.mjs --out <dir> [--label baseline] [--url http://localhost:5173/] [--segments home-town-walk,body-swap] [--profileJson scripts/perf-profiles/<file>.json] [--size 3840x2160] [--shots] [--audio] [--warm [--profile <chrome-user-data-dir>]]
--out output root (default: <os temp>/perf-profile) --label run name; files go to <out>/<label>/ (default: run) --url game URL; `diag&noenter&nointro` is always appended --segments comma-separated subset of segment names --profileJson segment file (default scripts/perf-profiles/default.json: home-idle, home-town-walk, home-surface-flight, space-approach, arrival-settle, luna-approach, luna-arrival-settle, body-swap, luna-surface-flight) --size window size WxH (default 1920x1080; render targets scale with it, so a 4K question needs a 4K run) --shots PNG screenshot at the end of each segment --audio allow autoplay so the AudioContext (and voice) can run --warm reuse a shared Chrome profile (warm shader cache AND the previous run's autosave); --profile names that directory. The default is a fresh profile per run, because the game autosaves and a reused profile starts where the last run ended. With --warm, read <segment>.where.json to see where each segment actually ran.WHY A SCRIPTED PROFILE. A hand-flown capture measures the pilot as much as the build: the scene evolves on its own (weather, orbits, streaming), so captures taken minutes apart differ by more than the change under test. Every segment starts from a deterministic pose set by console hooks, replays a fixed key script for a fixed wall time, and reports its own profiler window, so the same segment in two runs saw the same work.
SEGMENT FIELDS (JSON array of objects):
name, seconds, settleSec timed window length and untimed settle setup: [expr...] console expressions: teleports, body swaps, flag flips expectBody, arriveTimeoutSec wait until __where() reports this body with no swap pending (warns if it never arrives) setupAfterArrive: [expr...] run once the swap has landed; anything that poses the player belongs here preKeys, keys: [[atSec, "down"|"up", "KeyW"]...] preKeys fire before the settle, keys inside the timed window sampleEverySec, watch periodic samples into <segment>.series.json (the profiler ring holds only 240 frames) spin: [startSec, endSec, pxPerSec] right-button camera drag teardown: [expr...] state probes run after the dump ab: { arm: [expr...] }, repeats, windowSec, armSettleSec, windowKeys, windowSpin: [[startSec, endSec, pxPerSec]...] interleaved A/B inside one page life, with the arm order rotated every repeat; windowKeys and windowSpin (a right-button camera drag, a leg may be negative) replay inside each windowThe profiler is reset after the settle, so the window measures the steady regime (a segment with settleSec 0 measures the transition itself).
OUTPUT in
<segment>.json npcProfiler dump: CPU spans (mean/p95/p99/max), counters, gauges, gpu_times_ms, the cpu.frame and cpu.frameWall histograms, and the spike ring <segment>.where.json pose at the start and end of the window <segment>.series.json periodic samples (sampleEverySec only) <segment>.teardown.json full teardown probe values (teardown only) <segment>.ab.json paired A/B windows (ab segments only) <segment>.png screenshot (--shots only) boot.json boot timing and phase attribution summary.json the cross-segment table console.log the whole session, boot marks includedNever add __cfg('timeSpeed', 0) to a setup list. Freezing the clock stops
the weather simulation too, which flattens the sea and reads as a speedup
that is really a frozen scene. To hold the sun for comparable shots, pin
the time of day per window instead.
Written by release:site-sync from scripts/perf-profile.mjs at v2026.09.24. Edit the engine document, not this page.