PURPOSE
Wheel of Fortune overlay for the wheel event type. Owns the full-screen spinning prize wheel: slice generation, spin animation (fast / decel / tick / result sub-phases), pointer + center cap + outer ring rendering, SPIN and EXIT button rendering, and SPIN/EXIT hit-testing. Slices encode positive rewards (weapon, ship, artifact stacks; one weapon_10 jackpot) and negative HP penalties (-25%, -50%, -99%). Renders bottom-anchored result banner during the result phase.
OWNS
SLICE_DEFS— the static slice catalogue keyed byWheelSliceType, providing label, sublabel, color,isNegative, andisJackpotfor every slice variant.initWheelState(rng)— builds a freshWheelState: shuffles 7 positives plus 1–3 randomly selected negatives into the wheel and zero-initializes the animation fields.replaceWonSlice(state, wonIdx)— overwrites the indicated slice with ahp_25penalty (used after a positive outcome is claimed).triggerSpin(state, rng)— picks the landing index, computes the target rotation angle that aligns that slice’s center with the pointer after 5–7 full rotations, resets animation timers, and flipsphasetospinning.updateWheel(state, dt)— drives the spin state machine each frame:fastexponential-decay rotation with coin particle bursts,decelcubic-eased slowdown,tickdiscrete snaps to slice boundaries usingTICK_INTERVALS = [0.06, 0.12, 0.22], transition toresultwith a 1.8s timer plus landing particle burst, andresult → choosingreset oncerewardAppliedis true.hitTestWheelSpin(px, py)/hitTestWheelExit(px, py)— point-in-rect tests against the SPIN and EXIT button hitboxes cached during the last draw.drawWheel(ctx)— full overlay render: dark backdrop, rotating radial rays during spin, segmented pie wheel with per-slice fill / border / jackpot pulse / pop-out glow for the won slice, center cap, outer ring, downward-pointing pointer triangle, “WHEEL OF FORTUNE” title, result banner, and SPIN + EXIT round-rect buttons.- Module-private hitbox state
_spinHitBox/_exitHitBox, helper_shuffle, helper_drawSegmentText, and helper_roundRect. - Spin tuning constants:
SPIN_START_SPEED = 32,SPIN_DECAY = 0.8,SPIN_FAST_FLOOR = 6,DECEL_SPAN_SLICES = 3.
READS FROM
game._wheelState(read insidedrawWheel; bridge passes the same object explicitly toupdateWheel/triggerSpin/replaceWonSlice).game.timefor jackpot pulse and SPIN button pulse phase.shipis imported from../core(unused at present beyond the import).W,H,uiScalefrom../corefor layout: wheel center(W/2, H*0.42), radiusmin(W,H)*0.32, button row atH*0.88.WheelSlice,WheelSliceType,WheelStatetypes from../core/types.
PUSHES TO
- Mutates the passed
WheelState:phase,slices,spinAngle,startAngle,targetAngle,landedIdx,resultTimer,popOutAnim,spinSubPhase,spinElapsed,tickTimer,tickCount,ticksNeeded,rewardApplied,spinCount,bgRotation. Particles.burst— yellow spark bursts radiating around the wheel rim during the fast spin phase (capped to the first 2.5 s).Particles.burstHex— a 48-spark burst at the wheel center on landing, red (#ff4444) for negative results and gold (#ffd228) otherwise.- Canvas state via
ctx— sets composite mode, fill/stroke styles, shadow, transform, alpha, font, and text alignment; every block is wrapped insave/restore. - Module-local
_spinHitBox/_exitHitBoxrectangles, refreshed at the end of everydrawWheelcall.
DOES NOT
- Does not apply the reward. The bridge calls
_applyWheelReward(game._wheelState)afterupdateWheeland is responsible for translating the won slice into weapon / ship / artifact gains or HP loss. - Does not allocate or free
game._wheelState. The bridge constructs it viainitWheelStatewhen the wheel event starts and nulls it on EXIT. - Does not own input wiring.
GameScreencallshitTestWheelSpin/hitTestWheelExitfrom its pointer-down handler; this module only reports geometry. - Does not play audio.
- Does not write to telemetry, Supabase, or any persistent store.
- Does not gate on game pause flags, leveling, or other run state; the bridge guards entry by checking
!game._wheelStatebefore stepping leveling and reward queues. - Does not animate the EXIT button (no pulse), and does not respond to keyboard input.
Signals
phase(choosing→spinning→result→choosing) is the public progression signal. Callers read it to gate SPIN re-arm and EXIT availability.rewardAppliedis set by the bridge after it consumes aresultoutcome;updateWheelonly resets back tochoosingonce bothresultTimerhas elapsed andrewardAppliedis true.landedIdx >= 0duringresultindicates a valid claim target; reset to-1on thechoosingtransition.spinCountincrements at every landing — usable as a per-run counter.
Entry points
initWheelState(rng)— called by the bridge once when the wheel event begins (game._wheelState = initWheelState(() => Math.random())).triggerSpin(state, rng)— called from the bridge SPIN handler when the SPIN button is tapped.updateWheel(state, dt)— called every frame from the bridge update loop whengame._wheelStateis non-null.replaceWonSlice(state, wonIdx)— called by the bridge after a positive outcome is paid out so the same slice cannot be claimed again on the next spin.drawWheel(ctx)— called from the bridge render path whengame._wheelStateis non-null.hitTestWheelSpin/hitTestWheelExit— called fromGameScreenpointer handling.
Pattern notes
- Pure-data + free-function module: no class, no singleton, no lifecycle object. State lives in the caller’s
WheelState, which is owned bygame._wheelState. The module is stateless apart from the two cached hitbox rectangles. - Spin animation is a deterministic state machine with four sub-phases (
fast,decel,tick, plus the implicitresult). The tick phase always snaps to slice boundaries and forces the final value totargetAngleso the landing is frame-exact regardless of frame timing. - Target-angle math accounts for cumulative
spinAngleso successive spins always rotate forward by at leastfullSpins * TAU. - Slice rendering uses a Fisher–Yates shuffle (
_shuffle) seeded by the caller’srng, keeping the wheel layout reproducible from a seeded run. - Hit-test geometry is published as a side effect of
drawWheel: the buttons cannot be hit before the first frame they are drawn. This matches the rest of the engine’s pattern where layout and hit zones share one source of truth. - Particle effects are emitted by probability (
Math.random() < dt * 14) rather than fixed cadence — frame-rate-independent spawn density. - Jackpot (
weapon_10) and the lethalhp_99slice each get a custom text style branch in_drawSegmentText; everything else falls through to the generic positive/negative styling. _roundRectis a local quadratic-curve rounded-rect helper used only by SPIN and EXIT — no shared utility import.- The
shipimport from../coreis currently unused; the file relies only ongame,W,H, anduiScale.