PURPOSE

Skeleton render path for per-boss pre-baked looping background video. When a BossDef declares a backgroundLoop, this module creates a hidden <video> element on encounter start, autoplays it muted and looped, and draws each frame to the canvas under the arena layer (BACK z-tier, behind dustMotes). Torn down on encounter end. As of v5.147.1 no BossDef sets backgroundLoop yet — the lifecycle is wired in so adding a video URL to any boss def activates the path.

OWNS

  • Module-level _active: ActiveLoop | null singleton holding the currently-playing loop (boss id, loop def, hidden <video> element, ready flag).
  • The hidden <video> DOM element: creation, append to document.body, hidden via display: none, removal on stop.
  • The canplay event listener that flips ready = true and calls video.play().

READS FROM

  • BossBackgroundLoop type from ../../data/bosses — supplies videoUrl and optional tintColor.
  • Caller-supplied destination rect (destX, destY, destW, destH) and alpha for each frame draw.
  • HTMLVideoElement.readyState (gates draw on HAVE_CURRENT_DATA).

PUSHES TO

  • The supplied CanvasRenderingContext2D — calls drawImage(video, ...) for the frame, then an optional multiply-composited tint fill if loop.tintColor is set.
  • The DOM — appends/removes a hidden <video> element on the body.

DOES NOT

  • Choose where in the boss render pipeline to draw — callers position the destination rect and z-tier.
  • Decode or transcode video assets — relies on the browser’s native <video> decoder.
  • Handle audio — videos are forced muted, asset spec disallows audio tracks.
  • Retry playback on autoplay failure — defers to the engine’s existing pointerdown unlock; failures are swallowed silently.
  • Render anything until canplay has fired and readyState >= 2.

Signals

  • canplay event on the <video> element — flips ready and triggers play().
  • readyState polling on each renderBossBackgroundLoop call — gates drawImage.

Entry points

  • startBossBackgroundLoop(bossId, loop) — idempotent on same bossId; tears down previous loop on different bossId. Creates the hidden video, attaches canplay, sets _active.
  • renderBossBackgroundLoop(ctx, destX, destY, destW, destH, alpha = 1.0) — no-op until active and ready; draws current video frame plus optional tint.
  • stopBossBackgroundLoop() — pauses video, removes src, calls load() to release decoder resources, removes element from DOM, clears _active.
  • hasBossBackgroundLoop() — returns whether a loop is active (independent of ready-state) so callers can short-circuit.

Pattern notes

  • Singleton module state (_active) — only one boss background loop can exist at a time, matching the single-active-boss encounter model.
  • Hidden DOM video as a frame source for drawImage — bypasses layout while leveraging the browser’s native decoder.
  • iOS Safari requires playsInline = true to permit inline muted autoplay; explicit play() after canplay covers browsers that don’t honor the autoplay attribute alone.
  • Asset spec is encoded only in the file’s doc comment: 1280×720 H.264 baseline, no audio, 5–10 s seamless loop, 5–15 MB budget, served from public/boss-bg/<bossId>.mp4.
  • Optional tint is multiply-composited at alpha * 0.3 so the source frame still reads through.
  • Teardown wraps DOM ops in try/catch — best-effort cleanup if the element is already gone.
  • load() after clearing src is the documented way to force the browser to release decoder resources for the just-removed video.