@energy8platform/platform-core 0.24.5 → 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 +67 -3
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +52 -1
- package/dist/index.esm.js +67 -3
- 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 +45 -3
- package/dist/shell.cjs.js.map +1 -1
- package/dist/shell.d.ts +24 -1
- package/dist/shell.esm.js +45 -4
- 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 +24 -2
- package/src/shell/components/BottomBar.ts +3 -2
- 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/state.ts +1 -0
- package/src/shell/types.ts +11 -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;
|
|
@@ -722,6 +750,10 @@ interface ShellConfig {
|
|
|
722
750
|
balance: number;
|
|
723
751
|
win: number;
|
|
724
752
|
mode: ShellMode;
|
|
753
|
+
/** Mark this shell as a read-only historical-round replay. A replay never shows the player's
|
|
754
|
+
* balance (there's no live wallet), even while its free-spins phase runs in `freeSpins` mode.
|
|
755
|
+
* Defaults to `mode === 'replay'`; set explicitly when a replay starts in another mode. */
|
|
756
|
+
replay?: boolean;
|
|
725
757
|
features: ShellFeatures;
|
|
726
758
|
/** Override the BUY BONUS bar button's action: when set, tapping it calls this instead of
|
|
727
759
|
* opening the built-in buy-bonus overlay (e.g. the game shows its own bonus UI). The button
|
|
@@ -730,6 +762,10 @@ interface ShellConfig {
|
|
|
730
762
|
}
|
|
731
763
|
interface ShellState {
|
|
732
764
|
mode: ShellMode;
|
|
765
|
+
/** Sticky replay marker — true for a historical-round replay, regardless of the current
|
|
766
|
+
* `mode`. Set once (from config or when `mode` becomes 'replay') and never cleared, since a
|
|
767
|
+
* shell instance is either a live game or a replay viewer for its whole lifetime. */
|
|
768
|
+
replay: boolean;
|
|
733
769
|
balance: number;
|
|
734
770
|
win: number;
|
|
735
771
|
bet: number;
|
|
@@ -795,6 +831,9 @@ declare class GameShell extends EventEmitter<ShellEvents> {
|
|
|
795
831
|
* false, while a spin is running, while autoplay is active, outside base mode, when an
|
|
796
832
|
* overlay/modal is open, or when an editable element is focused. `repeat` (held key) is
|
|
797
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;
|
|
798
837
|
private handleKeyDown;
|
|
799
838
|
setLayout(layout: 'wide' | 'mobile'): void;
|
|
800
839
|
/** Resolve a built-in shell string. English is the source; with `isSocial` it is run through
|
|
@@ -813,6 +852,9 @@ declare class GameShell extends EventEmitter<ShellEvents> {
|
|
|
813
852
|
setBusy(busy: boolean): void;
|
|
814
853
|
setAutoplay(a: AutoplayOptions): void;
|
|
815
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;
|
|
816
858
|
setBuyBonusEnabled(enabled: boolean): void;
|
|
817
859
|
setFreeSpins(fs: FreeSpinsState): void;
|
|
818
860
|
private showModal;
|
|
@@ -837,6 +879,9 @@ declare class GameShell extends EventEmitter<ShellEvents> {
|
|
|
837
879
|
/** Open a generic, externally-driven modal (title + body + optional action buttons).
|
|
838
880
|
* Each action runs its `on` then closes; the ✕ shows when `availableClose` is true. */
|
|
839
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;
|
|
840
885
|
/** Open the non-dismissable replay summary modal (START REPLAY → onReplay → reopen). */
|
|
841
886
|
openReplay(opts: ReplayModalOptions): void;
|
|
842
887
|
/** Bet picker — list of available bets with an accent Confirm. */
|
|
@@ -889,6 +934,12 @@ interface NativeSimulationConfig {
|
|
|
889
934
|
rng?: NativeRNGKind;
|
|
890
935
|
/** Replay mode: requires `rng: 'provably-fair'` (or default). */
|
|
891
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;
|
|
892
943
|
/** Progress callback */
|
|
893
944
|
onProgress?: (completed: number, total: number) => void;
|
|
894
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
|
}
|
|
@@ -953,6 +976,7 @@ function removeCSSPreloader(_container) {
|
|
|
953
976
|
function createInitialState(config) {
|
|
954
977
|
return {
|
|
955
978
|
mode: config.mode,
|
|
979
|
+
replay: config.replay ?? config.mode === 'replay',
|
|
956
980
|
balance: config.balance,
|
|
957
981
|
win: config.win,
|
|
958
982
|
bet: config.currentBet ?? config.defaultBet,
|
|
@@ -1215,6 +1239,8 @@ const SHELL_CSS = SHELL_FONT_CSS + `
|
|
|
1215
1239
|
#${SHELL_ROOT_ID} .ge-gi-sec h3 { color:var(--shell-plaque-label); font-size:11px; letter-spacing:.14em;
|
|
1216
1240
|
text-transform:uppercase; margin:0 0 12px; }
|
|
1217
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; }
|
|
1218
1244
|
|
|
1219
1245
|
/* controls — two blocks (gameplay / menu & info), icon/name/description per control */
|
|
1220
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); }
|
|
@@ -1652,8 +1678,9 @@ function renderBottomBar(shell) {
|
|
|
1652
1678
|
// FS always shows the spins counter + accumulated Total Win (even €0); a replay shows them
|
|
1653
1679
|
// only when it's a free-spins replay (freeSpins.total > 0).
|
|
1654
1680
|
const showFsBlocks = isFS || (state.mode === 'replay' && state.freeSpins.total > 0);
|
|
1655
|
-
// Replay is a read-only historical round — there's no real balance to show, so hide it.
|
|
1656
|
-
|
|
1681
|
+
// Replay is a read-only historical round — there's no real balance to show, so hide it. Keyed on
|
|
1682
|
+
// the sticky `replay` flag (not `mode`) so it stays hidden through a replay's free-spins phase.
|
|
1683
|
+
const balance = state.replay
|
|
1657
1684
|
? null
|
|
1658
1685
|
: readout('balance', shell.t('Balance'), fmt(state.balance));
|
|
1659
1686
|
// With a feature active (e.g. Ante) the BET readout shows the effective stake, tinted with
|
|
@@ -1923,6 +1950,10 @@ function openSettingsModal(shell) {
|
|
|
1923
1950
|
return root;
|
|
1924
1951
|
}
|
|
1925
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
|
+
|
|
1926
1957
|
const SVG_NS = 'http://www.w3.org/2000/svg';
|
|
1927
1958
|
function openGameInfoModal(shell) {
|
|
1928
1959
|
const { root, body } = createOverlay({
|
|
@@ -1939,8 +1970,19 @@ function openGameInfoModal(shell) {
|
|
|
1939
1970
|
.map((s, i) => ({ s, i, k: base(s, i) }))
|
|
1940
1971
|
.sort((a, b) => a.k - b.k || a.i - b.i)
|
|
1941
1972
|
.forEach(({ s }) => body.appendChild(renderSection(shell, s)));
|
|
1973
|
+
body.appendChild(versionFooter(shell));
|
|
1942
1974
|
return root;
|
|
1943
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
|
+
}
|
|
1944
1986
|
function renderSection(shell, s) {
|
|
1945
1987
|
switch (s.type) {
|
|
1946
1988
|
case 'modes': return sectionModes(shell, s.modes, sec('info-modes', s.title, shell.t('Modes')));
|
|
@@ -2734,6 +2776,10 @@ class GameShell extends EventEmitter {
|
|
|
2734
2776
|
this.observeLayout();
|
|
2735
2777
|
if (typeof document !== 'undefined') {
|
|
2736
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);
|
|
2737
2783
|
this.keysBound = true;
|
|
2738
2784
|
}
|
|
2739
2785
|
this.render();
|
|
@@ -2819,6 +2865,12 @@ class GameShell extends EventEmitter {
|
|
|
2819
2865
|
* false, while a spin is running, while autoplay is active, outside base mode, when an
|
|
2820
2866
|
* overlay/modal is open, or when an editable element is focused. `repeat` (held key) is
|
|
2821
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 */ } };
|
|
2822
2874
|
handleKeyDown = (e) => {
|
|
2823
2875
|
if (this.destroyed || e.code !== 'Space' || e.repeat)
|
|
2824
2876
|
return;
|
|
@@ -2883,10 +2935,18 @@ class GameShell extends EventEmitter {
|
|
|
2883
2935
|
setBalance(n) { this.state.balance = n; this.render(); }
|
|
2884
2936
|
setWin(n) { this.state.win = n; this.render(); }
|
|
2885
2937
|
setBet(n) { this.state.bet = n; this.render(); }
|
|
2886
|
-
setMode(mode) {
|
|
2938
|
+
setMode(mode) {
|
|
2939
|
+
if (mode === 'replay')
|
|
2940
|
+
this.state.replay = true; // sticky: a replay stays a replay across modes
|
|
2941
|
+
this.state.mode = mode;
|
|
2942
|
+
this.render();
|
|
2943
|
+
}
|
|
2887
2944
|
setBusy(busy) { this.state.busy = busy; this.render(); }
|
|
2888
2945
|
setAutoplay(a) { this.state.autoplay = a; this.render(); }
|
|
2889
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); }
|
|
2890
2950
|
setBuyBonusEnabled(enabled) { this.state.buyBonusEnabled = enabled; this.render(); }
|
|
2891
2951
|
setFreeSpins(fs) { this.state.freeSpins = fs; this.render(); }
|
|
2892
2952
|
showModal(el) {
|
|
@@ -2957,6 +3017,9 @@ class GameShell extends EventEmitter {
|
|
|
2957
3017
|
/** Open a generic, externally-driven modal (title + body + optional action buttons).
|
|
2958
3018
|
* Each action runs its `on` then closes; the ✕ shows when `availableClose` is true. */
|
|
2959
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 = ''; }
|
|
2960
3023
|
/** Open the non-dismissable replay summary modal (START REPLAY → onReplay → reopen). */
|
|
2961
3024
|
openReplay(opts) {
|
|
2962
3025
|
if (this.destroyed)
|
|
@@ -2975,6 +3038,7 @@ class GameShell extends EventEmitter {
|
|
|
2975
3038
|
this.ro = null;
|
|
2976
3039
|
if (this.keysBound) {
|
|
2977
3040
|
document.removeEventListener('keydown', this.handleKeyDown);
|
|
3041
|
+
document.removeEventListener('pointerdown', this.pullFocus, true);
|
|
2978
3042
|
this.keysBound = false;
|
|
2979
3043
|
}
|
|
2980
3044
|
this.cancelMoneyAnims();
|