PR pixi-reels
API

ReelSetBuilder

Every method returns this so you can chain. build() validates and returns a ReelSet. missing required calls throw with a single error listing every problem.

Required

Method
.reels(n)Number of columns.
.visibleRows(n) or .visibleRowsPerReel([n,...]) or .multiways({...})One of these picks the row layout.
.symbolSize(w, h)Per-symbol dimensions in px.
.symbols((r) => { r.register(...) })At least one symbol id.
.ticker(pixiTicker)Drives frame updates.

Missing any of these throws at .build() with a human-readable list of problems.

Optional

MethodDefault
.symbolGap(x, y){ x: 0, y: 0 }
.bufferSymbols(n)1 (clamped to ≥ 1. the motion layer needs one buffer row above and below for wrap detection)
.weights({ id: n })10 for each registered symbol
.symbolData({ id: { zIndex, unmask, size, weight } })Per-symbol metadata. Auto-routes unmask; size > 1x1 declares a big symbol.
.reelPixelHeights([h,...])Per-reel pixel-box heights for pyramid layouts. Derived from visibleRowsPerReel × symbolHeight if unset.
.reelAnchor('top' | 'center' | 'bottom')'center'. vertical alignment of short reels inside the tallest reel’s box.
.speed(name, profile)SpeedPresets.NORMAL under 'normal'
.initialSpeed(name)'normal'
.offsetConfig(config){ mode: 'none' }
.spinningMode(mode)new StandardMode()
.frameMiddleware(mw)(none). add as many as you want; sorted by priority.
.phases((f) => { f.register(...) })Built-in START/SPIN/STOP/ANTICIPATION (+ cascade phases when .tumble() is used).
.tumble({ fall, dropIn })(unset; required for spin({ mode: 'cascade' }) and reelSet.refill(...)). see the Tumble section below.
.multiways({ minRows, maxRows, reelPixelHeight })(unset; required for setShape(...))
.pinMigrationDuration(ms | fn)200 (MultiWays only)
.pinMigrationEase(ease)'power2.out' (MultiWays only)
.maskStrategy(strategy)Per-reel RectMaskStrategy. Auto-switches to SharedRectMaskStrategy when big or unmasked symbols are registered with symbolGap.x > 0.
.gsap(instance)Bound from the package’s own gsap import. Use this when your app and the engine resolve to different gsap instances (symlinked workspaces, npm link, misconfigured dedupe).
.initialFrame(ColumnTarget[])Random per weights(). One ColumnTarget per reel: { visible, bufferAbove?, bufferBelow? }.

.build()

Validates every required field, instantiates every subsystem, returns a ReelSet. Throws synchronously with the full list of problems. you never see a half-built reel set.

Minimal example

import { ReelSetBuilder, SpriteSymbol, SpeedPresets } from 'pixi-reels';

const reelSet = new ReelSetBuilder()
  .reels(5)
  .visibleRows(3)
  .symbolSize(140, 140)
  .symbolGap(4, 4)
  .symbols((r) => {
    r.register('cherry', SpriteSymbol, { textures: { cherry: cherryTex } });
  })
  .weights({ cherry: 40 })
  .speed('turbo', SpeedPresets.TURBO)
  .ticker(app.ticker)
  .build();

Tumble

.tumble(config?) enables cascade mechanics. It replaces the strip-spin + bounce-stop chain with a three-phase pipeline (cascade:fall · cascade:place · cascade:dropIn) and flips the default spin mode to 'cascade' for that builder.

.tumble({
  fall:   { duration: 280, ease: 'sine.in',       rowStagger: 40 },
  dropIn: { duration: 480, ease: 'back.out(1.6)', rowStagger: 50, distance: 'perHole' },
})

TumbleFallConfig

FieldTypeDefaultNotes
durationnumber (ms)300Each symbol’s fall-out tween length.
easeGSAP easing string'sine.in'Gravity feel. Any GSAP easing works.
rowStaggernumber (ms)0Delay between successive rows starting their fall. 0 = every row falls together.
rowOrder'bottomToTop' | 'topToBottom''bottomToTop'Which row begins first. The default pairs with setDropOrder('ltr') for the canonical “bottom-left first, top-right last” feel.

TumbleDropInConfig

FieldTypeDefaultNotes
durationnumber (ms)600Each symbol’s drop-in tween length.
easeGSAP easing string'power2.out'Clean deceleration with no overshoot. matches commercial slots that play a landing spine animation after the drop. Use 'back.out(1.5)' for soft overshoot, 'bounce.out' for cartoon bounce, 'expo.out' for slam.
rowStaggernumber (ms)60Delay between successive rows starting their drop. 0 = simultaneous (the canonical refill choice).
rowOrder'bottomToTop' | 'topToBottom''bottomToTop'Which row arrives first when rowStagger > 0.
distance'perHole' | 'auto' | number'perHole''perHole' is gravity-correct (each symbol falls exactly its own offset). 'auto' makes every symbol traverse the full column in unison (“rain column” feel). A number forces a uniform pixel distance.

Picking a feel

Five preset recipes you can copy verbatim. see the tumble feels recipe for side-by-side canvases.

Feelfall.easedropIn.easedropIn.rowStaggerWhen to use
Classicsine.inback.out(1.6)50 msDefault. Start here.
Cartoon bouncesine.inbounce.out70 msMulti-bounce landing.
Slampower4.inexpo.out25 msTurbo mode. sub-half-second tumbles.
Rain columnsine.insine.in0 ms (+ distance: 'auto')Match-3 / puzzle / chess-board grids.
Wavesine.inback.out(2.0)110 msStrong per-row stagger. row-by-row arrival.

Overriding a phase

.tumble() registers three named phases (cascade:fall, cascade:place, cascade:dropIn). Each is an independently overridable ReelPhase subclass:

builder
  .tumble({ /* ... */ })
  .phases((f) => f.register('cascade:fall', MyCometFallPhase));

See docs/recipes/tumble-cascade.md for the phase contract and an override example.

Spin options

reelSet.spin(options?) accepts SpinOptions. All fields are optional.

FieldTypeNotes
holdReelsnumber[]Reel indices to FREEZE for this spin. Held reels skip START/SPIN/STOP entirely and stay on their current symbols. They count as already-landed for spin:allLanded. Out-of-range / duplicate / non-integer entries are silently filtered. setAnticipation([...]) and setStopDelays([...]) filter held indices too.
mode'standard' | 'cascade'Phase-chain selector for this spin. Overrides the builder default. 'cascade' requires .tumble(...) on the builder. the engine throws at spin() if cascade phases aren’t registered.
// Hold reels 0 and 4; only the middle three reroll.
const spin = reelSet.spin({ holdReels: [0, 4] });
reelSet.setResult(serverGrid); // entries at 0/4 are ignored
await spin;

// Per-spin cascade override on a builder that registered both modes.
await reelSet.spin({ mode: 'cascade' });

See the spin lifecycle guide for how these options interact with the event timeline, and the cascades guide for the cascade-specific orchestration.