# pixi-reels
> Open-source, batteries-included slot machine reel engine for PixiJS v8. Fluent builder API, typed domain events, deterministic testing utilities, and a catalog of live mechanic demos (classic lines, scatters → free spins, hold & win, cascades, sticky wilds, anticipation + slam-stop). MIT licensed. TypeScript. Works with any PixiJS v8 app.
Key facts:
- Latest version: 0.1
- License: MIT
- Language: TypeScript (ESM + CJS dual format)
- Peer deps: pixi.js ^8.17, gsap ^3.14 (+ optional @esotericsoftware/spine-pixi-v8)
- Repo: https://github.com/schmooky/pixi-reels
- Test count: 85 vitest tests passing
- Demo count: 6 live mechanic sandboxes on the site
- Docs site: https://pixi-reels.dev
## Quick install
```bash
pnpm add pixi-reels pixi.js gsap
```
## Shortest working example
```ts
import { Application } from 'pixi.js';
import { ReelSetBuilder, SpriteSymbol } from 'pixi-reels';
const app = new Application();
await app.init({ width: 900, height: 540 });
document.body.appendChild(app.canvas);
const reelSet = new ReelSetBuilder()
.reels(5).visibleSymbols(3).symbolSize(140, 140)
.symbols((r) => { r.register('cherry', SpriteSymbol, { textures: { cherry } }); })
.ticker(app.ticker)
.build();
app.stage.addChild(reelSet);
const result = await reelSet.spin(); // { symbols, wasSkipped, duration }
```
## Essential docs
- /guides/getting-started — install + first reel set
- /guides/your-first-reelset — 5-step tutorial with verify checks
- /guides/symbols — register Sprite, AnimatedSprite, Spine, or custom symbols
- /guides/spin-lifecycle — spin → start → spin → anticipation → stop events
- /guides/speed-modes — Normal, Turbo, SuperTurbo profiles
- /guides/win-animations — spotlight.cycle() + playWin()
- /guides/cheats-and-testing — FakeTicker + CheatEngine for deterministic tests
- /guides/debugging — debugSnapshot(), debugGrid(), enableDebug()
## API reference
- /wiki/api-reelset — main orchestrator (spin, setResult, setAnticipation, skip, setSpeed)
- /wiki/api-builder — fluent builder (reels, visibleSymbols, symbolSize, symbols, weights, ticker)
- /wiki/api-events — every typed event: spin:start, spin:allStarted, spin:stopping, spin:reelLanded, spin:allLanded, spin:complete, skip:requested, skip:completed, speed:changed, spotlight:start, spotlight:end, destroyed
- /wiki/api-phases — StartPhase, SpinPhase, AnticipationPhase, StopPhase (ReelPhase base class)
- /wiki/glossary — slot terminology (anticipation, scatter, wild, hold & win, RTP, cascade, near-miss)
## Spine demos (using real Bonanza-style skeletons)
Canonical animation vocabulary per symbol skeleton:
- idle · landing · win · disintegration · reactions/react_{u,d,l,r,ul,ur,dl,dr}
- /spine/landing-animation — fire `landing` on each symbol via spin:reelLanded listener
- /spine/win-celebration — parallel `playWin()` on matched cells, `scatter_win` for scatters
- /spine/reactions — 8-way `reactions/react_
` on non-winners pointing at nearest winner (Chebyshev distance)
- /spine/disintegrate-cascade — Spine `disintegration` animation as onWinnersVanish in runCascade
`SpineReelSymbol` API: playWin(), playLanding(), playOut(), playBlur(), playOnTrack(track, animName), get spine() for raw Spine access, per-symbol animations override ({ low1: { idle: 'ide' } }) to patch asset typos without editing assets.
## Architecture (visual deep dive, no UML/mermaid)
- /architecture/overview — component graph: builder → reel set → reels + subsystems
- /architecture/classes — composition tree, ownership chain, Disposable hierarchy
- /architecture/events — timeline of every event in a spin (reel-set + per-reel tracks)
- /architecture/spin-lifecycle — state machine per reel: IDLE → START → SPIN → [ANTICIPATION] → STOP → LANDED
- /architecture/cascade — gravity physics; why survivors with no winners below them must not move
- /architecture/testing — FakeTicker + HeadlessSymbol + createTestReelSet harness
## Recipes (single-idea how-tos with live micro-demos)
- /recipes/remove-symbol — fade+shrink a cell out before the next stage lands (cascade pop)
- /recipes/anticipate-a-reel — reelSet.setAnticipation([reelIndex]) before setResult() slows that reel
- /recipes/expand-a-symbol — animate symbol.view.scale.y from 1 to visibleRows on spin:complete
- /recipes/animate-paylines — reelSet.spotlight.cycle({ lines, perLine, dim, repeat })
- /recipes/slam-stop — if isSpinning, call skip(); SpinResult.wasSkipped indicates the outcome
- /recipes/near-miss — N-1 scatters + setAnticipation on the near reel (or use forceNearMiss cheat)
- /recipes/texture-atlas-symbols — PIXI.Assets.load(atlas.json) returns a Spritesheet with `textures` keyed by frame id. Pass the map to SpriteSymbol (crisp) or BlurSpriteSymbol (blur-on-spin). Covers atlas + separate-images modes.
## Mechanic demos (live sandboxes with cheats)
- /demos/classic-lines — 5×3 left-to-right line wins + spotlight cycle. Cheat: force a line, force full-grid jackpot.
- /demos/scatter-triggers-fs — 5×3 → 3+ scatters trigger free spins. Cheat: force N scatters, near-miss on reel 5.
- /demos/hold-and-win-respin — 5×3 coins lock, respin until grid fills. Cheat: guaranteed coin each spin.
- /demos/cascade-multiplier — 5×5 cascade/tumble with multiplier. Accepts array or AsyncIterable of stage grids (batch or streamed server responses).
- /demos/sticky-wilds — wilds persist N spins. Cheat: force wild on specific cell.
- /demos/anticipation-slam — slow-hold last reels or skip() for slam-stop.
- /demos/sprite-classic — 5×3 with real sprite art from a TexturePacker atlas (schmooky/prototype-symbols). Motion-blur on SPIN, crisp on land. BlurSpriteSymbol (examples/shared/BlurSpriteSymbol.ts) swaps textures on `phase:enter 'spin' / 'stop'`.
## Testing utilities (exported from `pixi-reels`)
- `FakeTicker` — drop-in PIXI.Ticker replacement with `tick(ms)`
- `HeadlessSymbol` — zero-render symbol for tests
- `createTestReelSet({ reels, visibleRows, symbolIds })` — headless ReelSet + FakeTicker
- `spinAndLand(reelSet, grid)` — synchronous deterministic spin lifecycle
- `expectGrid(reelSet, grid)` — diff-friendly grid assertion
- `captureEvents(reelSet, names)` — in-order event log
- `countSymbol(reelSet, id)` — visible count
## Cheats library (examples/shared/cheats.ts)
CheatEngine + `forceGrid`, `forceLine(row, id)`, `forceScatters(n, id)` (exact count), `forceNearMiss`, `forceCell`, `holdAndWinProgress`, `cascadeSequence`, `cascadingStages` (returns full stage sequence in meta), `forceAnticipation`. All seeded with Mulberry32 PRNG for reproducibility.
## Cascade loop helper (examples/shared/cascadeLoop.ts)
`runCascade(reelSet, stages, opts)` — accepts `string[][][]` or `AsyncIterable` so the same code path works for batch responses and streamed server stages. Identifies winners via `diffCells(prev, next)`, fades them out, spins to the next stage. Customizable `onWinnersVanish`, `onStageLanded`, `vanishDuration`, `pauseBetween`.