pixi-reels
For authors

Cheats & testing

pixi-reels is built so you can test mechanics, not canvases. Two tools make that possible:

  • createTestReelSet. a headless reel set wired to a FakeTicker. No renderer, no DOM.
  • CheatEngine (in the private @pixi-reels/cheats package). a pluggable outcome source that forces grids, scatters, lines, cascades on demand.

Together they let you write assertions like “this mechanic produces exactly 3 scatters, triggers anticipation on reel 5, and emits spin:complete.”

1. Headless reel set#

import { createTestReelSet, expectGrid } from 'pixi-reels/testing';

const { reelSet, spinAndLand, destroy } = createTestReelSet({
  reels: 5,
  visibleCells: 3,
  symbolIds: ['cherry', 'seven', 'wild', 'scatter'],
});

await spinAndLand([
  { visible: ['wild','wild','wild'] },
  { visible: ['wild','wild','wild'] },
  { visible: ['wild','wild','wild'] },
  { visible: ['wild','wild','wild'] },
  { visible: ['wild','wild','wild'] },
]);

expectGrid(reelSet, /* same 5×3 grid of 'wild' */);
destroy();

spinAndLand() calls spin() → setResult() → slamStop(), which bypasses all timing and resolves on a microtask. Perfect for CI. (slamStop() lands every reel outright; it does not go through the two-stage skipSpin() boost.)

2. Force outcomes with cheats#

Cheats are pure functions that take a context and return a grid.

import { CheatEngine, forceScatters, forceLine, holdAndWinProgress } from '@pixi-reels/cheats';

const engine = new CheatEngine({
  reelCount: 5, visibleCells: 3,
  symbolIds: ['a', 'b', 'scatter', 'coin'],
  seed: 42,
});

engine.register({
  id: 'scat3',
  label: 'Force 3 scatters',
  enabled: true,
  cheat: forceScatters(3, 'scatter'),
});

const { symbols, anticipationReels } = engine.next();
await spinAndLand(symbols.map((visible) => ({ visible })));

Built-in cheats#

CheatEffect
forceGrid(grid)Every spin returns this exact grid.
forceLine(cellIndex, id)Fills one cell index across every reel with id.
forceScatters(n, id)Sprinkles exactly n of id at random cells.
forceNearMiss(n, id, nearReel)n-1 scatters, none on nearReel. Triggers anticipation there.
forceCell(reel, cell, id)Places id at [reel, cell].
holdAndWinProgress(coinId, p)Keeps held coins, adds one new coin with probability p.
cascadeSequence([g1, g2, ...])Emits grids in order.
forceAnticipation(reels)Sets anticipation reels without constraining symbols.

Write your own#

A cheat is (ctx) => { symbols, anticipationReels, meta } | null. Returning null means “pass through, let the next cheat or the RNG handle it.”

const forcePairWild: Cheat = (ctx) => {
  const grid = /* random grid */;
  grid[2][1] = 'wild';
  grid[3][1] = 'wild';
  return { symbols: grid, anticipationReels: [] };
};

3. Assert events fired correctly#

import { captureEvents } from 'pixi-reels/testing';

const log = captureEvents(reelSet, ['spin:start', 'spin:complete']);
await spinAndLand(grid);   // grid is ColumnTarget[]
expect(log.map((e) => e.event)).toEqual(['spin:start', 'spin:complete']);

4. See a mechanic running#

The standalone demo apps left this repo in 2.0 and live in a separate one. The live, replayable mechanics are the recipes on this site. each page runs the exact source it shows, so you can read the wiring and watch it land.