PURPOSE
Implements the levelUpSlotModule cinematic — a three-reel slot-machine reveal for level-up reward cards. Three blurred reels spin, each ticks common → legendary with ascending chimes, then locks left-to-right at its true rarity with a chunk SFX, ring pulse, white flash, and star burst. After lock, draw falls through to hud.ts default card render so the reveal matches the post-cinematic painted card (no double-render).
OWNS
- Module-level per-reveal state:
_capturedChoices,_targetRarity,_rampDuration,_lockAt,_slotTime,_lastTickIdx,_lockedFired,_burstSpawned,_leverFired,_prevState,_cardRect. _starsparticle pool (capped atSTAR_POOL_CAP= 60).- Tuning constants:
RARITY_LADDER,PRE_SPIN_TIME,INTER_LOCK_PAUSE,RAMP_BY_RARITY,DURATION_BUFFER,RARITY_BURST_COUNT. - Reel rendering helpers
paintSpinningReel,roundRect,_starPath. - Schedule planner
planLockSchedule, ticker functionreelState, rarity coercioncoerceRarity. - Star burst spawner
spawnStarBurst. - The exported
levelUpSlotModule: CinematicModule.
READS FROM
./registry— typesCinematicModule,CinematicContext,RewardState.../card-theme—RARITY_GRADIENT,RARITY_ACCENT,paintCardGradient,Raritytype../audio—playCinematicTone(forslot-tickandslot-chunkcues).../hud—setSlotIntroDurationto grow the sharedslot_introstate to fit the planned schedule.../../audio/sample-sfx—SampleSfx.playLevelUp,SampleSfx.playReward.../../core/config/_accessibility—isSkipRewardAnimationsshort-circuit.- From
ctx:ctx.state,ctx.stateTimer,ctx.dt,ctx.choices,ctx.uiScale.
PUSHES TO
setSlotIntroDuration(plan.total + DURATION_BUFFER)— resizes hud’sslot_introstate once choices are captured (or to0.05when accessibility skip is on).playCinematicTone('slot-tick', pitch)andplayCinematicTone('slot-chunk', pitch, dur)for ticker and lock SFX.SampleSfx.playLevelUp()on the lever-down (announce → slot_intro transition).SampleSfx.playReward()at slot_lock → shine transition.- Direct
CanvasRenderingContext2Ddraws viadrawCardOverride(reel painting) anddrawOverlay(lock flash, ring pulse, star particles).
DOES NOT
- Does not own card layout math —
hud.tsowns positioning; this module only stashes the rect passed intodrawCardOverridevia_cardRect. - Does not paint the final revealed card — returns
falsefromdrawCardOverrideonce a reel is locked or the cinematic entersshowing/fadeout/fly_center/burst, so hud’s default card paint takes over. - Does not advance the cinematic state machine — only reads
ctx.stateand responds toonStateTransition. - Does not overshoot rarity — the ladder walks common → target and stops; never climbs higher than the actual outcome.
- Does not tick
_slotTimeduringannounce— the shared 2-second darken intro is excluded from the slot anim clock. - Does not run any animation work when
isSkipRewardAnimations()is true (other than setting a 0.05s slot_intro duration inonStart).
Signals
- Audio:
slot-chunkat 110 Hz, 0.14s — lever-down on announce → slot_intro.slot-tickascending pitch (base 380 Hz + 60 Hz per ladder step + 80 Hz per card slot) on each ticker bump while spinning.slot-chunklock pitch (140 Hz + 12 Hz per rarity index, dur 0.12s + 0.02s per rarity index) when each reel locks.SampleSfx.playLevelUp()on the lever-down moment.SampleSfx.playReward()on slot_lock → shine.
- Visual:
- Reel: rarity-tinted gradient, four scrolling motion-blur bands, top/bottom darkening, ghosted bobbing ”?” glyph, rarity-accent border.
- Lock: white flash overlay (0.12s, alpha 0.45), rarity-accent ring pulse (0.35s, expands from 0.35× to 0.95× card max dim, thins from 4px).
- Star burst: 10–20 five-pointed stars per card (count by rarity), spawned at card edge, additive
lightercomposite, ~0.65–0.95s lifespan, fast-in/slow-out fade with bright white core.
- State capture:
_cardRect[i]stashed each frame fromdrawCardOverridesodrawOverlayand the deferred star-burst spawn can hit the correct screen coordinates without duplicating hud’s layout.
Entry points
levelUpSlotModule.id='level-up-slot'— registry key.onStart(ctx)— resets module state; setsslot_introto 0.05s when skip-reward-animations is on.onUpdate(ctx)— captures choices on first frame they appear, plans schedule viaplanLockSchedule, resizes slot_intro, accumulates_slotTimeoutsideannounce, fires tick/lock SFX, spawns deferred star bursts, advances/decays star particles.onStateTransition(from, to, ctx)— fires lever-down chunk + level-up sample on announce → slot_intro; fires reward sample on slot_lock → shine.drawCardOverride(ctx2d, cardIndex, x, y, w, h, reward, alpha, scale, isSelected, ctx)— stashes card rect, returnsfalseafter lock or in non-slot states (delegates to hud’s default paint), otherwise paints the spinning reel and returnstrue.drawOverlay(ctx2d, ctx)— paints ring pulse + white flash per locked card, then star particles with additive blending.onEnd(ctx)— callsresetState().
Pattern notes
- Timing contract. Documented at the top of the file:
announce0.40s,slot_intro0.50s (resized viasetSlotIntroDuration),slot_lock0.15s,shine0.30s,showing∞. Lock times are absolute seconds sinceslot_introbegan and the slot clock excludesannounce. - No compression.
planLockScheduleaccumulatesPRE_SPIN_TIME + sum(ramps) + INTER_LOCK_PAUSE between locks. The required duration is pushed into hud viasetSlotIntroDurationrather than crushing rarities into a fixed window — common reveals stay snappy, all-legendary hands get full dwell. - Never overshoot target.
reelStatewalkscommonup totargetand stops; the ladder index isMath.min(targetIdx, Math.floor(p * steps))withsteps = targetIdx + 1. - Choices captured lazily.
onStartfires beforectx.choicesis populated, soonUpdatesnapshots_capturedChoiceson its first frame wherectx.choices.length > 0and only then plans the schedule. - Single-source draw. Once a reel locks (or state is
showing/later),drawCardOverridereturnsfalseso the player’s real card (gradient + badge + icon + name) is painted byhud.tsrather than re-drawn here — eliminates pop-in mismatch on the lock frame. - Star burst is deferred. Spawn happens in
onUpdateonly after bothrs.lockedis true and_cardRect[i]has been stashed by a priordrawCardOverridecall. - Particle pool discipline. Swap-remove iteration on
_stars, dragMath.pow(0.92, dt * 60)for framerate-stable decay, cap atSTAR_POOL_CAP. - Accessibility skip. Every lifecycle method early-returns when
isSkipRewardAnimations()is true, so the cinematic effectively collapses to a near-instantslot_introand hud takes over immediately. roundRectpolyfill. Local rounded-rect path matches hud’s silhouette so reel and final card line up exactly.