PR pixi-reels
v1.0.1 · MIT · PixiJS v8

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.

classic lines scatters free spins hold & win cascades tumbles sticky wilds walking wilds anticipation slam-stop multipliers respins jackpots bonus buy mystery symbols classic lines scatters free spins hold & win cascades tumbles sticky wilds walking wilds anticipation slam-stop multipliers respins jackpots bonus buy mystery symbols
Why pixi-reels

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.

10 lines to liftoff

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.
your-first-reelset.ts
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' ], ...]
Mechanics catalog

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.

basics spotlight

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
Open demo
scatter free-spins

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
Open demo
hold-and-win respin

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
Open demo
cascade tumble

Cascade + multiplier

6×5 · tumble × multiplier

Wins disappear, new symbols fall in. Each cascade multiplies payouts.

  • Scripted 4-cascade sequence
  • Single tumble
Open demo
wild sticky

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
Open demo
anticipation skip

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
Open demo
sprites atlas blur-on-spin

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
Open demo
big-symbols wild lines

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+
Open demo
megaways multiways ways

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
Open demo
pyramid cascade gravity

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
Open demo
FAQ

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.