@energy8platform/game-engine 0.40.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@energy8platform/game-engine",
3
- "version": "0.40.0",
3
+ "version": "0.41.0",
4
4
  "description": "Universal casino game engine built on PixiJS v8 and @energy8platform/game-sdk",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs.js",
@@ -184,6 +184,8 @@ export interface AnticipationOverride {
184
184
  slowdown?: PerReel<number>;
185
185
  /** Extra hold before landing. Scalar, or per-reel indexed by reel index. */
186
186
  holdMs?: PerReel<number>;
187
+ /** Growth of the gap between successive cells within a reel. Scalar, or per-reel. */
188
+ cellStaggerRamp?: PerReel<number>;
187
189
  }
188
190
 
189
191
  export interface AnticipationConfig {
@@ -213,6 +215,16 @@ export interface AnticipationConfig {
213
215
  progressiveSlowdown: number;
214
216
  /** Extra hold (ms) added per successive anticipated reel: reel #i gets `holdMs + i * this`. */
215
217
  progressiveHoldMs: number;
218
+ /**
219
+ * `cascade-drop`: how the gap between SUCCESSIVE CELLS grows inside an anticipated reel. The
220
+ * gap before cell #i is `cellStagger * <reel slowdown> * progressiveCellStagger ** i`, so 1
221
+ * (the default) keeps the reel's cells evenly spaced and > 1 makes each symbol land later than
222
+ * the last — the drip that turns a reel into a countdown.
223
+ *
224
+ * It compounds with `progressiveSlowdown`: that widens the base gap reel by reel, this widens
225
+ * it cell by cell, so a later reel drips both slower AND with a steeper ramp.
226
+ */
227
+ progressiveCellStagger: number;
216
228
  /** Optional grid zoom while anticipating (magnum-opus uses 1.3×). */
217
229
  zoom: { enabled: boolean; scale: number; ms: number };
218
230
  }
@@ -503,6 +515,7 @@ export const DEFAULT_REEL_CONFIG: ReelSystemConfig = {
503
515
  decide: null,
504
516
  progressiveSlowdown: 1,
505
517
  progressiveHoldMs: 0,
518
+ progressiveCellStagger: 1,
506
519
  zoom: { enabled: false, scale: 1.15, ms: 600 },
507
520
  },
508
521
  cascade: {
@@ -151,6 +151,7 @@ export const REEL_FIELD_SCHEMA: Section[] = [
151
151
  { kind: 'range', path: 'anticipation.holdMs', label: 'Hold (ms)', min: 0, max: 1200, step: 50 },
152
152
  { kind: 'range', path: 'anticipation.progressiveSlowdown', label: 'Slowdown ramp ×/reel', min: 0.3, max: 1, step: 0.05 },
153
153
  { kind: 'range', path: 'anticipation.progressiveHoldMs', label: 'Hold ramp (ms/reel)', min: 0, max: 600, step: 25 },
154
+ { kind: 'range', path: 'anticipation.progressiveCellStagger', label: 'Cell ramp ×/cell', min: 1, max: 2.5, step: 0.05 },
154
155
  { kind: 'toggle', path: 'anticipation.zoom.enabled', label: 'Reel zoom' },
155
156
  { kind: 'range', path: 'anticipation.zoom.scale', label: 'Zoom scale', min: 1, max: 1.6, step: 0.05 },
156
157
  { kind: 'range', path: 'anticipation.zoom.ms', label: 'Zoom (ms)', min: 200, max: 1200, step: 50 },
@@ -18,10 +18,18 @@ export interface AnticipationDecision {
18
18
  slowdown: PerReel<number>;
19
19
  /** Extra hold before landing. Scalar or per-reel array, same as `slowdown`. */
20
20
  holdMs: PerReel<number>;
21
+ /** `cascade-drop`: growth of the gap between successive cells inside the reel (1 = even). */
22
+ cellStaggerRamp: PerReel<number>;
21
23
  }
22
24
 
23
25
  /** Fresh "nothing to anticipate" decision (fresh, so callers may mutate `reels` freely). */
24
- const none = (): AnticipationDecision => ({ active: false, reels: [], slowdown: 1, holdMs: 0 });
26
+ const none = (): AnticipationDecision => ({
27
+ active: false,
28
+ reels: [],
29
+ slowdown: 1,
30
+ holdMs: 0,
31
+ cellStaggerRamp: 1,
32
+ });
25
33
 
26
34
  export class AnticipationController {
27
35
  private _cfg: AnticipationConfig;
@@ -53,7 +61,7 @@ export class AnticipationController {
53
61
  if (!custom) return none();
54
62
  const o: AnticipationOverride = Array.isArray(custom) ? { reels: custom } : custom;
55
63
  if (!o.reels?.length) return none();
56
- return this.build(o.reels.slice(), o.slowdown, o.holdMs);
64
+ return this.build(o.reels.slice(), o.slowdown, o.holdMs, o.cellStaggerRamp);
57
65
  }
58
66
 
59
67
  if (Array.isArray(this._cfg.reels)) {
@@ -86,10 +94,12 @@ export class AnticipationController {
86
94
  reels: number[],
87
95
  slowdown?: PerReel<number>,
88
96
  holdMs?: PerReel<number>,
97
+ cellStaggerRamp?: PerReel<number>,
89
98
  ): AnticipationDecision {
90
99
  return {
91
100
  active: true,
92
101
  reels,
102
+ cellStaggerRamp: cellStaggerRamp ?? this._cfg.progressiveCellStagger,
93
103
  slowdown:
94
104
  slowdown ??
95
105
  this.ramp(reels, this._cfg.slowdownFactor, (base, i) =>
@@ -38,6 +38,12 @@ export interface SpinRunOpts {
38
38
  anticipateSlowdown?: PerReel<number>;
39
39
  /** Extra hold (ms) for anticipated reels. Scalar, or per-reel indexed by reel. */
40
40
  anticipateHoldMs?: PerReel<number>;
41
+ /**
42
+ * `cascade-drop`: growth of the gap between successive cells inside an anticipated reel — the
43
+ * gap before cell #i is `cellStagger * <reel slowdown> * ramp ** i`. 1 keeps the reel evenly
44
+ * spaced. Scalar, or per-reel indexed by reel.
45
+ */
46
+ anticipateCellStaggerRamp?: PerReel<number>;
41
47
  /**
42
48
  * Reels whose tape runs normally but whose landing is NOT handed back. The engine stops and
43
49
  * disposes of the tape as usual and leaves the real cells hidden and unseated; the caller owns
@@ -137,6 +143,7 @@ export class SpinEngine {
137
143
  const anticipate = new Set(opts?.anticipateReels ?? []);
138
144
  const defer = new Set(opts?.deferReveal ?? []);
139
145
  const holds: number[] = [];
146
+ const ramps: number[] = [];
140
147
  const out: ReelStopPlan[] = [];
141
148
  for (let reel = 0; reel < cols; reel++) {
142
149
  const idx = order(reel);
@@ -154,6 +161,7 @@ export class SpinEngine {
154
161
  const hold = isAnticipated ? perReelValue(opts?.anticipateHoldMs, reel, 0) * f : 0;
155
162
  stopTime += hold;
156
163
  holds[reel] = hold;
164
+ ramps[reel] = isAnticipated ? perReelValue(opts?.anticipateCellStaggerRamp, reel, 1) : 1;
157
165
  const speed = isAnticipated ? perReelValue(opts?.anticipateSlowdown, reel, 1) : 1;
158
166
 
159
167
  out.push({
@@ -166,7 +174,7 @@ export class SpinEngine {
166
174
  deferred: defer.has(reel),
167
175
  });
168
176
  }
169
- if (this._cfg.style === 'cascade-drop') this._planDrop(out, holds, f, order);
177
+ if (this._cfg.style === 'cascade-drop') this._planDrop(out, holds, ramps, f, order);
170
178
  return out;
171
179
  }
172
180
 
@@ -179,6 +187,7 @@ export class SpinEngine {
179
187
  private _planDrop(
180
188
  out: ReelStopPlan[],
181
189
  holds: number[],
190
+ ramps: number[],
182
191
  f: number,
183
192
  order: (reel: number) => number,
184
193
  ): void {
@@ -205,10 +214,15 @@ export class SpinEngine {
205
214
  const chained = chainFrom >= 0 && position > 0 && position >= chainFrom;
206
215
  const start = (chained ? previousEnd + gap : position * gap) + (holds[p.reel] ?? 0);
207
216
 
217
+ // The gap before cell #i is `cellStagger * scale * ramp**i` — accumulate rather than
218
+ // multiply, so a ramp of 1 stays exactly the old evenly-spaced schedule.
219
+ const ramp = ramps[p.reel] ?? 1;
208
220
  const cells: number[] = [];
221
+ let offset = 0;
209
222
  for (let i = 0; i < rows; i++) {
210
223
  const row = bottomUp ? rows - 1 - i : i;
211
- cells[row] = start + i * this._cfg.cellStagger * scale + fall;
224
+ cells[row] = start + offset + fall;
225
+ offset += this._cfg.cellStagger * scale * Math.pow(ramp, i);
212
226
  }
213
227
  p.cellStopTimes = cells;
214
228
  p.stopTime = cells.length ? Math.max(...cells) : start;
@@ -215,12 +215,15 @@ export function createReelSystem(opts: CreateReelSystemOptions): ReelSystem {
215
215
  function resolveAnticipation(target: CellData[][], runOpts?: SpinRunOpts): AnticipationDecision {
216
216
  const explicit = runOpts?.anticipateReels;
217
217
  if (!explicit) return anticipation.decide(target);
218
- if (!explicit.length) return { active: false, reels: [], slowdown: 1, holdMs: 0 };
218
+ if (!explicit.length)
219
+ return { active: false, reels: [], slowdown: 1, holdMs: 0, cellStaggerRamp: 1 };
219
220
  return {
220
221
  active: true,
221
222
  reels: explicit.slice(),
222
223
  slowdown: runOpts?.anticipateSlowdown ?? config.anticipation.slowdownFactor,
223
224
  holdMs: runOpts?.anticipateHoldMs ?? config.anticipation.holdMs,
225
+ cellStaggerRamp:
226
+ runOpts?.anticipateCellStaggerRamp ?? config.anticipation.progressiveCellStagger,
224
227
  };
225
228
  }
226
229
 
@@ -234,6 +237,7 @@ export function createReelSystem(opts: CreateReelSystemOptions): ReelSystem {
234
237
  anticipateReels: decision.active ? decision.reels : undefined,
235
238
  anticipateSlowdown: decision.slowdown,
236
239
  anticipateHoldMs: decision.holdMs,
240
+ anticipateCellStaggerRamp: decision.cellStaggerRamp,
237
241
  };
238
242
  }
239
243