PR pixi-reels
All recipes

Cascade pops with WinPresenter

Drive cascade cell pops with the same WinPresenter you use for paylines. One API, one shape.

Loading recipe…

The minimum code

const presenter = new WinPresenter(reelSet, {
  symbolAnim: async (symbol) => /* your pop animation */,
});

await reelSet.runCascade({
  detectWinners: (g) => yourDetector(g),
  nextGrid:      (prev, winners) => yourNextGrid(prev, winners),
  onCascade: async ({ chain, winners }) => {
    if (winners.length === 0) return;
    await presenter.show([{
      id: chain,
      cells: winners.map(w => ({ reelIndex: w.reel, rowIndex: w.row })),
      value: winners.length * 10,
    }]);
  },
  // `destroySymbols` runs AFTER `onCascade` resolves. The presenter already
  // drove the visual feedback, so we suppress the implode's zIndex bump
  // (the destroy still snaps the cells to alpha 0, but invisibly).
  destroyOptions: { zIndex: null },
});

One shape, two use cases

A “win” is just cells. Whether those cells came from a payline, a cluster, or a cascade pop, the presenter treats them the same. No isPayline / isCluster narrowing. just win.cells.

For mixed rounds (a scatter cluster + a payline in the same spin), pass them together:

await presenter.show([
  { id: 0, cells: paylineCells, value: 50 },
  { id: 1, cells: scatterCells, value: 500 },   // sorts first (default value desc)
]);

UX guardrails

  • destroyOptions: { zIndex: null } keeps the library’s implode invisible. the presenter already played the visual.
  • presenter.abort() on spin:start. slam-spin mid-cascade cancels cleanly.
  • If you’d rather KEEP the library’s implode AS WELL as the presenter pop, drop destroyOptions entirely. The presenter resolves before destroySymbols runs, so they read as one continuous pop.