PR pixi-reels
Wiki · Architecture
Lifecycle

Spin lifecycle

One spin as a state machine, per reel.

Each reel moves through four phases, independently. The diagram below shows the transitions and what triggers each one. Anticipation is optional — a reel skips it unless setAnticipation([i]) put its index on the list. skip() can short-circuit any phase into an immediate landing.

IDLE speed = 0 spin() START accelerate + step-back tween complete SPIN speed = max symbols wrap if anticipationReels includes this reel default ANTICIPATION slow-hold for tension anticipationDelay ms hold complete STOP decelerate place target bounce + snap landed event skip() CALLER TRIGGERS setResult(symbols[][]) while all reels are in SPIN phase setAnticipation([i, j]) marks reels for ANTICIPATION
Idle · at rest
Conditional path · dashed = may be skipped
Required transition
Caller actions · drive the state machine

What each phase actually does

START

A brief downward "step-back" (pulling the handle), then GSAP tweens reel.speed from 0 to spinSpeed with accelerationEase. Each reel's start is delayed by reelIndex × spinDelay, producing the classic staggered kickoff.

SPIN

Reel holds at full speed indefinitely. Symbols wrap vertically via ReelMotion; as each symbol wraps, its identity is swapped for a new one from the RandomSymbolProvider. Waits for setResult() and spin:allStarted before any stop can begin.

ANTICIPATION (optional)

Reel holds at a reduced speed for anticipationDelay ms. Entered only if setAnticipation() included this reel. Fires spin:stopping on entry, not phase:enter "stop" yet.

STOP

GSAP timeline decelerates to 0, calls reel.placeSymbols(visible) to lock in the target, then overshoots by bounceDistance and snaps back. Fires landed when complete.

The generation counter

Internally SpinController increments _spinGeneration on every spin() and skip(). Every async chain checks the generation before continuing — an in-flight phase from a previous spin quietly no-ops if the player spun again or skipped. That's how you can hammer the spin button without state corruption.

What skip() does exactly

  1. Emits skip:requested.
  2. Force-completes every active phase's GSAP timeline.
  3. Bumps _spinGeneration so pending async chains exit.
  4. If setResult() was already called: places the target symbols directly. Otherwise snaps current symbols to grid.
  5. Emits spin:reelLanded + spin:allLanded + spin:complete with wasSkipped: true.
  6. Emits skip:completed.

The spinAndLand test helper uses this exact path. That's why it's synchronous — no ticker, no tweens, just direct symbol placement and event emission.