The mechanic
new ReelSetBuilder()
.reels(5)
.visibleRowsPerReel([3, 5, 5, 5, 3])
.reelAnchor('center') // diamond shape
.tumble({
fall: { duration: 280, ease: 'power3.in', rowStagger: 60 },
dropIn: { duration: 450, ease: 'power3.out', rowStagger: 60, distance: 'perHole' },
})
A stiff power3.out drop-in (no overshoot) keeps the symbols landing hard. the bouncy default would fight the win animation that arrives a frame later.
Gravity refill (not “redrop the whole frame”)
A naive cascade phase would fall all old symbols out then drop all new ones in. That’s wrong for a tumble: survivors should stay put, only the gaps left by winners should refill from above.
The fix is the canonical cascade pair, reelSet.destroySymbols(winners) + reelSet.refill({ winners, grid }):
await reelSet.destroySymbols(winnerCells); // pop animation
const next = computeRefillGrid(grid, winnerCells); // gravity-correct grid (string[][])
await reelSet.refill({ // survivors slide; new arrive
winners: winnerCells,
grid: next.map((visible) => ({ visible })), // wrap as ColumnTarget[]
});
Per column the engine computes per-cell offsets:
- New symbol filling cleared row R -> starts (R+1) slots above the viewport.
- Survivor at final row R, originally at row N -> falls (R - N) slots.
- A symbol whose
R === Ndoesn’t move at all.
So a 5-row reel with a single winner at row 0 only animates one symbol. the new top. Everything below stays put. Jagged shapes work the same way; each reel’s visibleRows is read at refill time.
Ways evaluation on a jagged grid
Same algorithm as MultiWays: walk reels left-to-right counting reels that contain the kind (or wild). The pyramid’s 3-row outer reels just have fewer cells per reel. the chain logic doesn’t care:
for (let c = 0; c < REEL_COUNT; c++) {
const matches = grid[c].flatMap((s, r) =>
s === kind || s === 'wild' ? [{ reelIndex: c, rowIndex: r }] : []);
if (matches.length === 0) break;
cellsByReel.push(matches);
}
Round flow
roundBus.emit('round:reset'). WinBox to 0.- Initial
reelSet.spin()→setDropOrder('ltr')→setResult(grid). reelSet.runCascade({ detectWinners, nextGrid })loops the chain. the awaited promise resolves withRunCascadeResultwhen it ends.- Inside
onCascade:roundBus.emit('win:add', cascadeWin). WinBox additively tickups. - Status shows
N CASCADES. TOTAL.