@energy8platform/platform-core 0.24.6 → 0.25.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/game-spec.cjs.js +209 -0
- package/dist/game-spec.cjs.js.map +1 -0
- package/dist/game-spec.d.ts +164 -0
- package/dist/game-spec.esm.js +198 -0
- package/dist/game-spec.esm.js.map +1 -0
- package/dist/index.cjs.js +57 -0
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +44 -1
- package/dist/index.esm.js +57 -0
- package/dist/index.esm.js.map +1 -1
- package/dist/lua.cjs.js +5 -2
- package/dist/lua.cjs.js.map +1 -1
- package/dist/lua.d.ts +9 -0
- package/dist/lua.esm.js +5 -2
- package/dist/lua.esm.js.map +1 -1
- package/dist/shell.cjs.js +35 -0
- package/dist/shell.cjs.js.map +1 -1
- package/dist/shell.d.ts +16 -1
- package/dist/shell.esm.js +35 -1
- package/dist/shell.esm.js.map +1 -1
- package/dist/simulation.cjs.js +40 -22
- package/dist/simulation.cjs.js.map +1 -1
- package/dist/simulation.d.ts +35 -2
- package/dist/simulation.esm.js +39 -23
- package/dist/simulation.esm.js.map +1 -1
- package/dist/slot-result.cjs.js +17 -0
- package/dist/slot-result.cjs.js.map +1 -0
- package/dist/slot-result.d.ts +26 -0
- package/dist/slot-result.esm.js +14 -0
- package/dist/slot-result.esm.js.map +1 -0
- package/package.json +12 -1
- package/scripts/gen-version.mjs +21 -0
- package/src/PlatformSession.ts +28 -0
- package/src/game-spec/defineGame.ts +16 -0
- package/src/game-spec/derive.ts +135 -0
- package/src/game-spec/export.ts +17 -0
- package/src/game-spec/index.ts +6 -0
- package/src/game-spec/types.ts +81 -0
- package/src/game-spec/validate.ts +49 -0
- package/src/lua/LuaEngine.ts +5 -2
- package/src/lua/types.ts +8 -0
- package/src/shell/GameShell.ts +19 -1
- package/src/shell/components/GameInfo.ts +13 -0
- package/src/shell/index.ts +1 -0
- package/src/shell/shell.css.ts +2 -0
- package/src/shell/types.ts +3 -0
- package/src/shell/version.ts +3 -0
- package/src/simulation/NativeSimulationRunner.ts +62 -26
- package/src/simulation/index.ts +3 -0
- package/src/slot-result/coerce.ts +11 -0
- package/src/slot-result/index.ts +2 -0
- package/src/slot-result/types.ts +19 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SessionData, GameConfigData, PlayParams, PlayResultData, BalanceData, CasinoGameSDK, InitData } from '@energy8platform/game-sdk';
|
|
1
|
+
import { SessionData, GameConfigData, PlayParams, PlayResultData, BalanceData, ConnectionStatePayload, CasinoGameSDK, InitData } from '@energy8platform/game-sdk';
|
|
2
2
|
export { AnywhereWinData, BalanceData, GameConfigData, InitData, PaylineData, PlayParams, PlayResultData, SessionData, SymbolData, WinLineData } from '@energy8platform/game-sdk';
|
|
3
3
|
|
|
4
4
|
interface GameDefinition {
|
|
@@ -76,6 +76,14 @@ interface LuaEngineConfig {
|
|
|
76
76
|
logger?: (level: string, msg: string) => void;
|
|
77
77
|
/** Skip marshalling data fields (matrix, wins, etc.) for faster simulation */
|
|
78
78
|
simulationMode?: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Allow `requires_session` actions (e.g. `free_spin`) to run even with no active session.
|
|
81
|
+
* Default false (server-faithful). The dev harness sets this true: when a bonus is bought
|
|
82
|
+
* through the BOOKS path the LuaEngine never created a session, yet the scaffold then replays
|
|
83
|
+
* `free_spin` (which has no books) via the Lua fallback — without this it would throw
|
|
84
|
+
* "Action free_spin requires an active session".
|
|
85
|
+
*/
|
|
86
|
+
allowSessionlessActions?: boolean;
|
|
79
87
|
}
|
|
80
88
|
interface LuaPlayResult {
|
|
81
89
|
totalWin: number;
|
|
@@ -354,6 +362,9 @@ interface PlatformSessionEvents {
|
|
|
354
362
|
balanceUpdate: BalanceData;
|
|
355
363
|
/** SDK or transport error */
|
|
356
364
|
error: Error;
|
|
365
|
+
/** Host link state changed (forwarded from the SDK): 'connecting' | 'lost' | 'restored'.
|
|
366
|
+
* The host renders a reconnect overlay on lost/connecting and dismisses it on restored. */
|
|
367
|
+
connectionStateChanged: ConnectionStatePayload;
|
|
357
368
|
}
|
|
358
369
|
/**
|
|
359
370
|
* Lifecycle wrapper around CasinoGameSDK + (optional) DevBridge.
|
|
@@ -401,6 +412,20 @@ declare class PlatformSession extends EventEmitter<PlatformSessionEvents> {
|
|
|
401
412
|
* Throws if the session was constructed with `sdk: false`.
|
|
402
413
|
*/
|
|
403
414
|
play(params: PlayParams): Promise<PlayResultData>;
|
|
415
|
+
/**
|
|
416
|
+
* Acknowledge a finished PLAY_RESULT (call AFTER the game has animated it).
|
|
417
|
+
*
|
|
418
|
+
* The host uses this to know the client is ready for the next action and, on
|
|
419
|
+
* Stake, to settle the round (`/wallet/end-round`) only once the win
|
|
420
|
+
* animation has played. No-op when constructed with `sdk: false`.
|
|
421
|
+
*/
|
|
422
|
+
playAck(result: PlayResultData): void;
|
|
423
|
+
/**
|
|
424
|
+
* Query the host for an in-flight round (e.g. after a page reload). Resolves with the last
|
|
425
|
+
* result snapshot when a round is still open, or `null`. Used to offer a "resume / finish"
|
|
426
|
+
* choice on boot. Resolves `null` when constructed with `sdk: false`.
|
|
427
|
+
*/
|
|
428
|
+
getState(): Promise<PlayResultData | null>;
|
|
404
429
|
/** Tear down the SDK, DevBridge, and clear listeners. */
|
|
405
430
|
destroy(): void;
|
|
406
431
|
}
|
|
@@ -712,6 +737,9 @@ interface ShellConfig {
|
|
|
712
737
|
theme?: ThemeConfig;
|
|
713
738
|
gameInfo: GameInfoContent;
|
|
714
739
|
language: string;
|
|
740
|
+
/** Game version shown in the game-info footer (e.g. '1.2.0'). Defaults to '1.0.0'. The footer
|
|
741
|
+
* stamp is `${version}.${engineVersionWithoutDots}` — e.g. game 1.0.0 on engine 0.24.6 → '1.0.0.0246'. */
|
|
742
|
+
version?: string;
|
|
715
743
|
/** When true, all built-in shell text is shown in the social-casino vocabulary (derived from
|
|
716
744
|
* English via word-swap rules), regardless of `language`. Game-supplied content is untouched. */
|
|
717
745
|
isSocial?: boolean;
|
|
@@ -803,6 +831,9 @@ declare class GameShell extends EventEmitter<ShellEvents> {
|
|
|
803
831
|
* false, while a spin is running, while autoplay is active, outside base mode, when an
|
|
804
832
|
* overlay/modal is open, or when an editable element is focused. `repeat` (held key) is
|
|
805
833
|
* ignored so it can't spam. */
|
|
834
|
+
/** Pull window focus into the iframe on first pointer interaction so `document` keydown (the
|
|
835
|
+
* spacebar shortcut) fires. No-op / harmless when already focused or full-page. */
|
|
836
|
+
private pullFocus;
|
|
806
837
|
private handleKeyDown;
|
|
807
838
|
setLayout(layout: 'wide' | 'mobile'): void;
|
|
808
839
|
/** Resolve a built-in shell string. English is the source; with `isSocial` it is run through
|
|
@@ -821,6 +852,9 @@ declare class GameShell extends EventEmitter<ShellEvents> {
|
|
|
821
852
|
setBusy(busy: boolean): void;
|
|
822
853
|
setAutoplay(a: AutoplayOptions): void;
|
|
823
854
|
setTurbo(level: number): void;
|
|
855
|
+
/** Currency-aware money formatter for WIN amounts (variable decimals: 0.0041 stays 0.0041, not
|
|
856
|
+
* 0.00). The host hands this to a scene so games format money without knowing the currency. */
|
|
857
|
+
formatWin(value: number): string;
|
|
824
858
|
setBuyBonusEnabled(enabled: boolean): void;
|
|
825
859
|
setFreeSpins(fs: FreeSpinsState): void;
|
|
826
860
|
private showModal;
|
|
@@ -845,6 +879,9 @@ declare class GameShell extends EventEmitter<ShellEvents> {
|
|
|
845
879
|
/** Open a generic, externally-driven modal (title + body + optional action buttons).
|
|
846
880
|
* Each action runs its `on` then closes; the ✕ shows when `availableClose` is true. */
|
|
847
881
|
openModal(opts: ModalOptions): void;
|
|
882
|
+
/** Programmatically dismiss whatever modal/overlay is currently shown (e.g. auto-close the
|
|
883
|
+
* reconnect overlay once the link is restored). No-op when nothing is open. */
|
|
884
|
+
closeModal(): void;
|
|
848
885
|
/** Open the non-dismissable replay summary modal (START REPLAY → onReplay → reopen). */
|
|
849
886
|
openReplay(opts: ReplayModalOptions): void;
|
|
850
887
|
/** Bet picker — list of available bets with an accent Confirm. */
|
|
@@ -897,6 +934,12 @@ interface NativeSimulationConfig {
|
|
|
897
934
|
rng?: NativeRNGKind;
|
|
898
935
|
/** Replay mode: requires `rng: 'provably-fair'` (or default). */
|
|
899
936
|
replay?: NativeReplayParams;
|
|
937
|
+
/**
|
|
938
|
+
* Path to write per-round JSONL book dump. When set, the binary writes one
|
|
939
|
+
* JSON object per line (one per round) to this file, enabling post-run
|
|
940
|
+
* analysis of the full round log.
|
|
941
|
+
*/
|
|
942
|
+
dump?: string;
|
|
900
943
|
/** Progress callback */
|
|
901
944
|
onProgress?: (completed: number, total: number) => void;
|
|
902
945
|
}
|
package/dist/index.esm.js
CHANGED
|
@@ -608,6 +608,26 @@ class PlatformSession extends EventEmitter {
|
|
|
608
608
|
}
|
|
609
609
|
return this.sdk.play(params);
|
|
610
610
|
}
|
|
611
|
+
/**
|
|
612
|
+
* Acknowledge a finished PLAY_RESULT (call AFTER the game has animated it).
|
|
613
|
+
*
|
|
614
|
+
* The host uses this to know the client is ready for the next action and, on
|
|
615
|
+
* Stake, to settle the round (`/wallet/end-round`) only once the win
|
|
616
|
+
* animation has played. No-op when constructed with `sdk: false`.
|
|
617
|
+
*/
|
|
618
|
+
playAck(result) {
|
|
619
|
+
this.sdk?.playAck(result);
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Query the host for an in-flight round (e.g. after a page reload). Resolves with the last
|
|
623
|
+
* result snapshot when a round is still open, or `null`. Used to offer a "resume / finish"
|
|
624
|
+
* choice on boot. Resolves `null` when constructed with `sdk: false`.
|
|
625
|
+
*/
|
|
626
|
+
async getState() {
|
|
627
|
+
if (!this.sdk)
|
|
628
|
+
return null;
|
|
629
|
+
return this.sdk.getState();
|
|
630
|
+
}
|
|
611
631
|
/** Tear down the SDK, DevBridge, and clear listeners. */
|
|
612
632
|
destroy() {
|
|
613
633
|
this.sdk?.destroy();
|
|
@@ -647,6 +667,9 @@ async function createPlatformSession(config = {}) {
|
|
|
647
667
|
sdk.on('balanceUpdate', (data) => {
|
|
648
668
|
session.emit('balanceUpdate', data);
|
|
649
669
|
});
|
|
670
|
+
sdk.on('connectionStateChanged', (state) => {
|
|
671
|
+
session.emit('connectionStateChanged', state);
|
|
672
|
+
});
|
|
650
673
|
}
|
|
651
674
|
return session;
|
|
652
675
|
}
|
|
@@ -1216,6 +1239,8 @@ const SHELL_CSS = SHELL_FONT_CSS + `
|
|
|
1216
1239
|
#${SHELL_ROOT_ID} .ge-gi-sec h3 { color:var(--shell-plaque-label); font-size:11px; letter-spacing:.14em;
|
|
1217
1240
|
text-transform:uppercase; margin:0 0 12px; }
|
|
1218
1241
|
#${SHELL_ROOT_ID} .ge-gi-sec p { color:rgba(255,255,255,.88); font-size:15px; line-height:1.6; margin:0; }
|
|
1242
|
+
#${SHELL_ROOT_ID} .ge-gi-version { text-align:center; color:var(--shell-muted); font-size:11px;
|
|
1243
|
+
letter-spacing:.08em; opacity:.7; margin:4px 0 2px; }
|
|
1219
1244
|
|
|
1220
1245
|
/* controls — two blocks (gameplay / menu & info), icon/name/description per control */
|
|
1221
1246
|
#${SHELL_ROOT_ID} .ge-gi-ctl-block + .ge-gi-ctl-block { margin-top:16px; padding-top:4px; border-top:1px solid var(--shell-plaque-line); }
|
|
@@ -1925,6 +1950,10 @@ function openSettingsModal(shell) {
|
|
|
1925
1950
|
return root;
|
|
1926
1951
|
}
|
|
1927
1952
|
|
|
1953
|
+
// AUTO-GENERATED by scripts/gen-version.mjs — do not edit. Mirrors package.json "version".
|
|
1954
|
+
/** The @energy8platform/platform-core package version, stamped at build time. */
|
|
1955
|
+
const PACKAGE_VERSION = '0.25.0';
|
|
1956
|
+
|
|
1928
1957
|
const SVG_NS = 'http://www.w3.org/2000/svg';
|
|
1929
1958
|
function openGameInfoModal(shell) {
|
|
1930
1959
|
const { root, body } = createOverlay({
|
|
@@ -1941,8 +1970,19 @@ function openGameInfoModal(shell) {
|
|
|
1941
1970
|
.map((s, i) => ({ s, i, k: base(s, i) }))
|
|
1942
1971
|
.sort((a, b) => a.k - b.k || a.i - b.i)
|
|
1943
1972
|
.forEach(({ s }) => body.appendChild(renderSection(shell, s)));
|
|
1973
|
+
body.appendChild(versionFooter(shell));
|
|
1944
1974
|
return root;
|
|
1945
1975
|
}
|
|
1976
|
+
/** A muted version stamp pinned to the bottom of the game-info modal:
|
|
1977
|
+
* `${config.version ?? '1.0.0'}.${engine version without dots}` (e.g. '1.0.0.0246'). */
|
|
1978
|
+
function versionFooter(shell) {
|
|
1979
|
+
const gameVersion = shell.config.version ?? '1.0.0';
|
|
1980
|
+
const el = document.createElement('div');
|
|
1981
|
+
el.dataset.ge = 'info-version';
|
|
1982
|
+
el.className = 'ge-gi-version';
|
|
1983
|
+
el.textContent = `${gameVersion}.${PACKAGE_VERSION.replaceAll('.', '')}`;
|
|
1984
|
+
return el;
|
|
1985
|
+
}
|
|
1946
1986
|
function renderSection(shell, s) {
|
|
1947
1987
|
switch (s.type) {
|
|
1948
1988
|
case 'modes': return sectionModes(shell, s.modes, sec('info-modes', s.title, shell.t('Modes')));
|
|
@@ -2736,6 +2776,10 @@ class GameShell extends EventEmitter {
|
|
|
2736
2776
|
this.observeLayout();
|
|
2737
2777
|
if (typeof document !== 'undefined') {
|
|
2738
2778
|
document.addEventListener('keydown', this.handleKeyDown);
|
|
2779
|
+
// Stake serves the game in an iframe; on first paint focus is on the HOST page, so a `document`
|
|
2780
|
+
// keydown never fires and Space scrolls the parent. Pull window focus into the iframe on the
|
|
2781
|
+
// first pointer interaction so the spacebar shortcut works. Harmless on full-page Energy8.
|
|
2782
|
+
document.addEventListener('pointerdown', this.pullFocus, true);
|
|
2739
2783
|
this.keysBound = true;
|
|
2740
2784
|
}
|
|
2741
2785
|
this.render();
|
|
@@ -2821,6 +2865,12 @@ class GameShell extends EventEmitter {
|
|
|
2821
2865
|
* false, while a spin is running, while autoplay is active, outside base mode, when an
|
|
2822
2866
|
* overlay/modal is open, or when an editable element is focused. `repeat` (held key) is
|
|
2823
2867
|
* ignored so it can't spam. */
|
|
2868
|
+
/** Pull window focus into the iframe on first pointer interaction so `document` keydown (the
|
|
2869
|
+
* spacebar shortcut) fires. No-op / harmless when already focused or full-page. */
|
|
2870
|
+
pullFocus = () => { try {
|
|
2871
|
+
window.focus();
|
|
2872
|
+
}
|
|
2873
|
+
catch { /* cross-origin / non-browser */ } };
|
|
2824
2874
|
handleKeyDown = (e) => {
|
|
2825
2875
|
if (this.destroyed || e.code !== 'Space' || e.repeat)
|
|
2826
2876
|
return;
|
|
@@ -2894,6 +2944,9 @@ class GameShell extends EventEmitter {
|
|
|
2894
2944
|
setBusy(busy) { this.state.busy = busy; this.render(); }
|
|
2895
2945
|
setAutoplay(a) { this.state.autoplay = a; this.render(); }
|
|
2896
2946
|
setTurbo(level) { this.state.turbo = level; this.render(); }
|
|
2947
|
+
/** Currency-aware money formatter for WIN amounts (variable decimals: 0.0041 stays 0.0041, not
|
|
2948
|
+
* 0.00). The host hands this to a scene so games format money without knowing the currency. */
|
|
2949
|
+
formatWin(value) { return formatCurrency(value, this.config.currency, true); }
|
|
2897
2950
|
setBuyBonusEnabled(enabled) { this.state.buyBonusEnabled = enabled; this.render(); }
|
|
2898
2951
|
setFreeSpins(fs) { this.state.freeSpins = fs; this.render(); }
|
|
2899
2952
|
showModal(el) {
|
|
@@ -2964,6 +3017,9 @@ class GameShell extends EventEmitter {
|
|
|
2964
3017
|
/** Open a generic, externally-driven modal (title + body + optional action buttons).
|
|
2965
3018
|
* Each action runs its `on` then closes; the ✕ shows when `availableClose` is true. */
|
|
2966
3019
|
openModal(opts) { this.showModal(buildModal(opts)); }
|
|
3020
|
+
/** Programmatically dismiss whatever modal/overlay is currently shown (e.g. auto-close the
|
|
3021
|
+
* reconnect overlay once the link is restored). No-op when nothing is open. */
|
|
3022
|
+
closeModal() { this.modalHost.innerHTML = ''; }
|
|
2967
3023
|
/** Open the non-dismissable replay summary modal (START REPLAY → onReplay → reopen). */
|
|
2968
3024
|
openReplay(opts) {
|
|
2969
3025
|
if (this.destroyed)
|
|
@@ -2982,6 +3038,7 @@ class GameShell extends EventEmitter {
|
|
|
2982
3038
|
this.ro = null;
|
|
2983
3039
|
if (this.keysBound) {
|
|
2984
3040
|
document.removeEventListener('keydown', this.handleKeyDown);
|
|
3041
|
+
document.removeEventListener('pointerdown', this.pullFocus, true);
|
|
2985
3042
|
this.keysBound = false;
|
|
2986
3043
|
}
|
|
2987
3044
|
this.cancelMoneyAnims();
|