@energy8platform/game-engine 0.40.0 → 0.42.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 +18 -4
- package/dist/animation.cjs.js.map +1 -1
- package/dist/animation.esm.js.map +1 -1
- package/dist/assets.cjs.js.map +1 -1
- package/dist/assets.esm.js.map +1 -1
- package/dist/audio.cjs.js.map +1 -1
- package/dist/audio.esm.js.map +1 -1
- package/dist/core.cjs.js.map +1 -1
- package/dist/core.esm.js.map +1 -1
- package/dist/debug.cjs.js.map +1 -1
- package/dist/debug.esm.js.map +1 -1
- package/dist/devtools.cjs.js +2 -0
- package/dist/devtools.cjs.js.map +1 -1
- package/dist/devtools.d.ts +12 -0
- package/dist/devtools.esm.js +2 -0
- package/dist/devtools.esm.js.map +1 -1
- package/dist/harness.cjs.js.map +1 -1
- package/dist/harness.esm.js.map +1 -1
- package/dist/host.cjs.js +5 -4
- package/dist/host.cjs.js.map +1 -1
- package/dist/host.d.ts +5 -2
- package/dist/host.esm.js +6 -5
- package/dist/host.esm.js.map +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js.map +1 -1
- package/dist/reel-panel-client.cjs.js +2 -0
- package/dist/reel-panel-client.cjs.js.map +1 -1
- package/dist/reel-panel-client.esm.js +2 -0
- package/dist/reel-panel-client.esm.js.map +1 -1
- package/dist/slot.cjs.js +24 -7
- package/dist/slot.cjs.js.map +1 -1
- package/dist/slot.d.ts +20 -0
- package/dist/slot.esm.js +24 -7
- package/dist/slot.esm.js.map +1 -1
- package/dist/vite.cjs.js.map +1 -1
- package/dist/vite.esm.js.map +1 -1
- package/package.json +2 -2
- package/src/host/createSlotGame.ts +6 -4
- package/src/host/shellConfig.ts +8 -5
- package/src/slot/config/ReelSystemConfig.ts +13 -0
- package/src/slot/devtools/fieldSchema.ts +1 -0
- package/src/slot/motion/AnticipationController.ts +12 -2
- package/src/slot/motion/SpinEngine.ts +16 -2
- package/src/slot/system/ReelSystem.ts +5 -1
package/dist/vite.cjs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite.cjs.js","sources":["../src/vite/index.ts"],"sourcesContent":[null],"names":["devBridgePlugin","spinPlugin"],"mappings":";;;;AAoCA;AAEA;;;;;;;;;;;;;;;;;;;;;;;AAuBG;AACG,SAAU,gBAAgB,CAAC,MAAA,GAAqB,EAAE,EAAA;IACtD,MAAM,OAAO,GAAa,EAAE;AAE5B,IAAA,IAAI,MAAM,CAAC,SAAS,EAAE;AACpB,QAAA,MAAM,UAAU,GAAG,MAAM,CAAC,eAAe,IAAI,cAAc;QAC3D,OAAO,CAAC,IAAI,CAACA,oBAAe,CAAC,UAAU,CAAC,CAAC;;AAEzC,QAAA,OAAO,CAAC,IAAI,CACVC,eAAU,CAAC;AACT,YAAA,QAAQ,EAAE,MAAM,CAAC,UAAU,IAAI,wBAAwB;YACvD,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,IAAI,EAAE,MAAM,CAAC,QAAQ;AACtB,SAAA,CAAC,CACH;IACH;AAEA,IAAA,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,IAAI,EAAE;IAElC,OAAO;AACL,QAAA,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,IAAI;AAEzB,QAAA,OAAO,EAAE;AACP,YAAA,GAAG,OAAO;AACV,YAAA,IAAK,QAAQ,CAAC,OAAoB,IAAI,EAAE,CAAC;AAC1C,SAAA;AAED,QAAA,KAAK,EAAE;AACL,YAAA,MAAM,EAAE,QAAQ;AAChB,YAAA,iBAAiB,EAAE,IAAI;AACvB,YAAA,SAAS,EAAE,KAAK;AAChB,YAAA,aAAa,EAAE;AACb,gBAAA,MAAM,EAAE;AACN,oBAAA,YAAY,EAAE;wBACZ,IAAI,EAAE,CAAC,SAAS,CAAC;AAClB,qBAAA;AACF,iBAAA;AACF,aAAA;YACD,GAAG,QAAQ,CAAC,KAAK;AAClB,SAAA;AAED,QAAA,MAAM,EAAE;AACN,YAAA,IAAI,EAAE,IAAI;AACV,YAAA,IAAI,EAAE,IAAI;YACV,GAAG,QAAQ,CAAC,MAAM;AACnB,SAAA;AAED,QAAA,OAAO,EAAE;AACP,YAAA,MAAM,EAAE;gBACN,SAAS;AACV,aAAA;YACD,GAAG,QAAQ,CAAC,OAAO;AACpB,SAAA;AAED,QAAA,YAAY,EAAE;AACZ,YAAA,OAAO,EAAE;gBACP,SAAS;AACV,aAAA;AACD,YAAA,cAAc,EAAE;AACd,gBAAA,MAAM,EAAE,QAAQ;AACjB,aAAA;YACD,GAAG,QAAQ,CAAC,YAAY;AACzB,SAAA;KACF;AACH
|
|
1
|
+
{"version":3,"file":"vite.cjs.js","sources":["../src/vite/index.ts"],"sourcesContent":[null],"names":["devBridgePlugin","spinPlugin"],"mappings":";;;;AAoCA;AAEA;;;;;;;;;;;;;;;;;;;;;;;AAuBG;AACG,SAAU,gBAAgB,CAAC,MAAA,GAAqB,EAAE,EAAA;IACtD,MAAM,OAAO,GAAa,EAAE;AAE5B,IAAA,IAAI,MAAM,CAAC,SAAS,EAAE;AACpB,QAAA,MAAM,UAAU,GAAG,MAAM,CAAC,eAAe,IAAI,cAAc;QAC3D,OAAO,CAAC,IAAI,CAACA,oBAAe,CAAC,UAAU,CAAC,CAAC;;AAEzC,QAAA,OAAO,CAAC,IAAI,CACVC,eAAU,CAAC;AACT,YAAA,QAAQ,EAAE,MAAM,CAAC,UAAU,IAAI,wBAAwB;YACvD,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,IAAI,EAAE,MAAM,CAAC,QAAQ;AACtB,SAAA,CAAC,CACH;IACH;AAEA,IAAA,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,IAAI,EAAE;IAElC,OAAO;AACL,QAAA,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,IAAI;AAEzB,QAAA,OAAO,EAAE;AACP,YAAA,GAAG,OAAO;AACV,YAAA,IAAK,QAAQ,CAAC,OAAoB,IAAI,EAAE,CAAC;AAC1C,SAAA;AAED,QAAA,KAAK,EAAE;AACL,YAAA,MAAM,EAAE,QAAQ;AAChB,YAAA,iBAAiB,EAAE,IAAI;AACvB,YAAA,SAAS,EAAE,KAAK;AAChB,YAAA,aAAa,EAAE;AACb,gBAAA,MAAM,EAAE;AACN,oBAAA,YAAY,EAAE;wBACZ,IAAI,EAAE,CAAC,SAAS,CAAC;AAClB,qBAAA;AACF,iBAAA;AACF,aAAA;YACD,GAAG,QAAQ,CAAC,KAAK;AAClB,SAAA;AAED,QAAA,MAAM,EAAE;AACN,YAAA,IAAI,EAAE,IAAI;AACV,YAAA,IAAI,EAAE,IAAI;YACV,GAAG,QAAQ,CAAC,MAAM;AACnB,SAAA;AAED,QAAA,OAAO,EAAE;AACP,YAAA,MAAM,EAAE;gBACN,SAAS;AACV,aAAA;YACD,GAAG,QAAQ,CAAC,OAAO;AACpB,SAAA;AAED,QAAA,YAAY,EAAE;AACZ,YAAA,OAAO,EAAE;gBACP,SAAS;AACV,aAAA;AACD,YAAA,cAAc,EAAE;AACd,gBAAA,MAAM,EAAE,QAAQ;AACjB,aAAA;YACD,GAAG,QAAQ,CAAC,YAAY;AACzB,SAAA;KACF;AACH;;;;;;;;;;"}
|
package/dist/vite.esm.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite.esm.js","sources":["../src/vite/index.ts"],"sourcesContent":[null],"names":[],"mappings":";;;AAoCA;AAEA;;;;;;;;;;;;;;;;;;;;;;;AAuBG;AACG,SAAU,gBAAgB,CAAC,MAAA,GAAqB,EAAE,EAAA;IACtD,MAAM,OAAO,GAAa,EAAE;AAE5B,IAAA,IAAI,MAAM,CAAC,SAAS,EAAE;AACpB,QAAA,MAAM,UAAU,GAAG,MAAM,CAAC,eAAe,IAAI,cAAc;QAC3D,OAAO,CAAC,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC;;AAEzC,QAAA,OAAO,CAAC,IAAI,CACV,UAAU,CAAC;AACT,YAAA,QAAQ,EAAE,MAAM,CAAC,UAAU,IAAI,wBAAwB;YACvD,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,IAAI,EAAE,MAAM,CAAC,QAAQ;AACtB,SAAA,CAAC,CACH;IACH;AAEA,IAAA,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,IAAI,EAAE;IAElC,OAAO;AACL,QAAA,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,IAAI;AAEzB,QAAA,OAAO,EAAE;AACP,YAAA,GAAG,OAAO;AACV,YAAA,IAAK,QAAQ,CAAC,OAAoB,IAAI,EAAE,CAAC;AAC1C,SAAA;AAED,QAAA,KAAK,EAAE;AACL,YAAA,MAAM,EAAE,QAAQ;AAChB,YAAA,iBAAiB,EAAE,IAAI;AACvB,YAAA,SAAS,EAAE,KAAK;AAChB,YAAA,aAAa,EAAE;AACb,gBAAA,MAAM,EAAE;AACN,oBAAA,YAAY,EAAE;wBACZ,IAAI,EAAE,CAAC,SAAS,CAAC;AAClB,qBAAA;AACF,iBAAA;AACF,aAAA;YACD,GAAG,QAAQ,CAAC,KAAK;AAClB,SAAA;AAED,QAAA,MAAM,EAAE;AACN,YAAA,IAAI,EAAE,IAAI;AACV,YAAA,IAAI,EAAE,IAAI;YACV,GAAG,QAAQ,CAAC,MAAM;AACnB,SAAA;AAED,QAAA,OAAO,EAAE;AACP,YAAA,MAAM,EAAE;gBACN,SAAS;AACV,aAAA;YACD,GAAG,QAAQ,CAAC,OAAO;AACpB,SAAA;AAED,QAAA,YAAY,EAAE;AACZ,YAAA,OAAO,EAAE;gBACP,SAAS;AACV,aAAA;AACD,YAAA,cAAc,EAAE;AACd,gBAAA,MAAM,EAAE,QAAQ;AACjB,aAAA;YACD,GAAG,QAAQ,CAAC,YAAY;AACzB,SAAA;KACF;AACH
|
|
1
|
+
{"version":3,"file":"vite.esm.js","sources":["../src/vite/index.ts"],"sourcesContent":[null],"names":[],"mappings":";;;AAoCA;AAEA;;;;;;;;;;;;;;;;;;;;;;;AAuBG;AACG,SAAU,gBAAgB,CAAC,MAAA,GAAqB,EAAE,EAAA;IACtD,MAAM,OAAO,GAAa,EAAE;AAE5B,IAAA,IAAI,MAAM,CAAC,SAAS,EAAE;AACpB,QAAA,MAAM,UAAU,GAAG,MAAM,CAAC,eAAe,IAAI,cAAc;QAC3D,OAAO,CAAC,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC;;AAEzC,QAAA,OAAO,CAAC,IAAI,CACV,UAAU,CAAC;AACT,YAAA,QAAQ,EAAE,MAAM,CAAC,UAAU,IAAI,wBAAwB;YACvD,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,IAAI,EAAE,MAAM,CAAC,QAAQ;AACtB,SAAA,CAAC,CACH;IACH;AAEA,IAAA,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,IAAI,EAAE;IAElC,OAAO;AACL,QAAA,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,IAAI;AAEzB,QAAA,OAAO,EAAE;AACP,YAAA,GAAG,OAAO;AACV,YAAA,IAAK,QAAQ,CAAC,OAAoB,IAAI,EAAE,CAAC;AAC1C,SAAA;AAED,QAAA,KAAK,EAAE;AACL,YAAA,MAAM,EAAE,QAAQ;AAChB,YAAA,iBAAiB,EAAE,IAAI;AACvB,YAAA,SAAS,EAAE,KAAK;AAChB,YAAA,aAAa,EAAE;AACb,gBAAA,MAAM,EAAE;AACN,oBAAA,YAAY,EAAE;wBACZ,IAAI,EAAE,CAAC,SAAS,CAAC;AAClB,qBAAA;AACF,iBAAA;AACF,aAAA;YACD,GAAG,QAAQ,CAAC,KAAK;AAClB,SAAA;AAED,QAAA,MAAM,EAAE;AACN,YAAA,IAAI,EAAE,IAAI;AACV,YAAA,IAAI,EAAE,IAAI;YACV,GAAG,QAAQ,CAAC,MAAM;AACnB,SAAA;AAED,QAAA,OAAO,EAAE;AACP,YAAA,MAAM,EAAE;gBACN,SAAS;AACV,aAAA;YACD,GAAG,QAAQ,CAAC,OAAO;AACpB,SAAA;AAED,QAAA,YAAY,EAAE;AACZ,YAAA,OAAO,EAAE;gBACP,SAAS;AACV,aAAA;AACD,YAAA,cAAc,EAAE;AACd,gBAAA,MAAM,EAAE,QAAQ;AACjB,aAAA;YACD,GAAG,QAAQ,CAAC,YAAY;AACzB,SAAA;KACF;AACH;;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@energy8platform/game-engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.42.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",
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
},
|
|
95
95
|
"dependencies": {
|
|
96
96
|
"@energy8platform/platform-core": ">=0.33.0",
|
|
97
|
-
"@energy8platform/shell": ">=0.
|
|
97
|
+
"@energy8platform/shell": ">=0.10.0"
|
|
98
98
|
},
|
|
99
99
|
"peerDependencies": {
|
|
100
100
|
"@energy8platform/game-sdk": "^2.9.0",
|
|
@@ -280,7 +280,8 @@ export async function createSlotGame<T extends SlotSpinResultBase = SlotSpinResu
|
|
|
280
280
|
* and the Artube bridge fill it the same way, so the resumed-bet path below is shared. */
|
|
281
281
|
session?: { betAmount?: number };
|
|
282
282
|
/** Session currency CODE. The Artube bridge's only currency surface (the platform picks it
|
|
283
|
-
* per session; demo
|
|
283
|
+
* per session; a demo session is labelled 'DEMO'); Stake sends full meta on
|
|
284
|
+
* `config.currency` instead. */
|
|
284
285
|
currency?: string;
|
|
285
286
|
lang?: string;
|
|
286
287
|
/** The client the platform launched us on. Both bridges read it off the launch URL
|
|
@@ -307,9 +308,10 @@ export async function createSlotGame<T extends SlotSpinResultBase = SlotSpinResu
|
|
|
307
308
|
// is absent and we only have the spec's currency CODE — resolve it through the SAME table
|
|
308
309
|
// (stake-bridge's lookupCurrency) so e.g. 'EUR' renders as '€', not the literal text "EUR".
|
|
309
310
|
// stake-bridge ships with every scaffold; if it's somehow absent we degrade to the code.
|
|
310
|
-
// On Artube the session currency is the PLATFORM's (per player, and '
|
|
311
|
-
// arrives as a bare code on initData — there
|
|
312
|
-
//
|
|
311
|
+
// On Artube the session currency is the PLATFORM's (per player, and the word 'DEMO' for a demo
|
|
312
|
+
// session — see artube-bridge's displayCurrency) and arrives as a bare code on initData — there
|
|
313
|
+
// is no meta object. It outranks the spec's static code, which would otherwise show every
|
|
314
|
+
// Artube player the spec's currency symbol.
|
|
313
315
|
const currencyCode = (isArtubeNow ? initData?.currency : undefined) || opts.model.spec.currency;
|
|
314
316
|
let currencyMeta = config?.currency;
|
|
315
317
|
if (!currencyMeta?.symbol && currencyCode) {
|
package/src/host/shellConfig.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// `socialize` / `createI18n` / `Lang` are renderer-agnostic helpers from the shell core
|
|
3
3
|
// (@energy8platform/shell). Renderer-agnostic types (GameInfoSection/Content, PaytableRow,
|
|
4
4
|
// GameMode) and the pixi-specific surface types come from the @energy8platform/shell/pixi entry.
|
|
5
|
-
import { socialize, createI18n } from '@energy8platform/shell';
|
|
5
|
+
import { socialize, createI18n, DISCLAIMER_LINES } from '@energy8platform/shell';
|
|
6
6
|
import type { Lang } from '@energy8platform/shell';
|
|
7
7
|
import type { GameInfoContent, GameInfoSection, PaytableRow, GameMode } from '@energy8platform/shell/pixi';
|
|
8
8
|
import type {
|
|
@@ -60,8 +60,11 @@ export interface ShellRuntime {
|
|
|
60
60
|
mode: ShellMode;
|
|
61
61
|
/** Social-casino mode from initData (`config.socialMode`); swaps shell vocabulary. */
|
|
62
62
|
social?: boolean;
|
|
63
|
-
/**
|
|
64
|
-
*
|
|
63
|
+
/** Disclaimer lines the PLATFORM supplied (`initData.config.disclaimerLines` — in practice the
|
|
64
|
+
* Stake bridge, which appends its own "TM and © {year} Stake Engine." to the mandated body).
|
|
65
|
+
* They outrank the default. When absent — Artube, dev, any other host — the shell's canonical
|
|
66
|
+
* `DISCLAIMER_LINES` are used: the same mandated wording, minus a mark that isn't theirs. The
|
|
67
|
+
* disclaimer is required legal copy, so there is no case where the section is simply missing. */
|
|
65
68
|
disclaimerLines?: string[];
|
|
66
69
|
/** Jurisdiction flags from initData (`config.jurisdiction`). Restrict shell features — applied
|
|
67
70
|
* OVER the author's features so a jurisdiction restriction always wins. */
|
|
@@ -268,7 +271,7 @@ function isBrandLine(line: string): boolean {
|
|
|
268
271
|
* caller passes a translation-only resolver (never socializing) — mandated legal copy must not be
|
|
269
272
|
* word-swapped even in social mode. */
|
|
270
273
|
function disclaimerSection(
|
|
271
|
-
lines: string[] | undefined,
|
|
274
|
+
lines: readonly string[] | undefined,
|
|
272
275
|
t: (s: string) => string = (s) => s,
|
|
273
276
|
): GameInfoSection | null {
|
|
274
277
|
const clean = (lines ?? []).map((l) => l.trim()).filter(Boolean);
|
|
@@ -312,7 +315,7 @@ export function defaultGameInfo(
|
|
|
312
315
|
const modes = modesSection(model, t);
|
|
313
316
|
if (modes) sections.push(modes);
|
|
314
317
|
sections.push({ type: 'controls' });
|
|
315
|
-
const disclaimer = disclaimerSection(runtime.disclaimerLines, tDisclaimer);
|
|
318
|
+
const disclaimer = disclaimerSection(runtime.disclaimerLines ?? DISCLAIMER_LINES, tDisclaimer);
|
|
316
319
|
if (disclaimer) sections.push(disclaimer);
|
|
317
320
|
return { sections };
|
|
318
321
|
}
|
|
@@ -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 => ({
|
|
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 +
|
|
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)
|
|
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
|
|