@energy8platform/game-engine 0.39.0 → 0.41.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/README.md +52 -2
- package/dist/devtools.cjs.js +6 -0
- package/dist/devtools.cjs.js.map +1 -1
- package/dist/devtools.d.ts +35 -0
- package/dist/devtools.esm.js +6 -0
- package/dist/devtools.esm.js.map +1 -1
- package/dist/reel-panel-client.cjs.js +6 -0
- package/dist/reel-panel-client.cjs.js.map +1 -1
- package/dist/reel-panel-client.esm.js +6 -0
- package/dist/reel-panel-client.esm.js.map +1 -1
- package/dist/slot.cjs.js +72 -11
- package/dist/slot.cjs.js.map +1 -1
- package/dist/slot.d.ts +58 -1
- package/dist/slot.esm.js +72 -11
- package/dist/slot.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/slot/config/ReelSystemConfig.ts +38 -0
- package/src/slot/devtools/fieldSchema.ts +3 -0
- package/src/slot/index.ts +2 -0
- package/src/slot/motion/AnticipationController.ts +12 -2
- package/src/slot/motion/SpinEngine.ts +77 -12
- package/src/slot/system/ReelSystem.ts +5 -1
package/dist/slot.d.ts
CHANGED
|
@@ -411,6 +411,10 @@ interface GridConfig {
|
|
|
411
411
|
}
|
|
412
412
|
type MotionStyle = 'swap' | 'strip' | 'cascade-drop';
|
|
413
413
|
type StopMode = 'sequential' | 'sync' | 'random';
|
|
414
|
+
/** `cascade-drop` fill direction within one reel. */
|
|
415
|
+
type DropOrder = 'top-down' | 'bottom-up';
|
|
416
|
+
/** How `cascade-drop` spaces reels: by formula, or one strictly after the other. */
|
|
417
|
+
type DropSequence = 'parallel' | 'chained' | 'chained-when-anticipated';
|
|
414
418
|
type StopOrder = 'ltr' | 'rtl';
|
|
415
419
|
type Intensity = 'full' | 'reduced' | 'minimal';
|
|
416
420
|
interface SettleConfig {
|
|
@@ -462,6 +466,25 @@ interface MotionConfig {
|
|
|
462
466
|
reelStaggerFactor: number;
|
|
463
467
|
/** `cascade-drop`: fall duration as a fraction of `spinUp`. Default 0.6. */
|
|
464
468
|
dropFallFactor: number;
|
|
469
|
+
/**
|
|
470
|
+
* `cascade-drop`: which cell of a reel lands first. `'top-down'` (default, the engine's original
|
|
471
|
+
* behaviour) deals the reel like cards from the top; `'bottom-up'` fills it the way gravity
|
|
472
|
+
* would — the lowest cell arrives first and the rest stack on top of it.
|
|
473
|
+
*/
|
|
474
|
+
dropOrder: DropOrder;
|
|
475
|
+
/**
|
|
476
|
+
* `cascade-drop`: how reels are spaced.
|
|
477
|
+
* - `'parallel'` (default) starts every reel at its own formula offset —
|
|
478
|
+
* `reel * stopStagger * reelStaggerFactor` — so a reel can open while the previous one is
|
|
479
|
+
* still dropping. Fast, and what a normal spin wants.
|
|
480
|
+
* - `'chained'` starts a reel only once the previous one has seated its LAST cell, plus that
|
|
481
|
+
* same offset as the gap. One reel at a time, always — correct, but it makes every spin as
|
|
482
|
+
* long as the sum of its reels.
|
|
483
|
+
* - `'chained-when-anticipated'` runs the un-armed reels in parallel and switches to the chain
|
|
484
|
+
* from the first ANTICIPATED reel onwards. The base spin keeps its stop window; the hunt for
|
|
485
|
+
* the last scatter goes strictly reel by reel, each slower than the last.
|
|
486
|
+
*/
|
|
487
|
+
dropSequence: DropSequence;
|
|
465
488
|
}
|
|
466
489
|
/** What a game-supplied `AnticipationConfig.decide` may return instead of a bare reel list. */
|
|
467
490
|
interface AnticipationOverride {
|
|
@@ -471,6 +494,8 @@ interface AnticipationOverride {
|
|
|
471
494
|
slowdown?: PerReel<number>;
|
|
472
495
|
/** Extra hold before landing. Scalar, or per-reel indexed by reel index. */
|
|
473
496
|
holdMs?: PerReel<number>;
|
|
497
|
+
/** Growth of the gap between successive cells within a reel. Scalar, or per-reel. */
|
|
498
|
+
cellStaggerRamp?: PerReel<number>;
|
|
474
499
|
}
|
|
475
500
|
interface AnticipationConfig {
|
|
476
501
|
enabled: boolean;
|
|
@@ -499,6 +524,16 @@ interface AnticipationConfig {
|
|
|
499
524
|
progressiveSlowdown: number;
|
|
500
525
|
/** Extra hold (ms) added per successive anticipated reel: reel #i gets `holdMs + i * this`. */
|
|
501
526
|
progressiveHoldMs: number;
|
|
527
|
+
/**
|
|
528
|
+
* `cascade-drop`: how the gap between SUCCESSIVE CELLS grows inside an anticipated reel. The
|
|
529
|
+
* gap before cell #i is `cellStagger * <reel slowdown> * progressiveCellStagger ** i`, so 1
|
|
530
|
+
* (the default) keeps the reel's cells evenly spaced and > 1 makes each symbol land later than
|
|
531
|
+
* the last — the drip that turns a reel into a countdown.
|
|
532
|
+
*
|
|
533
|
+
* It compounds with `progressiveSlowdown`: that widens the base gap reel by reel, this widens
|
|
534
|
+
* it cell by cell, so a later reel drips both slower AND with a steeper ramp.
|
|
535
|
+
*/
|
|
536
|
+
progressiveCellStagger: number;
|
|
502
537
|
/** Optional grid zoom while anticipating (magnum-opus uses 1.3×). */
|
|
503
538
|
zoom: {
|
|
504
539
|
enabled: boolean;
|
|
@@ -743,6 +778,12 @@ interface SpinRunOpts {
|
|
|
743
778
|
anticipateSlowdown?: PerReel<number>;
|
|
744
779
|
/** Extra hold (ms) for anticipated reels. Scalar, or per-reel indexed by reel. */
|
|
745
780
|
anticipateHoldMs?: PerReel<number>;
|
|
781
|
+
/**
|
|
782
|
+
* `cascade-drop`: growth of the gap between successive cells inside an anticipated reel — the
|
|
783
|
+
* gap before cell #i is `cellStagger * <reel slowdown> * ramp ** i`. 1 keeps the reel evenly
|
|
784
|
+
* spaced. Scalar, or per-reel indexed by reel.
|
|
785
|
+
*/
|
|
786
|
+
anticipateCellStaggerRamp?: PerReel<number>;
|
|
746
787
|
/**
|
|
747
788
|
* Reels whose tape runs normally but whose landing is NOT handed back. The engine stops and
|
|
748
789
|
* disposes of the tape as usual and leaves the real cells hidden and unseated; the caller owns
|
|
@@ -773,6 +814,13 @@ interface ReelStopPlan {
|
|
|
773
814
|
slowdown: number;
|
|
774
815
|
/** True when `deferReveal` withheld this reel's landing (see `SpinRunOpts.deferReveal`). */
|
|
775
816
|
deferred: boolean;
|
|
817
|
+
/**
|
|
818
|
+
* `cascade-drop` only: ms from spin start at which each cell of this reel seats, INDEXED BY ROW.
|
|
819
|
+
* The reel's fill direction lives in these numbers (`motion.dropOrder` decides which row is the
|
|
820
|
+
* smallest), as does its place in the chain (`motion.dropSequence`). `stopTime` is the largest
|
|
821
|
+
* of them — the frame the whole reel has landed.
|
|
822
|
+
*/
|
|
823
|
+
cellStopTimes?: number[];
|
|
776
824
|
}
|
|
777
825
|
declare class SpinEngine {
|
|
778
826
|
private _grid;
|
|
@@ -791,6 +839,13 @@ declare class SpinEngine {
|
|
|
791
839
|
private scale;
|
|
792
840
|
/** PURE: per-reel stop schedule. No Pixi mutation. */
|
|
793
841
|
plan(data: SpinData, opts?: SpinRunOpts): ReelStopPlan[];
|
|
842
|
+
/**
|
|
843
|
+
* `cascade-drop` lays its reels out on a different clock from the tape styles: a reel is a
|
|
844
|
+
* sequence of per-cell arrivals, not one deceleration. Overwrite `stopTime` with the moment the
|
|
845
|
+
* reel has fully landed and fill in `cellStopTimes`, so `plan()` stays the single source of truth
|
|
846
|
+
* for WHEN anything happens — `_runDrop` below only executes these numbers.
|
|
847
|
+
*/
|
|
848
|
+
private _planDrop;
|
|
794
849
|
/** Execute the spin for every reel concurrently. */
|
|
795
850
|
run(data: SpinData, opts?: SpinRunOpts): Promise<void>;
|
|
796
851
|
private _runReel;
|
|
@@ -817,6 +872,8 @@ interface AnticipationDecision {
|
|
|
817
872
|
slowdown: PerReel<number>;
|
|
818
873
|
/** Extra hold before landing. Scalar or per-reel array, same as `slowdown`. */
|
|
819
874
|
holdMs: PerReel<number>;
|
|
875
|
+
/** `cascade-drop`: growth of the gap between successive cells inside the reel (1 = even). */
|
|
876
|
+
cellStaggerRamp: PerReel<number>;
|
|
820
877
|
}
|
|
821
878
|
declare class AnticipationController {
|
|
822
879
|
private _cfg;
|
|
@@ -1135,4 +1192,4 @@ declare class MultiplierAccumulator {
|
|
|
1135
1192
|
}
|
|
1136
1193
|
|
|
1137
1194
|
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 };
|
|
1138
|
-
export type { AnimatedSymbolConfig, AnticipationConfig, AnticipationDecision, AnticipationOverride, BigWinOverlayConfig, BlurConfig, CarryPolicy, CascadeAnim, CascadeConfig, CascadeStepData, CascadeTimings, CellData, CellFrameStyle, CellSizeSpec, CellState, CountUpConfig, CreateReelSystemOptions, DecorationConfig, DeepPartial, EasingName, EvaluationMode, ExpandingWildConfig, FeatureContext, FeatureKey, FeaturesConfig, GeometryInput, GiantConfig, GridConfig, HoldAndSpinConfig, Intensity, MotionConfig, MotionStyle, MultiplierConfig, MysteryConfig, NudgeConfig, PerReel, PresetId, RandomWildConfig, ReelFeature, ReelGridConfig, ReelModifierConfig, ReelPreset, ReelSpinData, ReelSpinTimings, ReelStepData, ReelStopPlan$1 as ReelStopPlan, ReelSystem, ReelSystemConfig, ResolvedGeometry, SettleConfig, SpinData, SpinRunOpts, ReelStopPlan as SpinStopPlan, SplitConfig, SquashConfig, StackedConfig, StepRunOpts, StickyConfig, StopMode, StopOrder, SymbolCellConfig, SymbolResolver, SymbolTextures, SymbolView, TransformConfig, TumbleStep, WalkingWildConfig, WinConfig, WinTier };
|
|
1195
|
+
export type { AnimatedSymbolConfig, AnticipationConfig, AnticipationDecision, AnticipationOverride, BigWinOverlayConfig, BlurConfig, CarryPolicy, CascadeAnim, CascadeConfig, CascadeStepData, CascadeTimings, CellData, CellFrameStyle, CellSizeSpec, CellState, CountUpConfig, CreateReelSystemOptions, DecorationConfig, DeepPartial, DropOrder, DropSequence, EasingName, EvaluationMode, ExpandingWildConfig, FeatureContext, FeatureKey, FeaturesConfig, GeometryInput, GiantConfig, GridConfig, HoldAndSpinConfig, Intensity, MotionConfig, MotionStyle, MultiplierConfig, MysteryConfig, NudgeConfig, PerReel, PresetId, RandomWildConfig, ReelFeature, ReelGridConfig, ReelModifierConfig, ReelPreset, ReelSpinData, ReelSpinTimings, ReelStepData, ReelStopPlan$1 as ReelStopPlan, ReelSystem, ReelSystemConfig, ResolvedGeometry, SettleConfig, SpinData, SpinRunOpts, ReelStopPlan as SpinStopPlan, SplitConfig, SquashConfig, StackedConfig, StepRunOpts, StickyConfig, StopMode, StopOrder, SymbolCellConfig, SymbolResolver, SymbolTextures, SymbolView, TransformConfig, TumbleStep, WalkingWildConfig, WinConfig, WinTier };
|
package/dist/slot.esm.js
CHANGED
|
@@ -1129,6 +1129,8 @@ const DEFAULT_REEL_CONFIG = {
|
|
|
1129
1129
|
cellStagger: 24,
|
|
1130
1130
|
reelStaggerFactor: 0.4,
|
|
1131
1131
|
dropFallFactor: 0.6,
|
|
1132
|
+
dropOrder: 'top-down',
|
|
1133
|
+
dropSequence: 'parallel',
|
|
1132
1134
|
},
|
|
1133
1135
|
anticipation: {
|
|
1134
1136
|
enabled: false,
|
|
@@ -1140,6 +1142,7 @@ const DEFAULT_REEL_CONFIG = {
|
|
|
1140
1142
|
decide: null,
|
|
1141
1143
|
progressiveSlowdown: 1,
|
|
1142
1144
|
progressiveHoldMs: 0,
|
|
1145
|
+
progressiveCellStagger: 1,
|
|
1143
1146
|
zoom: { enabled: false, scale: 1.15, ms: 600 },
|
|
1144
1147
|
},
|
|
1145
1148
|
cascade: {
|
|
@@ -1569,6 +1572,8 @@ class SpinEngine {
|
|
|
1569
1572
|
const order = (reel) => (this._cfg.stopOrder === 'rtl' ? cols - 1 - reel : reel);
|
|
1570
1573
|
const anticipate = new Set(opts?.anticipateReels ?? []);
|
|
1571
1574
|
const defer = new Set(opts?.deferReveal ?? []);
|
|
1575
|
+
const holds = [];
|
|
1576
|
+
const ramps = [];
|
|
1572
1577
|
const out = [];
|
|
1573
1578
|
for (let reel = 0; reel < cols; reel++) {
|
|
1574
1579
|
const idx = order(reel);
|
|
@@ -1584,8 +1589,10 @@ class SpinEngine {
|
|
|
1584
1589
|
else
|
|
1585
1590
|
stopTime = (this._cfg.spinUp + this._cfg.hold + idx * this._cfg.stopStagger) * f;
|
|
1586
1591
|
const isAnticipated = anticipate.has(reel);
|
|
1587
|
-
|
|
1588
|
-
|
|
1592
|
+
const hold = isAnticipated ? perReelValue(opts?.anticipateHoldMs, reel, 0) * f : 0;
|
|
1593
|
+
stopTime += hold;
|
|
1594
|
+
holds[reel] = hold;
|
|
1595
|
+
ramps[reel] = isAnticipated ? perReelValue(opts?.anticipateCellStaggerRamp, reel, 1) : 1;
|
|
1589
1596
|
const speed = isAnticipated ? perReelValue(opts?.anticipateSlowdown, reel, 1) : 1;
|
|
1590
1597
|
out.push({
|
|
1591
1598
|
reel,
|
|
@@ -1597,8 +1604,53 @@ class SpinEngine {
|
|
|
1597
1604
|
deferred: defer.has(reel),
|
|
1598
1605
|
});
|
|
1599
1606
|
}
|
|
1607
|
+
if (this._cfg.style === 'cascade-drop')
|
|
1608
|
+
this._planDrop(out, holds, ramps, f, order);
|
|
1600
1609
|
return out;
|
|
1601
1610
|
}
|
|
1611
|
+
/**
|
|
1612
|
+
* `cascade-drop` lays its reels out on a different clock from the tape styles: a reel is a
|
|
1613
|
+
* sequence of per-cell arrivals, not one deceleration. Overwrite `stopTime` with the moment the
|
|
1614
|
+
* reel has fully landed and fill in `cellStopTimes`, so `plan()` stays the single source of truth
|
|
1615
|
+
* for WHEN anything happens — `_runDrop` below only executes these numbers.
|
|
1616
|
+
*/
|
|
1617
|
+
_planDrop(out, holds, ramps, f, order) {
|
|
1618
|
+
const bottomUp = this._cfg.dropOrder === 'bottom-up';
|
|
1619
|
+
// walk the reels in STOP order, so `stopOrder: 'rtl'` reverses the drop as well
|
|
1620
|
+
const byPosition = [];
|
|
1621
|
+
for (let reel = 0; reel < out.length; reel++)
|
|
1622
|
+
byPosition[order(reel)] = reel;
|
|
1623
|
+
// the position from which reels stop overlapping and start queueing behind each other
|
|
1624
|
+
const chainFrom = this._cfg.dropSequence === 'chained'
|
|
1625
|
+
? 0
|
|
1626
|
+
: this._cfg.dropSequence === 'chained-when-anticipated'
|
|
1627
|
+
? byPosition.findIndex((reel) => out[reel].anticipated)
|
|
1628
|
+
: -1;
|
|
1629
|
+
let previousEnd = 0;
|
|
1630
|
+
for (let position = 0; position < byPosition.length; position++) {
|
|
1631
|
+
const p = out[byPosition[position]];
|
|
1632
|
+
const rows = this._grid.rowsOf(p.reel);
|
|
1633
|
+
const scale = f * p.slowdown;
|
|
1634
|
+
const gap = this._cfg.stopStagger * this._cfg.reelStaggerFactor * scale;
|
|
1635
|
+
const fall = this._cfg.spinUp * this._cfg.dropFallFactor * scale;
|
|
1636
|
+
// by formula while reels may overlap; queued behind the previous reel once the chain starts
|
|
1637
|
+
const chained = chainFrom >= 0 && position > 0 && position >= chainFrom;
|
|
1638
|
+
const start = (chained ? previousEnd + gap : position * gap) + (holds[p.reel] ?? 0);
|
|
1639
|
+
// The gap before cell #i is `cellStagger * scale * ramp**i` — accumulate rather than
|
|
1640
|
+
// multiply, so a ramp of 1 stays exactly the old evenly-spaced schedule.
|
|
1641
|
+
const ramp = ramps[p.reel] ?? 1;
|
|
1642
|
+
const cells = [];
|
|
1643
|
+
let offset = 0;
|
|
1644
|
+
for (let i = 0; i < rows; i++) {
|
|
1645
|
+
const row = bottomUp ? rows - 1 - i : i;
|
|
1646
|
+
cells[row] = start + offset + fall;
|
|
1647
|
+
offset += this._cfg.cellStagger * scale * Math.pow(ramp, i);
|
|
1648
|
+
}
|
|
1649
|
+
p.cellStopTimes = cells;
|
|
1650
|
+
p.stopTime = cells.length ? Math.max(...cells) : start;
|
|
1651
|
+
previousEnd = p.stopTime;
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1602
1654
|
/** Execute the spin for every reel concurrently. */
|
|
1603
1655
|
async run(data, opts) {
|
|
1604
1656
|
this._killed = false;
|
|
@@ -1740,6 +1792,8 @@ class SpinEngine {
|
|
|
1740
1792
|
}
|
|
1741
1793
|
const step = this._grid.cellPosition(p.reel, 1).y - this._grid.cellPosition(p.reel, 0).y;
|
|
1742
1794
|
const slow = this.slowOf(p); // anticipation drops the reel in more slowly
|
|
1795
|
+
const fall = this._cfg.spinUp * this._cfg.dropFallFactor * f * slow;
|
|
1796
|
+
const schedule = p.cellStopTimes ?? [];
|
|
1743
1797
|
await Promise.all(Array.from({ length: rows }, (_, r) => r).map(async (r) => {
|
|
1744
1798
|
if (this._killed)
|
|
1745
1799
|
return;
|
|
@@ -1749,13 +1803,11 @@ class SpinEngine {
|
|
|
1749
1803
|
cell.setData(data);
|
|
1750
1804
|
cell.position.set(to.x, to.y - step * (rows + 1));
|
|
1751
1805
|
cell.alpha = 1;
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
f *
|
|
1755
|
-
slow;
|
|
1806
|
+
// the plan says WHEN this cell seats; back off the fall to get when it must let go
|
|
1807
|
+
const delay = Math.max(0, (schedule[r] ?? fall) - fall);
|
|
1756
1808
|
if (delay)
|
|
1757
1809
|
await Tween.delay(delay);
|
|
1758
|
-
await Tween.to(cell, { 'position.y': to.y },
|
|
1810
|
+
await Tween.to(cell, { 'position.y': to.y }, fall, easingByName(this._cfg.settle.easing));
|
|
1759
1811
|
// the impact frame — fired before the squash so a game can sync its own hit feedback
|
|
1760
1812
|
opts?.onCellSeated?.(p.reel, r, data);
|
|
1761
1813
|
await this._squashCell(cell, f);
|
|
@@ -1848,7 +1900,13 @@ class SpinEngine {
|
|
|
1848
1900
|
// Decides which trailing reels get the "anticipation" slow-down treatment, based purely on
|
|
1849
1901
|
// the already-resolved landing grid (presentation only — never a secondary outcome decision).
|
|
1850
1902
|
/** Fresh "nothing to anticipate" decision (fresh, so callers may mutate `reels` freely). */
|
|
1851
|
-
const none = () => ({
|
|
1903
|
+
const none = () => ({
|
|
1904
|
+
active: false,
|
|
1905
|
+
reels: [],
|
|
1906
|
+
slowdown: 1,
|
|
1907
|
+
holdMs: 0,
|
|
1908
|
+
cellStaggerRamp: 1,
|
|
1909
|
+
});
|
|
1852
1910
|
class AnticipationController {
|
|
1853
1911
|
_cfg;
|
|
1854
1912
|
constructor(cfg) {
|
|
@@ -1881,7 +1939,7 @@ class AnticipationController {
|
|
|
1881
1939
|
const o = Array.isArray(custom) ? { reels: custom } : custom;
|
|
1882
1940
|
if (!o.reels?.length)
|
|
1883
1941
|
return none();
|
|
1884
|
-
return this.build(o.reels.slice(), o.slowdown, o.holdMs);
|
|
1942
|
+
return this.build(o.reels.slice(), o.slowdown, o.holdMs, o.cellStaggerRamp);
|
|
1885
1943
|
}
|
|
1886
1944
|
if (Array.isArray(this._cfg.reels)) {
|
|
1887
1945
|
// explicit reel list — arm only if the threshold is met somewhere on the board
|
|
@@ -1910,10 +1968,11 @@ class AnticipationController {
|
|
|
1910
1968
|
return this.build(reels);
|
|
1911
1969
|
}
|
|
1912
1970
|
/** Assemble a decision, applying the configured progression unless the caller pinned values. */
|
|
1913
|
-
build(reels, slowdown, holdMs) {
|
|
1971
|
+
build(reels, slowdown, holdMs, cellStaggerRamp) {
|
|
1914
1972
|
return {
|
|
1915
1973
|
active: true,
|
|
1916
1974
|
reels,
|
|
1975
|
+
cellStaggerRamp: cellStaggerRamp ?? this._cfg.progressiveCellStagger,
|
|
1917
1976
|
slowdown: slowdown ??
|
|
1918
1977
|
this.ramp(reels, this._cfg.slowdownFactor, (base, i) => i === 0 ? base : base * Math.pow(this._cfg.progressiveSlowdown, i)),
|
|
1919
1978
|
holdMs: holdMs ??
|
|
@@ -2993,12 +3052,13 @@ function createReelSystem(opts) {
|
|
|
2993
3052
|
if (!explicit)
|
|
2994
3053
|
return anticipation.decide(target);
|
|
2995
3054
|
if (!explicit.length)
|
|
2996
|
-
return { active: false, reels: [], slowdown: 1, holdMs: 0 };
|
|
3055
|
+
return { active: false, reels: [], slowdown: 1, holdMs: 0, cellStaggerRamp: 1 };
|
|
2997
3056
|
return {
|
|
2998
3057
|
active: true,
|
|
2999
3058
|
reels: explicit.slice(),
|
|
3000
3059
|
slowdown: runOpts?.anticipateSlowdown ?? config.anticipation.slowdownFactor,
|
|
3001
3060
|
holdMs: runOpts?.anticipateHoldMs ?? config.anticipation.holdMs,
|
|
3061
|
+
cellStaggerRamp: runOpts?.anticipateCellStaggerRamp ?? config.anticipation.progressiveCellStagger,
|
|
3002
3062
|
};
|
|
3003
3063
|
}
|
|
3004
3064
|
/** Fold a resolved decision back onto the caller's run options (everything else passes through). */
|
|
@@ -3008,6 +3068,7 @@ function createReelSystem(opts) {
|
|
|
3008
3068
|
anticipateReels: decision.active ? decision.reels : undefined,
|
|
3009
3069
|
anticipateSlowdown: decision.slowdown,
|
|
3010
3070
|
anticipateHoldMs: decision.holdMs,
|
|
3071
|
+
anticipateCellStaggerRamp: decision.cellStaggerRamp,
|
|
3011
3072
|
};
|
|
3012
3073
|
}
|
|
3013
3074
|
buildGrid();
|