@energy8platform/game-engine 0.38.0 → 0.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/slot.esm.js CHANGED
@@ -1073,6 +1073,14 @@ class ReelSpinController {
1073
1073
  // override only what they need.
1074
1074
  //
1075
1075
  // Design notes are in docs/reels-analysis-and-design.md.
1076
+ /** Resolve a `PerReel<T>` for one reel. `undefined` (or a hole in the array) yields `fallback`. */
1077
+ function perReelValue(value, reel, fallback) {
1078
+ if (value === undefined)
1079
+ return fallback;
1080
+ if (Array.isArray(value))
1081
+ return value[reel] ?? fallback;
1082
+ return value;
1083
+ }
1076
1084
  const FEATURE_KEYS = [
1077
1085
  'reelModifier', // pre-spin
1078
1086
  'giant',
@@ -1118,6 +1126,11 @@ const DEFAULT_REEL_CONFIG = {
1118
1126
  intensity: 'full',
1119
1127
  slamStop: true,
1120
1128
  symbolsPerReel: 6,
1129
+ cellStagger: 24,
1130
+ reelStaggerFactor: 0.4,
1131
+ dropFallFactor: 0.6,
1132
+ dropOrder: 'top-down',
1133
+ dropSequence: 'parallel',
1121
1134
  },
1122
1135
  anticipation: {
1123
1136
  enabled: false,
@@ -1126,6 +1139,9 @@ const DEFAULT_REEL_CONFIG = {
1126
1139
  reels: 'trailing',
1127
1140
  slowdownFactor: 0.3,
1128
1141
  holdMs: 400,
1142
+ decide: null,
1143
+ progressiveSlowdown: 1,
1144
+ progressiveHoldMs: 0,
1129
1145
  zoom: { enabled: false, scale: 1.15, ms: 600 },
1130
1146
  },
1131
1147
  cascade: {
@@ -1252,11 +1268,28 @@ function mergeReelConfig(base, partial) {
1252
1268
  function resolveReelConfig(partial) {
1253
1269
  return mergeReelConfig(DEFAULT_REEL_CONFIG, partial);
1254
1270
  }
1271
+ /** True only for `{}`-shaped objects — a class instance or a Date is NOT one. */
1272
+ function isCloneableRecord(v) {
1273
+ if (typeof v !== 'object' || v === null || Array.isArray(v))
1274
+ return false;
1275
+ const proto = Object.getPrototypeOf(v);
1276
+ return proto === Object.prototype || proto === null;
1277
+ }
1278
+ /**
1279
+ * Deep-clone a config. Hand-rolled rather than `structuredClone` because a config may carry
1280
+ * functions (`anticipation.decide`), which `structuredClone` refuses to copy. Functions and
1281
+ * anything that is not a plain object/array pass through by reference.
1282
+ */
1255
1283
  function structuredCloneSafe(v) {
1256
- // structuredClone is available in modern browsers + Node 17+; fall back to JSON for safety.
1257
- if (typeof structuredClone === 'function')
1258
- return structuredClone(v);
1259
- return JSON.parse(JSON.stringify(v));
1284
+ if (Array.isArray(v))
1285
+ return v.map((item) => structuredCloneSafe(item));
1286
+ if (isCloneableRecord(v)) {
1287
+ const out = {};
1288
+ for (const [k, val] of Object.entries(v))
1289
+ out[k] = structuredCloneSafe(val);
1290
+ return out;
1291
+ }
1292
+ return v;
1260
1293
  }
1261
1294
  /** Effective per-reel row counts (resolves Megaways `rowsPerReel`, else uniform `rows`). */
1262
1295
  function effectiveRowsPerReel(grid) {
@@ -1493,6 +1526,7 @@ class SpinEngine {
1493
1526
  _killed = false;
1494
1527
  _shaking = false;
1495
1528
  _temp = [];
1529
+ _deferred = new Set();
1496
1530
  constructor(grid, resolve, cfg, win) {
1497
1531
  this._grid = grid;
1498
1532
  this._resolve = resolve;
@@ -1536,6 +1570,8 @@ class SpinEngine {
1536
1570
  const cols = this._grid.cols;
1537
1571
  const order = (reel) => (this._cfg.stopOrder === 'rtl' ? cols - 1 - reel : reel);
1538
1572
  const anticipate = new Set(opts?.anticipateReels ?? []);
1573
+ const defer = new Set(opts?.deferReveal ?? []);
1574
+ const holds = [];
1539
1575
  const out = [];
1540
1576
  for (let reel = 0; reel < cols; reel++) {
1541
1577
  const idx = order(reel);
@@ -1551,23 +1587,70 @@ class SpinEngine {
1551
1587
  else
1552
1588
  stopTime = (this._cfg.spinUp + this._cfg.hold + idx * this._cfg.stopStagger) * f;
1553
1589
  const isAnticipated = anticipate.has(reel);
1554
- if (isAnticipated)
1555
- stopTime += (opts?.anticipateHoldMs ?? 0) * f;
1590
+ const hold = isAnticipated ? perReelValue(opts?.anticipateHoldMs, reel, 0) * f : 0;
1591
+ stopTime += hold;
1592
+ holds[reel] = hold;
1593
+ const speed = isAnticipated ? perReelValue(opts?.anticipateSlowdown, reel, 1) : 1;
1556
1594
  out.push({
1557
1595
  reel,
1558
1596
  stopTime,
1559
1597
  landing: data.targetGrid[reel] ?? [],
1560
1598
  settle: { amp: this._cfg.settle.amp, ms: this._cfg.settle.ms * f },
1561
1599
  anticipated: isAnticipated,
1600
+ slowdown: Math.max(1, 1 / (speed || 1)),
1601
+ deferred: defer.has(reel),
1562
1602
  });
1563
1603
  }
1604
+ if (this._cfg.style === 'cascade-drop')
1605
+ this._planDrop(out, holds, f, order);
1564
1606
  return out;
1565
1607
  }
1608
+ /**
1609
+ * `cascade-drop` lays its reels out on a different clock from the tape styles: a reel is a
1610
+ * sequence of per-cell arrivals, not one deceleration. Overwrite `stopTime` with the moment the
1611
+ * reel has fully landed and fill in `cellStopTimes`, so `plan()` stays the single source of truth
1612
+ * for WHEN anything happens — `_runDrop` below only executes these numbers.
1613
+ */
1614
+ _planDrop(out, holds, f, order) {
1615
+ const bottomUp = this._cfg.dropOrder === 'bottom-up';
1616
+ // walk the reels in STOP order, so `stopOrder: 'rtl'` reverses the drop as well
1617
+ const byPosition = [];
1618
+ for (let reel = 0; reel < out.length; reel++)
1619
+ byPosition[order(reel)] = reel;
1620
+ // the position from which reels stop overlapping and start queueing behind each other
1621
+ const chainFrom = this._cfg.dropSequence === 'chained'
1622
+ ? 0
1623
+ : this._cfg.dropSequence === 'chained-when-anticipated'
1624
+ ? byPosition.findIndex((reel) => out[reel].anticipated)
1625
+ : -1;
1626
+ let previousEnd = 0;
1627
+ for (let position = 0; position < byPosition.length; position++) {
1628
+ const p = out[byPosition[position]];
1629
+ const rows = this._grid.rowsOf(p.reel);
1630
+ const scale = f * p.slowdown;
1631
+ const gap = this._cfg.stopStagger * this._cfg.reelStaggerFactor * scale;
1632
+ const fall = this._cfg.spinUp * this._cfg.dropFallFactor * scale;
1633
+ // by formula while reels may overlap; queued behind the previous reel once the chain starts
1634
+ const chained = chainFrom >= 0 && position > 0 && position >= chainFrom;
1635
+ const start = (chained ? previousEnd + gap : position * gap) + (holds[p.reel] ?? 0);
1636
+ const cells = [];
1637
+ for (let i = 0; i < rows; i++) {
1638
+ const row = bottomUp ? rows - 1 - i : i;
1639
+ cells[row] = start + i * this._cfg.cellStagger * scale + fall;
1640
+ }
1641
+ p.cellStopTimes = cells;
1642
+ p.stopTime = cells.length ? Math.max(...cells) : start;
1643
+ previousEnd = p.stopTime;
1644
+ }
1645
+ }
1566
1646
  /** Execute the spin for every reel concurrently. */
1567
1647
  async run(data, opts) {
1568
1648
  this._killed = false;
1569
1649
  this._temp = [];
1570
1650
  const plan = this.plan(data, opts);
1651
+ // remembered for skip(): a deferred reel's cells belong to the caller, not to us
1652
+ this._deferred = new Set(plan.filter((p) => p.deferred).map((p) => p.reel));
1653
+ opts?.onPlan?.(plan);
1571
1654
  const f = this.scale(opts);
1572
1655
  await Promise.all(plan.map((p) => this._runReel(p, data, opts, f)));
1573
1656
  this._cleanupTemp();
@@ -1585,9 +1668,9 @@ class SpinEngine {
1585
1668
  return this._runSwap(p, data, opts, f);
1586
1669
  }
1587
1670
  }
1588
- /** Anticipation time-stretch factor for a reel (>=1, longer = slower). */
1589
- slowOf(p, opts) {
1590
- return p.anticipated ? Math.max(1, 1 / (opts?.anticipateSlowdown ?? 1)) : 1;
1671
+ /** Anticipation time-stretch factor for a reel (>=1, longer = slower). Resolved in `plan()`. */
1672
+ slowOf(p) {
1673
+ return p.slowdown;
1591
1674
  }
1592
1675
  // ── swap: cycle symbols quickly in the real cells, then land ──────────────
1593
1676
  async _runSwap(p, data, opts, f) {
@@ -1598,7 +1681,7 @@ class SpinEngine {
1598
1681
  const blur = this._applyBlur(cells, true);
1599
1682
  const tickMs = 1000 / 30;
1600
1683
  // anticipation makes the reel spin longer before it lands
1601
- const ticks = Math.max(6, Math.floor((p.stopTime * this.slowOf(p, opts)) / tickMs));
1684
+ const ticks = Math.max(6, Math.floor((p.stopTime * this.slowOf(p)) / tickMs));
1602
1685
  for (let i = 0; i < ticks; i++) {
1603
1686
  if (this._killed)
1604
1687
  break;
@@ -1607,8 +1690,24 @@ class SpinEngine {
1607
1690
  await Tween.delay(tickMs);
1608
1691
  }
1609
1692
  blur?.();
1610
- for (let r = 0; r < cells.length; r++)
1611
- cells[r].setData(p.landing[r] ?? { symbol: null });
1693
+ if (p.deferred) {
1694
+ // the caller owns this reel's result — go dark and unseated instead of handing it back.
1695
+ // NB 'swap' cycles the tape THROUGH the real cells, so a deferred reel only truly withholds
1696
+ // its result when `SpinData.strip` supplies filler for it; otherwise the tape is built from
1697
+ // the landing symbols. 'strip' and 'cascade-drop' have no such caveat.
1698
+ cells.forEach((c) => {
1699
+ c.setData({ symbol: null });
1700
+ c.visible = false;
1701
+ });
1702
+ opts?.onReelStop?.(p.reel, p);
1703
+ return;
1704
+ }
1705
+ for (let r = 0; r < cells.length; r++) {
1706
+ const cell = p.landing[r] ?? { symbol: null };
1707
+ cells[r].setData(cell);
1708
+ opts?.onCellSeated?.(p.reel, r, cell);
1709
+ }
1710
+ opts?.onReelStop?.(p.reel, p);
1612
1711
  await this._settle(p.reel, p.settle, f);
1613
1712
  await this._frameShake(p.landing);
1614
1713
  }
@@ -1643,7 +1742,7 @@ class SpinEngine {
1643
1742
  this._grid.addChild(tape);
1644
1743
  this._temp.push(tape);
1645
1744
  const clearBlur = this._applyBlur([tape], true);
1646
- const slow = this.slowOf(p, opts);
1745
+ const slow = this.slowOf(p);
1647
1746
  // overshoot/settle honour the configured settle (amp in px, easing)
1648
1747
  const overshoot = p.settle.amp || step * 0.18;
1649
1748
  await Tween.to(tape, { y: restY + overshoot }, p.stopTime * slow, easingByName('easeInOutQuad'));
@@ -1653,12 +1752,21 @@ class SpinEngine {
1653
1752
  }
1654
1753
  clearBlur?.();
1655
1754
  await Tween.to(tape, { y: restY }, Math.max(120, p.settle.ms), easingByName(this._cfg.settle.easing));
1656
- // hand the result back to the real cells
1657
- for (let r = 0; r < rows; r++)
1658
- realCells[r].setData(p.landing[r] ?? { symbol: null });
1659
- realCells.forEach((c) => (c.visible = true));
1755
+ // hand the result back to the real cells — unless the caller deferred the reveal, in which
1756
+ // case the tape simply goes away and the reel is left dark, unseated and owned by the caller
1757
+ if (!p.deferred) {
1758
+ for (let r = 0; r < rows; r++) {
1759
+ const cell = p.landing[r] ?? { symbol: null };
1760
+ realCells[r].setData(cell);
1761
+ opts?.onCellSeated?.(p.reel, r, cell);
1762
+ }
1763
+ realCells.forEach((c) => (c.visible = true));
1764
+ }
1660
1765
  tape.destroy();
1661
1766
  this._temp = this._temp.filter((t) => t !== tape);
1767
+ opts?.onReelStop?.(p.reel, p);
1768
+ if (p.deferred)
1769
+ return;
1662
1770
  // squash the real cells on impact when enabled
1663
1771
  if (this._cfg.squash.enabled)
1664
1772
  await Promise.all(realCells.map((c) => this._squashCell(c, f)));
@@ -1667,22 +1775,36 @@ class SpinEngine {
1667
1775
  // ── cascade-drop: symbols drop in from above with stagger + bounce + squash ─
1668
1776
  async _runDrop(p, opts, f) {
1669
1777
  const rows = this._grid.rowsOf(p.reel);
1778
+ if (p.deferred) {
1779
+ // nothing to drop — the caller brings this reel in itself
1780
+ for (let r = 0; r < rows; r++)
1781
+ this._grid.getCell(p.reel, r).visible = false;
1782
+ opts?.onReelStop?.(p.reel, p);
1783
+ return;
1784
+ }
1670
1785
  const step = this._grid.cellPosition(p.reel, 1).y - this._grid.cellPosition(p.reel, 0).y;
1671
- const slow = this.slowOf(p, opts); // anticipation drops the reel in more slowly
1786
+ const slow = this.slowOf(p); // anticipation drops the reel in more slowly
1787
+ const fall = this._cfg.spinUp * this._cfg.dropFallFactor * f * slow;
1788
+ const schedule = p.cellStopTimes ?? [];
1672
1789
  await Promise.all(Array.from({ length: rows }, (_, r) => r).map(async (r) => {
1673
1790
  if (this._killed)
1674
1791
  return;
1675
1792
  const cell = this._grid.getCell(p.reel, r);
1676
1793
  const to = this._grid.cellPosition(p.reel, r);
1677
- cell.setData(p.landing[r] ?? { symbol: null });
1794
+ const data = p.landing[r] ?? { symbol: null };
1795
+ cell.setData(data);
1678
1796
  cell.position.set(to.x, to.y - step * (rows + 1));
1679
1797
  cell.alpha = 1;
1680
- const delay = (p.reel * this._cfg.stopStagger * 0.4 + r * 24) * f * slow;
1798
+ // the plan says WHEN this cell seats; back off the fall to get when it must let go
1799
+ const delay = Math.max(0, (schedule[r] ?? fall) - fall);
1681
1800
  if (delay)
1682
1801
  await Tween.delay(delay);
1683
- await Tween.to(cell, { 'position.y': to.y }, this._cfg.spinUp * 0.6 * f * slow, easingByName(this._cfg.settle.easing));
1802
+ await Tween.to(cell, { 'position.y': to.y }, fall, easingByName(this._cfg.settle.easing));
1803
+ // the impact frame — fired before the squash so a game can sync its own hit feedback
1804
+ opts?.onCellSeated?.(p.reel, r, data);
1684
1805
  await this._squashCell(cell, f);
1685
1806
  }));
1807
+ opts?.onReelStop?.(p.reel, p);
1686
1808
  await this._frameShake(p.landing);
1687
1809
  }
1688
1810
  // ── shared helpers ────────────────────────────────────────────────────────
@@ -1746,13 +1868,17 @@ class SpinEngine {
1746
1868
  Tween.killTweensOf(this._grid);
1747
1869
  this._grid.x = 0; // undo any in-flight frame shake
1748
1870
  for (let c = 0; c < this._grid.cols; c++) {
1871
+ // a deferred reel's visibility belongs to the caller — a slam stop must not reveal it
1872
+ const deferred = this._deferred.has(c);
1749
1873
  for (let r = 0; r < this._grid.rowsOf(c); r++) {
1750
1874
  const cell = this._grid.getCell(c, r);
1751
1875
  if (cell.destroyed)
1752
1876
  continue;
1753
1877
  Tween.killTweensOf(cell);
1754
- cell.visible = true;
1755
- cell.alpha = 1;
1878
+ if (!deferred) {
1879
+ cell.visible = true;
1880
+ cell.alpha = 1;
1881
+ }
1756
1882
  cell.filters = [];
1757
1883
  cell.scale.set(1);
1758
1884
  }
@@ -1765,6 +1891,8 @@ class SpinEngine {
1765
1891
  //
1766
1892
  // Decides which trailing reels get the "anticipation" slow-down treatment, based purely on
1767
1893
  // the already-resolved landing grid (presentation only — never a secondary outcome decision).
1894
+ /** Fresh "nothing to anticipate" decision (fresh, so callers may mutate `reels` freely). */
1895
+ const none = () => ({ active: false, reels: [], slowdown: 1, holdMs: 0 });
1768
1896
  class AnticipationController {
1769
1897
  _cfg;
1770
1898
  constructor(cfg) {
@@ -1788,19 +1916,23 @@ class AnticipationController {
1788
1916
  */
1789
1917
  decide(targetGrid) {
1790
1918
  if (!this._cfg.enabled)
1791
- return { active: false, reels: [], slowdown: 1, holdMs: 0 };
1919
+ return none();
1920
+ // A game-supplied predicate replaces the symbol counting entirely.
1921
+ if (this._cfg.decide) {
1922
+ const custom = this._cfg.decide(targetGrid);
1923
+ if (!custom)
1924
+ return none();
1925
+ const o = Array.isArray(custom) ? { reels: custom } : custom;
1926
+ if (!o.reels?.length)
1927
+ return none();
1928
+ return this.build(o.reels.slice(), o.slowdown, o.holdMs);
1929
+ }
1792
1930
  if (Array.isArray(this._cfg.reels)) {
1793
1931
  // explicit reel list — arm only if the threshold is met somewhere on the board
1794
1932
  const total = targetGrid.reduce((sum, reel) => sum + this.countOnReel(reel), 0);
1795
- const active = total >= this._cfg.threshold;
1796
- return active
1797
- ? {
1798
- active,
1799
- reels: this._cfg.reels.slice(),
1800
- slowdown: this._cfg.slowdownFactor,
1801
- holdMs: this._cfg.holdMs,
1802
- }
1803
- : { active: false, reels: [], slowdown: 1, holdMs: 0 };
1933
+ if (total < this._cfg.threshold)
1934
+ return none();
1935
+ return this.build(this._cfg.reels.slice());
1804
1936
  }
1805
1937
  // 'trailing': find the reel where the cumulative count hits the threshold
1806
1938
  let running = 0;
@@ -1813,13 +1945,37 @@ class AnticipationController {
1813
1945
  }
1814
1946
  }
1815
1947
  if (armReel < 0)
1816
- return { active: false, reels: [], slowdown: 1, holdMs: 0 };
1948
+ return none();
1817
1949
  const reels = [];
1818
1950
  for (let c = armReel + 1; c < targetGrid.length; c++)
1819
1951
  reels.push(c);
1820
1952
  if (reels.length === 0)
1821
- return { active: false, reels: [], slowdown: 1, holdMs: 0 };
1822
- return { active: true, reels, slowdown: this._cfg.slowdownFactor, holdMs: this._cfg.holdMs };
1953
+ return none();
1954
+ return this.build(reels);
1955
+ }
1956
+ /** Assemble a decision, applying the configured progression unless the caller pinned values. */
1957
+ build(reels, slowdown, holdMs) {
1958
+ return {
1959
+ active: true,
1960
+ reels,
1961
+ slowdown: slowdown ??
1962
+ this.ramp(reels, this._cfg.slowdownFactor, (base, i) => i === 0 ? base : base * Math.pow(this._cfg.progressiveSlowdown, i)),
1963
+ holdMs: holdMs ??
1964
+ this.ramp(reels, this._cfg.holdMs, (base, i) => base + this._cfg.progressiveHoldMs * i),
1965
+ };
1966
+ }
1967
+ /**
1968
+ * A flat scalar when the progression is a no-op, else an array INDEXED BY REEL so the engine can
1969
+ * read a per-reel value straight out of `plan()`.
1970
+ */
1971
+ ramp(reels, base, at) {
1972
+ if (at(base, 1) === base)
1973
+ return base;
1974
+ const out = [];
1975
+ reels.forEach((reel, i) => {
1976
+ out[reel] = at(base, i);
1977
+ });
1978
+ return out;
1823
1979
  }
1824
1980
  /** Optionally zoom the grid in while anticipating, then settle back. Returns a reset fn. */
1825
1981
  async zoomIn(grid) {
@@ -2871,6 +3027,33 @@ function createReelSystem(opts) {
2871
3027
  function ctx(freeSpins) {
2872
3028
  return { grid, resolve, cfg: config, fx, board, freeSpins, log };
2873
3029
  }
3030
+ /**
3031
+ * Which reels get the anticipation treatment for this spin. An explicit `anticipateReels` on the
3032
+ * run options WINS over the configured decision — passing it is how a game drives anticipation
3033
+ * from its own logic. Omit it and the configured `AnticipationController` decides.
3034
+ */
3035
+ function resolveAnticipation(target, runOpts) {
3036
+ const explicit = runOpts?.anticipateReels;
3037
+ if (!explicit)
3038
+ return anticipation.decide(target);
3039
+ if (!explicit.length)
3040
+ return { active: false, reels: [], slowdown: 1, holdMs: 0 };
3041
+ return {
3042
+ active: true,
3043
+ reels: explicit.slice(),
3044
+ slowdown: runOpts?.anticipateSlowdown ?? config.anticipation.slowdownFactor,
3045
+ holdMs: runOpts?.anticipateHoldMs ?? config.anticipation.holdMs,
3046
+ };
3047
+ }
3048
+ /** Fold a resolved decision back onto the caller's run options (everything else passes through). */
3049
+ function mergeAnticipation(runOpts, decision) {
3050
+ return {
3051
+ ...runOpts,
3052
+ anticipateReels: decision.active ? decision.reels : undefined,
3053
+ anticipateSlowdown: decision.slowdown,
3054
+ anticipateHoldMs: decision.holdMs,
3055
+ };
3056
+ }
2874
3057
  buildGrid();
2875
3058
  const api = {
2876
3059
  view,
@@ -2913,20 +3096,22 @@ function createReelSystem(opts) {
2913
3096
  update(partial) {
2914
3097
  api.setConfig(mergeReelConfig(config, partial));
2915
3098
  },
3099
+ anticipationFor(target, runOpts) {
3100
+ return resolveAnticipation(target, runOpts);
3101
+ },
3102
+ planSpin(target, runOpts) {
3103
+ const decision = resolveAnticipation(target, runOpts);
3104
+ return spin.plan({ targetGrid: target }, mergeAnticipation(runOpts, decision));
3105
+ },
2916
3106
  async spin(target, runOpts) {
2917
3107
  const data = { targetGrid: target };
2918
- const decision = anticipation.decide(target);
3108
+ const decision = resolveAnticipation(target, runOpts);
2919
3109
  let resetZoom = null;
2920
3110
  if (decision.active) {
2921
3111
  log?.(`Anticipation on reels [${decision.reels.join(', ')}]`);
2922
3112
  resetZoom = await anticipation.zoomIn(grid);
2923
3113
  }
2924
- await spin.run(data, {
2925
- ...runOpts,
2926
- anticipateReels: decision.active ? decision.reels : undefined,
2927
- anticipateSlowdown: decision.slowdown,
2928
- anticipateHoldMs: decision.holdMs,
2929
- });
3114
+ await spin.run(data, mergeAnticipation(runOpts, decision));
2930
3115
  if (resetZoom)
2931
3116
  await resetZoom();
2932
3117
  board = target;
@@ -3171,5 +3356,5 @@ class MultiplierAccumulator {
3171
3356
  }
3172
3357
  }
3173
3358
 
3174
- export { AnimatedSymbol, AnticipationController, BigWinOverlay, CascadeController, CountUpDisplay, DEFAULT_REEL_CONFIG, EASING_BY_NAME, FEATURES, FEATURE_KEYS, FEATURE_LIST, INTENSITY_SCALE, MultiplierAccumulator, PRESETS, PRESET_LIST, ReelGrid, ReelSpinController, ReelStepController, SpinEngine, SymbolCell, TumbleController, buildReelStepTape, cellPositionOf, createReelSystem, easingByName, effectiveRowsPerReel, mergeReelConfig, pickTier, resolveGeometry, resolveGridGeometry, resolveReelConfig, tierIndexAtValue, valueAt, waysCount };
3359
+ export { AnimatedSymbol, AnticipationController, BigWinOverlay, CascadeController, CountUpDisplay, DEFAULT_REEL_CONFIG, EASING_BY_NAME, FEATURES, FEATURE_KEYS, FEATURE_LIST, INTENSITY_SCALE, MultiplierAccumulator, PRESETS, PRESET_LIST, ReelGrid, ReelSpinController, ReelStepController, SpinEngine, SymbolCell, TumbleController, buildReelStepTape, cellPositionOf, createReelSystem, easingByName, effectiveRowsPerReel, mergeReelConfig, perReelValue, pickTier, resolveGeometry, resolveGridGeometry, resolveReelConfig, tierIndexAtValue, valueAt, waysCount };
3175
3360
  //# sourceMappingURL=slot.esm.js.map