PURPOSE

Radial darkness overlay drawn directly into the main canvas 2D context each frame. Sized to the actual viewport in device pixels so the corners are fully dark regardless of aspect ratio, DPR, or active caller transform. Replaces an earlier pre-baked square canvas that left corners outside the dark band when stretched to 16:9.

OWNS

  • The runtime opacity knob window.__vignetteOpacity (default 0.6 via VIGNETTE_DEFAULT_OPACITY).
  • Per-frame radial gradient construction and the single fillRect covering the screen.
  • URL-param ingestion for ?vignette=N (where N is 0-100 percent).
  • The exported setter used by the dev console.

READS FROM

  • window.__vignetteOpacity each frame (falls back to default if unset or null/undefined).
  • ctx.canvas.width and ctx.canvas.height for device-pixel dimensions.
  • window.location.search once at boot for the vignette URL param.

PUSHES TO

  • The provided CanvasRenderingContext2D — writes one fillRect covering the entire canvas with the radial gradient.
  • window.__vignetteOpacity — written by initVignetteFromUrl (when URL param parses to a finite number) and by setVignetteOpacity.

DOES NOT

  • Does not read or trust the caller’s transform state — forces setTransform(1,0,0,1,0,0) inside its own save/restore block so the overlay is never affected by camera offset, world zoom, or DPR scale.
  • Does not use the _w / _h parameters — they are accepted for API compatibility and ignored.
  • Does not cache the gradient — rebuilds it every frame (cost ~0.3 ms total).
  • Does not draw anything when opacity is <= 0.
  • Does not register itself with any render loop — the caller invokes drawVignette explicitly.
  • Does not throw or guard against a missing canvas; assumes a valid 2D context is passed.

Signals

  • Opacity is encoded in the gradient color stops, not in ctx.globalAlpha (which is forced to 1).
  • Outer radius is the half-diagonal Math.hypot(W, H) / 2 so the corners hit the fully-dark stop.
  • Inner radius is Math.min(W, H) * 0.20 — a small transparent center proportional to the shorter viewport edge.
  • Opacity is clamped to [0, 1] both at write time (setVignetteOpacity, initVignetteFromUrl) and at read time inside the color-stop expression.

Entry points

  • drawVignette(ctx, _w, _h) — call once per frame after the scene has been rendered.
  • initVignetteFromUrl() — call once at app startup to seed opacity from ?vignette=N.
  • setVignetteOpacity(opacity) — runtime setter (0-1), returns the clamped value; backs __dev.setVignette.

Pattern notes

  • Stateless module — no module-level mutable state beyond the window global it reads/writes.
  • The save/restore + setTransform identity reset is the load-bearing pattern: it makes the function safe to call from any render-loop position without coordinating with camera or DPR code.
  • Constant-extracted default (VIGNETTE_DEFAULT_OPACITY) keeps the magic number out of the function body.
  • URL ingestion is gated on typeof window === 'undefined' to stay safe under non-browser execution (e.g. tests, SSR).
  • Number.isFinite guard on the parsed URL value ensures NaN from a malformed param is silently ignored rather than poisoning the global.