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 | nullsingleton holding the currently-playing loop (boss id, loop def, hidden<video>element,readyflag). - The hidden
<video>DOM element: creation, append todocument.body, hidden viadisplay: none, removal on stop. - The
canplayevent listener that flipsready = trueand callsvideo.play().
READS FROM
BossBackgroundLooptype from../../data/bosses— suppliesvideoUrland optionaltintColor.- Caller-supplied destination rect (
destX,destY,destW,destH) andalphafor each frame draw. HTMLVideoElement.readyState(gates draw onHAVE_CURRENT_DATA).
PUSHES TO
- The supplied
CanvasRenderingContext2D— callsdrawImage(video, ...)for the frame, then an optionalmultiply-composited tint fill ifloop.tintColoris 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
canplayhas fired andreadyState >= 2.
Signals
canplayevent on the<video>element — flipsreadyand triggersplay().readyStatepolling on eachrenderBossBackgroundLoopcall — gatesdrawImage.
Entry points
startBossBackgroundLoop(bossId, loop)— idempotent on samebossId; tears down previous loop on differentbossId. Creates the hidden video, attachescanplay, 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, removessrc, callsload()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 = trueto permit inline muted autoplay; explicitplay()aftercanplaycovers browsers that don’t honor theautoplayattribute 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 atalpha * 0.3so the source frame still reads through. - Teardown wraps DOM ops in
try/catch— best-effort cleanup if the element is already gone. load()after clearingsrcis the documented way to force the browser to release decoder resources for the just-removed video.