PURPOSE

Smoothed FPS sampler for HUD/diagnostic display. Informational only — does not feed quality scaling or any gameplay decision.

OWNS

  • Module-local mutable _smooth value (current exponentially smoothed FPS estimate).
  • Smoothing constant ALPHA (0.05) controlling the EMA weight on each raw sample.
  • The three exported functions tickFps, getSmoothedFps, and resetSmoothedFps.

READS FROM

  • Raw per-frame FPS values supplied by the caller as the rawFps argument to tickFps. No imports, no globals, no time source of its own.

PUSHES TO

  • Returns the current smoothed value via getSmoothedFps to any caller that asks. Has no side effects beyond updating the module-local _smooth.

DOES NOT

  • Does not sample performance.now, requestAnimationFrame, or any clock — it only smooths whatever raw FPS the caller computes.
  • Does not influence quality scaling, throttling, simulation rate, or any gameplay system.
  • Does not emit telemetry events or write to any store.
  • Does not validate the smoothing constant or expose it to callers.
  • Does not coordinate with engine/telemetry/sampler.ts’s own tickFps — they are independent rolling-FPS estimators.

Signals

  • Non-finite or non-positive rawFps samples are ignored: tickFps early-returns without mutating _smooth. This prevents NaN/Infinity from poisoning the smoothed value during a stalled or zero-delta frame.
  • Initial value of _smooth is 60, so the HUD reads a sensible figure before any frame has been ticked.
  • resetSmoothedFps restores _smooth to 60 — used on engine boot and on loop restart to clear any stale post-pause reading.

Entry points

  • tickFps(rawFps: number): void — fold a single raw FPS sample into the smoothed estimate via _smooth += (rawFps - _smooth) * ALPHA. Called from engine/bridge.ts inside the per-frame update path with 1 / rawDt.
  • getSmoothedFps(): number — return the current smoothed value. Called from engine/rendering/hud.ts to render the FPS line.
  • resetSmoothedFps(): void — reset to 60. Called from engine/core/loop.ts on loop start and from engine/bridge.ts on engine reset.

Pattern notes

  • Classic exponential moving average. With ALPHA = 0.05 each raw sample contributes 5% and the prior estimate retains 95%, giving roughly a 20-frame effective window. Tune by editing ALPHA — there is no runtime knob.
  • Module-level let _smooth is the singleton state; there is no class, no instance, and no per-scene reset beyond the explicit resetSmoothedFps call.
  • Guard against bad samples lives at the boundary (!isFinite(rawFps) || rawFps <= 0) and crashes nothing — consistent with the “defensive at boundaries, crash internally” rule.
  • The HUD pulls smoothed FPS lazily via getSmoothedFps rather than receiving a push, so reads are free and require no subscription.
  • Separate from Sampler.tickFps(rawDt) in engine/telemetry/sampler.ts, which keeps its own rolling FPS buffer for telemetry payloads. Both are fed in the same bridge frame but serve different consumers.