// Temporary wrappers live only in the profiling page, never in the simulation bundle. function perfWrap(names, data) { const originals = {}; for (const name of names) { if (typeof window[name] !== 'function') throw Error('Missing profiler target: ' + name); const f = originals[name] = window[name]; data[name] = { ms: 0, calls: 0 }; window[name] = function (...args) { const t = performance.now(); try { return f.apply(this, args); } finally { data[name].ms += performance.now() - t; data[name].calls++; } }; } return () => { for (const name of names) window[name] = originals[name]; }; } function perfSim(names, scene, w, h) { // Five full periods warm even the 120-tick cold pass without forcing conditional calls. for (let k = 0; k < 600; k++) simTick(); perfScene(scene, w, h); const start = perfState(), data = {}, restore = perfWrap(names, data), n = 240; const t = performance.now(); try { for (let k = 0; k < n; k++) simTick(); } finally { restore(); } const total = (performance.now() - t) / n; const steps = {}; for (const [name, d] of Object.entries(data)) steps[name] = { ms_per_tick: d.ms / n, ms_per_call: d.calls ? d.ms / d.calls : null, calls: d.calls }; return { total_ms: total, ticks: n, start, end: perfState(), steps, rest_ms: total - Object.values(data).reduce((s, d) => s + d.ms / n, 0) }; } async function perfRender(lighting, overlays) { SET.lighting = lighting; PERF_MARKS = true; try { for (let k = 0; k < 5; k++) { R.frame++; drawWorld(); } const marks = {}, n = 30; let draw = 0; for (let k = 0; k < n; k++) { R.frame++; drawWorld(); draw += perfLast.drawWorld / n; for (const [key, value] of Object.entries(perfMarks)) marks[key] = (marks[key] || 0) + value / n; } const light = 0; // Exercise the actual overlay calls and their arguments/conditions in frame(). PERF_MARKS = false; for (let k = 0; k < 5; k++) frame(lastT + 1000 / 60); const data = {}, restore = perfWrap(overlays, data), frameStart = performance.now(); try { for (let k = 0; k < n; k++) frame(lastT + 1000 / 60); } finally { restore(); } const frame_ms = (performance.now() - frameStart) / n; // One completed timestamp sample for this configuration, outside the CPU measurements. let gpu_ms = null; if (GPU.queries) { await GPU.timingPromise; PERF_MARKS = true; drawWorld(); await GPU.timingPromise; PERF_MARKS = false; gpu_ms = GPU.gpuMs; marks.gpu = gpu_ms; marks.gpuLight = GPU.lightMs; } return { frame_ms, drawWorld_ms: draw, computeLight_ms: light, marks_ms: marks, overlays: Object.fromEntries(Object.entries(data).map(([key, d]) => [key, { ms_per_frame: d.ms / n, calls: d.calls }])), overlay_ms: Object.values(data).reduce((sum, d) => sum + d.ms / n, 0), scale: 1, actual_look: SET.look, renderer: 'webgpu', override_cells: GPU.overrideCount || 0, gpu_ms }; } finally { PERF_MARKS = false; } } function perfRun(seconds) { return new Promise(resolve => { SET.lighting = 'full'; let warm = 5, previous = null; const samples = []; GAME.paused = false; acc = 0; lastT = performance.now(); lastRender = lastT; PERF_FRAME = s => { if (warm-- > 0) { previous = s.at; return; } samples.push({ interval: s.at - previous, work: s.work, sim: s.sim, steps: s.steps }); previous = s.at; }; window.__perfStop = false; requestAnimationFrame(frame); setTimeout(() => { window.__perfHalt(); PERF_FRAME = null; GAME.paused = true; resolve({ samples, end: perfState(), scale: 1, actual_look: SET.look, renderer: 'webgpu', override_cells: GPU.overrideCount || 0, gpu_ms: GPU.gpuMs }); }, seconds * 1000); }); }