PURPOSE

Reward-cinematic module that paints a left-to-right rainbow shooting star across the reward card row during the slot_intro HUD state. As the bead crosses each card’s midpoint, the card is revealed and a short-lived burst of glittery sparkle particles is spawned at the card’s center. The trail behind the bead is a tapered rainbow comet painted additively in the overlay pass. Registered for cinematic family 'shooting_star'.

OWNS

  • Module-scoped streak state: captured choices array, per-card revealed/sparked flags, sparkle particle pool, intro elapsed timer, last-sampled bead position (_starX, _starY), cached per-card center coordinates.
  • The Sparkle particle record (position, velocity, life, max life, hue, base size, spin rate, current rotation).
  • Tunable constants for the streak fraction, bob amplitude, trail length and segment count, sparkles per card, sparkle cap, sparkle lifetime, and the rainbow hue stops.
  • Card-center geometry computation that mirrors the HUD’s resting card layout math.
  • Rendering helpers for the rainbow comet trail, the bright white bead with warm halo, and a single 4-point sparkle.
  • The exported shootingStarModule implementing the CinematicModule contract (id, onStart, onUpdate, drawCardOverride, drawOverlay, onEnd).

READS FROM

  • ./registry for the CinematicModule and CinematicContext types.
  • ./audio for playCinematicTone, used to trigger the per-card sparkle chime.
  • ../hud for setSlotIntroDuration, used to shorten the slot intro to match the streak’s running time (or to a near-zero value when accessibility skip is on).
  • ../../core/config/_accessibility for isSkipRewardAnimations, the accessibility gate that bypasses the cinematic entirely.
  • The CinematicContext fields supplied each frame: viewport W and H, cardW, cardH, state, stateTimer, dt, choices, and the canvas 2D context passed into overlay/card-override callbacks.

PUSHES TO

  • The provided CanvasRenderingContext2D during the overlay pass, painting the rainbow trail, the bright white core stroke, the bead’s radial gradient, and each live sparkle. Composite mode is set to lighter (additive) and restored via save/restore.
  • The HUD’s slot-intro duration, written through setSlotIntroDuration on cinematic start (full streak duration, or a tiny value when reward animations are skipped).
  • The cinematic audio channel via playCinematicTone, emitting a 'sparkle' tone whose frequency rises by card index when each card reveals.
  • The HUD’s card draw pipeline indirectly, by returning true from drawCardOverride while a card is still hidden behind the bead (suppressing the card) and false once the bead has passed it (handing rendering back).

DOES NOT

  • Does not advance, schedule, or transition cinematic state — state and timers come from the HUD via CinematicContext.
  • Does not select reward choices or mutate ctx.choices; it snapshots them once for stable layout and ignores later changes to the same array.
  • Does not render the reward cards themselves, draw their text, glyphs, frames, or selection highlight — that remains the HUD’s responsibility once a card is unsuppressed.
  • Does not exit or short-circuit slot_intro; it shapes the duration on start and otherwise lets the HUD finish the state.
  • Does not allocate beyond the configured sparkle cap; once SPARKLE_CAP is reached, further sparkles in a burst are dropped.
  • Does not handle input, hit-testing, or selection.
  • Does not write to module state from the render callbacks; all state mutation lives in onUpdate and onStart/onEnd.

Signals

  • Cinematic start (onStart): full state reset; on accessibility skip, slot intro is set to a near-instant duration and the function returns; otherwise slot intro is set to a duration that matches the streak’s run time.
  • Cinematic update (onUpdate): on the first frame ctx.choices is populated, the choice list is snapshotted, per-card flags are sized, and card centers are computed; if the viewport changed mid-cinematic the centers are recomputed. The bead position is sampled from the current state — parked off-screen left during announce, eased left-to-right across the screen with a sinusoidal Y bob during slot_intro, and clamped past the right edge for slot_lock/shine/showing. For each unrevealed card whose midpoint the bead has crossed, the card is marked revealed, sparkles are spawned at the card center, and the card-specific sparkle chime is played. Particles are then stepped by ctx.dt.
  • Card override (drawCardOverride): returns false when reward animations are skipped, when the card has been revealed, or when the state is not announce/slot_intro; otherwise returns true to suppress the card while the bead has not yet arrived.
  • Overlay draw (drawOverlay): returns immediately when reward animations are skipped, and again when the streak is not active and no sparkles are alive. Otherwise saves composite state, switches to additive 'lighter' blending, paints the trail and bead at the cached bead position with intensity decayed across slot_lock and shine, paints every live sparkle, then restores composite state.
  • Cinematic end (onEnd): full state reset.

Entry points

  • shootingStarModule — default export object implementing CinematicModule with the fixed id 'shooting-star'. Registered for the 'shooting_star' family by the cinematic registry. Public surface is onStart, onUpdate, drawCardOverride, drawOverlay, and onEnd; all other helpers in this file are module-private.

Pattern notes

  • State machine model: this module is a passive consumer of the HUD’s reward-flow states (announce, slot_intro, slot_lock, shine, showing). It maps each state to a stage of the streak (charge, run, lock decay, shine fade-out, idle) without owning timers.
  • Reveal contract: the cinematic owns card visibility during announce/slot_intro via drawCardOverride; once a card is marked revealed it stays revealed across all subsequent states, so the HUD reclaims rendering smoothly.
  • Snapshot-on-first-frame: ctx.choices is copied once and the copy drives layout for the rest of the cinematic, which keeps reveal geometry stable even if upstream rebuilds the choices array. Cached card centers are also recomputed defensively when their length stops matching the snapshot (orientation flip mid-reveal).
  • Easing: bead travel uses a smooth in-out curve so cards “pop” rather than fly; the bob is a low-amplitude sinusoid on Y for life.
  • Trail: 14 tapered horizontal segments, alpha and thickness weighted toward the head, plus a bright white core pass over the front quarter for a hot-bead feel. Drawn with lineCap = 'round' and additive composite.
  • Hue palette: a fixed array of seven rainbow stops drives both the trail gradient walk and the sparkle hue assignment, which keeps the streak and the per-card bursts visually consistent.
  • Particle pool: sparkles are an Array<Sparkle> updated in place; dead entries are swap-popped from the tail to avoid mid-array splices. The SPARKLE_CAP is a hard upper bound on concurrent particles for mobile cost control.
  • Sparkle draw safety: k = min(1, life / maxLife) clamps the lifetime ratio so that variable spawn lifetimes can’t drive a negative radius into arc().
  • Accessibility: isSkipRewardAnimations() is gated in onStart, onUpdate, drawCardOverride, and drawOverlay; when on, the cinematic short-circuits, the slot intro is compressed to near-instant, all cards are pre-marked revealed, and no draw work is done.
  • Cost discipline: all per-frame allocation is constrained to fixed-size arrays and per-particle scratch math; per-frame heap allocations from helpers are limited to a single radial gradient for the bead and per-segment HSL strings for the trail.
  • Composite hygiene: overlay rendering wraps every additive pass in ctx2d.save() / ctx2d.restore() and resets globalAlpha between intensity-scaled passes, so the caller’s composite state is preserved.