PR pixi-reels
Docs · 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. skipSpin() can short-circuit any phase into an immediate landing.

stateDiagram-v2
  [*] --> IDLE
  IDLE --> START : spin()
  START --> SPIN : tween complete
  SPIN --> ANTICIPATION : if anticipationReels<br/>includes this reel
  SPIN --> STOP : default
  ANTICIPATION --> STOP : hold complete
  STOP --> IDLE : landed event

  SPIN --> IDLE : skipSpin()
  ANTICIPATION --> IDLE : skipSpin()
  STOP --> IDLE : skipSpin()
  START --> IDLE : skipSpin()

  note left of START
    accelerate + step-back
  end note
  note right of SPIN
    speed = max
    symbols wrap
    setResult() called here
    setAnticipation() called here
  end note
  note right of ANTICIPATION
    slow-hold for tension
    anticipationDelay ms
  end note
  note right of STOP
    decelerate
    place target
    bounce + snap
  end note
  
Per-reel state machine. setResult and setAnticipation are caller actions during SPIN; skipSpin short-circuits any phase to landing.
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 skipSpin(). 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 skipSpin() 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.