Slot reels,
ready to spin.
An open-source reel engine for PixiJS v8. Fluent builder, typed events, configurable spin phases, win presenters, and a headless testing harness.
The parts you'd have to build anyway.
Fluent builder
Zero to a running 5×3 reelset in 10 chainable lines. Validated on build().
Typed events
spin:start, spin:reelLanded, speed:changed, spotlight:end. fully typed payloads.
Headless testing
createTestReelSet + FakeTicker run the full spin lifecycle in Node. No renderer, no flaky timers.
Mechanic sandboxes
Every demo ships with a cheat panel. "Force 3 scatters" is one click.
Agent-friendly debug
enableDebug() attaches JSON + ASCII snapshots to window. Agents inspect state without reading the canvas.
Plug in anything
Symbols, phases, spinning modes, frame middleware. each extension is a named interface.
A builder so fluent
you'll never touch new ReelSet() again.
- One import. ReelSetBuilder wires every subsystem.
- Runtime validation. forget a required call? You'll know at build().
- Extension points everywhere. custom symbols, phases, spinning modes.
- No PixiJS coupling in your logic. tests run in Node.
import { ReelSetBuilder, SpriteSymbol } from 'pixi-reels';
const reelSet = new ReelSetBuilder()
.reels(5)
.visibleRows(3)
.symbolSize(140, 140)
.symbols((r) => {
r.register('cherry', SpriteSymbol, { textures: { cherry: cherryTex } });
r.register('seven', SpriteSymbol, { textures: { seven: sevenTex } });
r.register('bar', SpriteSymbol, { textures: { bar: barTex } });
})
.weights({ cherry: 40, seven: 10, bar: 20 })
.ticker(app.ticker)
.build();
const result = await reelSet.spin();
console.log(result.symbols); // [[ 'cherry','seven','bar' ], ...]
Every mechanic,
force any outcome.
Each demo is a live sandbox with the full source, a setup walkthrough, and a cheat panel that pins the result you want. no waiting on RNG.
Classic line pays
5×3 · left-to-right lines
The foundation every slot builds on. Forced wins, spotlight cycling.
- ◆Force winning line
- ◆Force full-grid jackpot
Scatter triggers Free Spins
5×3 · 3+ scatters → FS
Land three scatters anywhere, play a hit animation, enter bonus.
- ◆Force 3 scatters
- ◆Force 4 scatters
Hold & Win respin
5×3 · coins lock, respin until jackpot
Coins lock in place, respin until the grid fills. or 3 coins on middle row.
- ◆Guaranteed landing
- ◆Middle-row progression
Cascade + multiplier
6×5 · tumble × multiplier
Wins disappear, new symbols fall in. Each cascade multiplies payouts.
- ◆Scripted 4-cascade sequence
- ◆Single tumble
Sticky wilds
5×3 · wilds persist for N spins
A wild lands and stays for 3 spins. Stacks with more.
- ◆Force wild on reel 3
- ◆Force 3 stickies on row 2
Anticipation + slam-stop
5×3 · tension + skip()
Hold the last reel for tension, let the player slam-stop it.
- ◆Force anticipation on reels 4+5
- ◆Near-miss scatter
Classic lines with sprite symbols
5×3 · TexturePacker atlas · blur-on-spin
Real sprite art from a single atlas. Motion-blur textures swap in during SPIN, crisp on land.
- ◆Force royal line
- ◆Full-grid royal jackpot
Big symbols (2x2 BIG WILD)
5x4 · 2x2 wild block · glyph-only win animation
A 2x2 wild lands at an anchor cell, the engine paints the rest of the block with OCCUPIED stubs. Win animation pulses only the glyph - the card body stays still.
- ◆BIG WILD every third spin
- ◆Lines pay 3+
MultiWays (Megaways)
6 reels · setShape() per spin · ways pay
Per-spin per-reel row counts (2-7) via the engine's MultiWays + AdjustPhase. Ways = product of row counts. Wins are chains of consecutive reels.
- ◆Random shape every spin
- ◆Bias to bigger ways
Pyramid cascade (3-5-5-5-3)
Jagged diamond · ways pay · real gravity refill
visibleRowsPerReel + center anchor builds a diamond. Cascade refills use reelSet.refill({ winners, grid }) - survivors stay put, only the cleared slots refill from above.
- ◆Stiff drop (no bounce)
- ◆Survivors don't re-drop
Questions you probably have.
What is pixi-reels? +
An MIT-licensed TypeScript library that gives you a slot reel engine for PixiJS v8: fluent builder, typed events, default spin phases, win spotlight, pluggable symbols (Sprite, AnimatedSprite, Spine), and a headless testing mode.
Which PixiJS version is required? +
PixiJS v8 (^8.17.0) and GSAP v3 (^3.14.0). Spine is optional. only install @esotericsoftware/spine-pixi-v8 if you use SpineReelSymbol.
Can I test mechanics without a renderer? +
Yes. Import from pixi-reels/testing: createTestReelSet, FakeTicker, HeadlessSymbol, spinAndLand, expectGrid, captureEvents, countSymbol. You can run full spin lifecycles and assert on grid outcomes in Node. no canvas, no DOM.
How do I force a specific outcome? +
Call reelSet.setResult(grid) during a spin. For structured control (force a scatter count, force a near-miss, seed an RNG), examples/shared/cheats.ts exposes a CheatEngine with forceGrid, forceLine, forceScatters, forceNearMiss, forceCell, holdAndWinProgress, cascadeSequence, forceAnticipation. Deterministic via Mulberry32.
Which mechanics ship as demos? +
Line pays, scatter-triggered free spins, hold & win, cascade/tumble with multipliers, sticky wilds, anticipation, slam-stop. Each has its own /demos page with a cheat panel.
Does it handle server-driven cascades? +
Yes. reelSet.runCascade({ detectWinners, nextGrid }) owns the detect → destroy → refill loop. Your `nextGrid` callback can be async. await your server inside it and return the next grid. Pass `signal: AbortSignal` for caller-driven cancellation (the right shape for "player tapped slam between refills"). The returned `RunCascadeResult` carries `{ chainLength, totalWinners, finalGrid, wasSkipped }`.
Is it tree-shakeable? +
ESM + CJS dual format. Spine lives on a separate subpath (pixi-reels/spine) so its runtime drops out of your bundle when unused. Testing utilities are in pixi-reels/testing for the same reason.
Install, and you're spinning.
One package, three peer deps, ten lines of code. Or skip ahead and poke a demo.