Cascading / tumbling reels

TL;DR

После выигрыша победившие символы взрываются (анимация исчезновения), верхние падают вниз, на пустоты сверху приходят новые символы из той же reelstrip-полоски. Заново evaluate. Цикл до прекращения выигрышей. Один base-spin может породить длинную цепочку tumble-ов с возрастающим global multiplier-ом — это и есть «комбо».

Названия-синонимы: cascade, tumble, avalanche (NetEnt), rolling reels (Microgaming). Геометрически разные имена, механика одинаковая.

Как работает

Алгоритм tumble loop

while win_data.totalWin > 0 and not wincap_triggered:
  отметить win_positions как explode
  tumble_board():
    для каждого reel:
      удалить explode-позиции
      сдвинуть оставшиеся вниз
      на пустоты сверху насыпать символы из reelstrip
      (top-symbol сохраняется!)
  evaluate_wins(board) → новый win_data
  wallet.update_spin_win(win_data.totalWin)
  if global_mult_increments_on_tumble:
    global_mult += 1
    emit update_global_mult_event
  emit tumble_board_event, tumble_win_event

Top-symbol сохраняется

Если в config include_padding = True, фронт показывает «padding-символы» сверху и снизу board (часть в-кадре). При tumble top-symbol сохраняется, не приходит новый — это важно для UX «полностью видимой полоски» reelstrip-а. Иначе первые позиции сразу после tumble могли бы выглядеть «телепортацией».

Реализация: на каждый tumble-step сохраняем индекс reelstrip-стопа (reel_pos); новые символы берутся из позиций выше изначального стопа, top-symbol = reelstrip[reel_pos - 1] остаётся неизменным.

Мат-эффект

  • Hit frequency на base-spin не меняется (триггерится первой evaluate). Но расширенный hit frequency «спин с хотя бы одной выплатой за tumble» заметно выше.
  • Volatility растёт за счёт длинных tumble-цепочек с multiplier-инкрементом.
  • Average win за раунд выше — суммируются tumble-ы.
  • RTP контролируется через paytable × probabilities для board ДО первого tumble. Tumble-цепочка считается рекурсивно при симуляции.

Варианты и подвиды

Global multiplier инкремент

  • +1 на каждый tumble в freegame — стандарт (Pragmatic Play Gates of Olympus, Sweet Bonanza).
  • +1 на каждый tumble в base — реже.
  • Persistent через freegame (не сбрасывается на каждый freespin) vs сбрасывается (стандарт «сбрасывается»).

Position-multiplier grid

  • В freegame каждая cell board может иметь multiplier (накапливается с каждым tumble на этой позиции, обычно 2× → 4× → 8× → … → 512×). Sweet Bonanza, Sugar Rush.
  • Multiplier-grid сохраняется через retrigger.

Multiplier-symbols на board

  • Особый символ M с написанным multiplier-значением (типа «3×»).
  • Не «взрывается» при tumble.
  • В конце цепочки tumble-ов суммируется и применяется к итогу. Gates of Olympus.

Без multiplier

Чистый tumble без множителей — Bonanza (NetEnt), Reactoonz (Play’n GO).

Реализационные заметки

Бэк (TS)

function runTumbleLoop(ctx: GameContext) {
  let winData = evaluateWins(ctx);
  emitWinInfoEvent(ctx, winData);
  ctx.wallet.updateSpinWin(winData.totalWin);
  ctx.wallet.tumbleWin += winData.totalWin;
 
  while (winData.totalWin > 0 && !ctx.wincapTriggered) {
    markExplode(ctx.board, winData.wins);
    tumbleBoard(ctx);                  // mutate board, emit tumbleBoard event
    if (ctx.config.globalMultIncrementOnTumble && ctx.gametype === "freegame") {
      ctx.globalMult += 1;
      emit({type: "updateGlobalMult", value: ctx.globalMult});
    }
    winData = evaluateWins(ctx);
    if (winData.totalWin > 0) {
      ctx.wallet.updateSpinWin(winData.totalWin);
      ctx.wallet.tumbleWin += winData.totalWin;
      emitWinInfoEvent(ctx, winData);
    }
    evaluateWincap(ctx);
  }
 
  // emit cumulative tumble banner
  emit({type: "tumbleBanner", amount: ctx.wallet.tumbleWin, globalMult: ctx.globalMult});
  ctx.wallet.tumbleWin = 0n;
}
 
function tumbleBoard(ctx) {
  for (let reel = 0; reel < ctx.board.length; reel++) {
    const explodedRows = collectExplodedRows(ctx.board[reel]);
    if (explodedRows.length === 0) continue;
    const remaining = ctx.board[reel].filter(s => !s.explode);
    const newSyms = nextSymbolsFromReelstrip(ctx.reelstrip[reel], ctx.reelPos[reel], explodedRows.length);
    ctx.board[reel] = [...newSyms, ...remaining];
    ctx.reelPos[reel] -= explodedRows.length;
  }
  emit({type: "tumbleBoard", removed: explodedPositions, newSymbols: newSymbolsByReel});
}

Edge cases

  • Wincap во время tumblewincapTriggered обрезает loop. Дополнительные tumble-ы не запускаются.
  • Бесконечный tumble — теоретически возможно (вся reelstrip из одинаковых символов). На практике reelstrip-композиция гарантирует прекращение. Должна быть hard-cap на число tumble-ов в loop (например, 50) как fail-safe против infinite loop, с error-метрикой.
  • Position-multiplier через retrigger — multiplier-grid между freespin-ами должен сохраняться.

Фронт

Эмитятся подряд:

  1. winInfo — что выиграло.
  2. tumbleBoard — какие позиции исчезают, какие приходят.
  3. (опц.) updateGlobalMult — multiplier увеличился.
  4. …повтор…
  5. tumbleBanner — итоговый banner с total-tumble-win и global mult.

Фронт делает анимацию win-highlight → explode → fall → reveal, потом повторяет evaluate ивента. Каждый шаг — отдельный animation pipeline.

Compliance / cert

  • Help раскрывает: «After a winning combination, winning symbols are removed and replaced by symbols falling from above. Multipliers may apply.»
  • Если global multiplier инкрементируется — это раскрывается явно.
  • Должны соблюдаться правила responsible product design про auto-play лимиты — длинные tumble-цепочки не должны заглушать «paused» state для игрока.

Примеры реализаций

  • NetEnt Gonzo’s Quest (avalanche, родоначальник tumble-механики в онлайне, 2010).
  • NetEnt Bonanza (Megaways + tumble).
  • Pragmatic Play Gates of Olympus (scatter-pays + tumble + global mult + multiplier-symbols).
  • Pragmatic Play Sweet Bonanza (cluster + tumble + multiplier-symbols).
  • Pragmatic Play Sugar Rush (cluster + tumble + position-multiplier-grid).
  • Stake math-sdk sample: games/0_0_cluster/, games/0_0_scatter/.

Связанные