PURPOSE
Draws the in-mission “SURVIVE M:SS until extraction” reminder banner above the bottom weapon-slot row. Frames the mission-timer loss condition positively as time-until-extraction rather than time-until-death. Includes entrance slide/fade/scale, mirrored exit, and an urgency pulse on the accent stripe and time number when the timer drops under the urgent threshold.
OWNS
- Module-private banner entrance state (
_introStartUiTime) tracking when the banner first became visible this run. - Banner geometry constants: width, height, side safety margin, weapon-row clearance math.
- Entrance / exit animation tunings: durations, slide offsets, scale endpoints, easing functions (
_easeOutCubic,_easeInCubic). - Urgency pulse tunings: pulse rate, amplitude for stripe and time number, urgency threshold.
- Typography constants and the M:SS string formatter (
_formatMmSs). - Banner Y-position calculation that anchors above the largest plausible weapon row, clearing the fire-flash ring and ready glow.
READS FROM
game.phase,game.missionTimer,game.missionTimerMax,game.uiTime,game.timefrom../core.ship.alivefrom../core.uiScale,W,Hfrom../corefor layout.MedicalPalettetokens (borderStrong,textInverse,accentBright,statusBad,statusWarn) andmedFontBodyfrom./medical-canvas-palette.- Optional
yOverrideparameter for test placement.
PUSHES TO
- The supplied
CanvasRenderingContext2D— paints background pill, four-edge border, accent stripe, and three text segments (label, time, suffix). - Returns the banner height in screen px (0 when not drawn) to the caller.
- Restores
ctx.textBaseline,ctx.textAlign, andctx.globalAlphato drawHUD’s expected baseline before returning.
DOES NOT
- Does not own or mutate
game.missionTimer— purely a renderer. - Does not draw on the menu, level-up, or cinematic phases, when the player is dead, when the mission is untimed (
missionTimerMax <= 0), or once the timer has expired (overtime path handles its own messaging). - Does not use gradients,
shadowBlur, orclip— sticks to the 2ms HUD perf budget. - Does not animate during pause — the urgency pulse uses
game.time, which freezes when the game pauses. - Does not stroke its border — uses four
fillRectedges instead, since axis-alignedstrokeRectwould cost more path ops than one fillRect ring.
Signals
urgentflips true whengame.missionTimer <= URGENT_THRESHOLD_SEC(30s), driving stripe colour swap tostatusBad, time-number swap tostatusWarn, and a sine pulse atURGENT_PULSE_RATE(8 rad/s) on both — matched to the top-right HUD timer pulse so the two views read as the same cue.inOutroflips true whengame.missionTimer < OUTRO_FADE_WINDOW_SEC(0.45s), starting the mirrored exit slide-down + shrink + fade.- Composite alpha below 0.005 short-circuits the draw entirely.
_introStartUiTimeresets tonullwhenever the banner becomes ineligible, so the entrance replays on every new mission, respawn, or eligibility flip.
Entry points
drawMissionTimerBanner(ctx, yOverride?)— main draw call, invoked by the HUD renderer each frame. Returns the rendered banner height in screen px.getMissionTimerBannerY()— exported helper that returns the resting top-Y of the banner; lets the HUD reason about banner placement without redoing the layout math.resetMissionTimerBanner()— exported state-reset for the bridge to call at run start / run end (mirrorsclearArtifactBanners); also useful for tests that need a clean entrance between cases.
Pattern notes
- First frame after eligibility seeds
_introStartUiTimebiased ~1 frame into the past (FIRST_FRAME_BIAS = 0.016) so the entrance never paints a fully transparent frame. - Entrance uses
_easeOutCubic(fast-then-settle); exit uses_easeInCubic(slow-then-accelerate). Asymmetric easing keeps the exit from feeling mechanical. - Entrance slide distance is clamped in device-px (
INTRO_SLIDE_OFFSET_DEVICE_MAX = 22) so high-uiScale phones don’t experience a heavy 40+ device-px lurch. - Scale transforms around the banner center (translate / scale / translate) so the pop reads from the middle rather than a corner.
- Three-segment text layout (
SURVIVE/M:SS/until extraction) lets the time number carry its own larger font size, brighter colour, and independent urgency alpha pulse — visual hierarchy puts the number first at mobile glance distance. - Banner width clamps to
max(80, W - sideSafety*2)so narrow split / playground viewports never overflow. - Banner Y-position is computed bottom-up from
H, subtracting bottom margin, weapon-row gap, slot diameter, glow clearance, pad, and banner height — each term constant-defined anduiScale-aware so it tracksdrawHUD’s weapon-row layout exactly. - Typography routes through
medFontBody(size, FONT_WEIGHT)at weight 600 so the banner inherits the medical-UI font family/fallback stack and reads as part of the HUD type system. - Background stays semi-opaque dark (
rgba(8, 12, 20, 0.78)) rather than the medical white surface, because the banner sits on the live game canvas and a full-white panel would over-bright the run-feel.