PURPOSE

Automated performance stress-test screen mounted at /perf-benchmark. Launches a real mission with an invulnerable player and walks through nine escalating phases that isolate enemy count, bullet count, particle load, and combined load, then logs detailed telemetry to the console. Used to diagnose subsystem bottlenecks without relying on browser devtools profiling.

OWNS

  • The PHASES table — nine BenchPhase entries (warmup, enemies_light, enemies_medium, enemies_heavy, enemies_extreme, bullets_flood, particles_max, combined_hell, cooldown) with per-phase durationSec, targetEnemies, forceFire, and particleBurst.
  • BenchPhase and BenchLogEntry types.
  • The module-level _benchLog array that accumulates telemetry entries for the run.
  • buildSummary() — aggregates the log by phase into per-phase FPS min/avg/max, mean frame time, top-5 render passes by cost, spike count, and peak entity/bullet/particle counts.
  • buildBenchRunDef() — clones DEFAULT_RUN and overrides combat stats to make the player effectively invulnerable while keeping shots from killing enemies (huge HP, huge shield, max regen, max damage reduction, large magnet range, max fire-rate, weapon damage at -90%).
  • PerfBenchmarkScreen React component — mounts the canvas, wires the bridge mission, and runs the benchmark control loop.
  • The HUD overlay: status string, large color-coded FPS readout (red < 30, amber < 55, green otherwise), current phase, and completion hint.

READS FROM

  • ../engine/bridgecreateMission and MissionHandle type for starting/stopping the mission.
  • ../data/run-configDEFAULT_RUN and RunDefinition type as the base for the benchmark run.
  • ../engine/core/statesetDebugOverlay, game, ship, world globals (reads ship.x/y/hp/hpMax/shieldHp/shieldMax, world.enemies, world.particles).
  • ../engine/core/render-diagdiagGetPerfSnapshot (per-tick FPS, frame time, pass timings, entity counts), diagDrainSpikes (queued spike snapshots), and the RenderPerfSnapshot / SpikeSnapshot types.
  • ../engine/enemies/spawnerGameMaster.spawnEnemy for force-spawning the four canned types orb_common, charger_common, shooter_common, mortar_common.
  • window.devicePixelRatio, window.innerWidth, window.innerHeight for canvas sizing.

PUSHES TO

  • The browser console: phase-start banners, per-2-second telemetry lines ([PERF-BENCH] <phase> @<t>s | FPS:<n> frame:<ms>ms | enemies/bullets/particles | TOP: <top-5 passes>), spike counts, and at completion the full log JSON plus the summary JSON.
  • setDebugOverlay(true) to turn on the in-engine debug overlay for visual monitoring.
  • mission.start() and mission.destroy() on the bridge MissionHandle.
  • React state: status, currentFps, currentPhase, done (all rendered in the HUD).
  • Mutates ship.hp, ship.shieldHp, every enemy’s hp, hpMax, fireTimer, and _aoeCooldown, and pushes synthetic spark particles onto world.particles.
  • The canvas DOM element (width/height/style sized to the window each mount).

DOES NOT

  • Score, persist, or upload benchmark results — output is console-only.
  • Render the game itself; rendering is done by the bridge mission. This screen only sizes the canvas and overlays a HUD.
  • Spawn through the normal GameMaster cadence — it bypasses spawn pacing and force-fills to targetEnemies in batches of up to 20 per tick.
  • Honor the player’s run config beyond DEFAULT_RUN — there is no ship selection, no shop, no progression.
  • Handle resize after mount; canvas dimensions are captured once on effect entry.
  • Read or write Supabase, telemetry, or Sentry — no cloud logging.
  • Pause or respond to onPhaseChange / onGameOver callbacks (both wired as no-ops).
  • Cap world.particles above 500 — particle bursts skip when at or above that count.

Signals

  • React state: status: string, currentFps: number, currentPhase: string, done: boolean.
  • Refs: canvasRef: HTMLCanvasElement | null, missionRef: MissionHandle | null.
  • Effect-scoped locals driving the bench loop: phaseIdx, benchStart, phaseStart, lastLog, benchDone, benchRAF.
  • Telemetry shape (BenchLogEntry): t, phase, phaseT, perf (RenderPerfSnapshot), spikes (SpikeSnapshot[]), meta ({ targetEnemies, forceFire, particleBurst }).
  • Console tag is [PERF-BENCH].

Entry points

  • Default-exported React component PerfBenchmarkScreen — mounted at the /perf-benchmark route.
  • The component is self-driving once mounted: a single useEffect with empty deps starts the mission, schedules benchTick via requestAnimationFrame, and registers a cleanup that flags the loop done, cancels the rAF, destroys the mission, and clears missionRef.

Pattern notes

  • Module-level _benchLog is intentionally a singleton — every mount resets it before the loop begins, so reloading the route discards the prior run.
  • The bench loop is a separate requestAnimationFrame chain from the bridge mission loop; it piggybacks on the engine’s own tick rather than driving it.
  • Player invulnerability is maintained both via config (huge stats in buildBenchRunDef) and via per-tick clamping (ship.hp = ship.hpMax, shield refilled) to defeat any in-engine damage path.
  • Enemy invulnerability is set per-spawn (hp = hpMax = 999999) so they survive incidental player damage from the negative-weapon-damage run.
  • Bullet-flood phase forces fire by zeroing every enemy’s fireTimer and _aoeCooldown every tick.
  • Particle-stress phase pushes 30 sparks per tick toward the ship’s surroundings until the global particle array reaches 500.
  • Cooldown phase drains by setting every alive enemy’s hp to 0 rather than calling a kill helper.
  • Telemetry cadence is fixed at 2000 ms wall-clock; the per-tick work above runs every animation frame regardless.
  • Phase transition resets phaseStart and returns early from the tick, skipping the mutation block for the boundary frame.
  • HUD overlay uses inline styles only — no CSS module or design-system component — and pins itself with pointerEvents: 'none' so it never blocks input to the canvas.
  • The bench logs the full per-entry array (FULL LOG) and the aggregated SUMMARY separately at completion; the summary’s topPasses is the 5 most expensive render passes averaged across that phase’s snapshots.