@energy8platform/game-engine 0.17.0 → 0.19.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.
Files changed (80) hide show
  1. package/dist/audio.cjs.js +15 -5
  2. package/dist/audio.cjs.js.map +1 -1
  3. package/dist/audio.d.ts +5 -0
  4. package/dist/audio.esm.js +15 -5
  5. package/dist/audio.esm.js.map +1 -1
  6. package/dist/core.cjs.js +108 -19
  7. package/dist/core.cjs.js.map +1 -1
  8. package/dist/core.d.ts +46 -3
  9. package/dist/core.esm.js +109 -21
  10. package/dist/core.esm.js.map +1 -1
  11. package/dist/game-spec.cjs.js +13 -0
  12. package/dist/game-spec.cjs.js.map +1 -0
  13. package/dist/game-spec.d.ts +1 -0
  14. package/dist/game-spec.esm.js +2 -0
  15. package/dist/game-spec.esm.js.map +1 -0
  16. package/dist/host.cjs.js +3612 -0
  17. package/dist/host.cjs.js.map +1 -0
  18. package/dist/host.d.ts +1000 -0
  19. package/dist/host.esm.js +3603 -0
  20. package/dist/host.esm.js.map +1 -0
  21. package/dist/index.cjs.js +59 -19
  22. package/dist/index.cjs.js.map +1 -1
  23. package/dist/index.d.ts +19 -2
  24. package/dist/index.esm.js +59 -19
  25. package/dist/index.esm.js.map +1 -1
  26. package/dist/react.cjs.js.map +1 -1
  27. package/dist/react.d.ts +19 -2
  28. package/dist/react.esm.js.map +1 -1
  29. package/dist/shell.cjs.js +19 -0
  30. package/dist/shell.cjs.js.map +1 -0
  31. package/dist/shell.d.ts +1 -0
  32. package/dist/shell.esm.js +2 -0
  33. package/dist/shell.esm.js.map +1 -0
  34. package/dist/slot.cjs.js +998 -0
  35. package/dist/slot.cjs.js.map +1 -0
  36. package/dist/slot.d.ts +333 -0
  37. package/dist/slot.esm.js +985 -0
  38. package/dist/slot.esm.js.map +1 -0
  39. package/package.json +28 -2
  40. package/src/audio/AudioManager.ts +17 -5
  41. package/src/core/GameApplication.ts +38 -6
  42. package/src/core/index.ts +2 -0
  43. package/src/game-spec/index.ts +1 -0
  44. package/src/host/autoplay.ts +78 -0
  45. package/src/host/balanceGate.ts +46 -0
  46. package/src/host/buildConfig.ts +28 -0
  47. package/src/host/createSlotGame.ts +543 -0
  48. package/src/host/fatalError.ts +104 -0
  49. package/src/host/freeSpinsCounter.ts +44 -0
  50. package/src/host/index.ts +21 -0
  51. package/src/host/overlayController.ts +81 -0
  52. package/src/host/pauseController.ts +21 -0
  53. package/src/host/playError.ts +64 -0
  54. package/src/host/preboot.ts +25 -0
  55. package/src/host/replay.ts +9 -0
  56. package/src/host/runRound.ts +55 -0
  57. package/src/host/sceneAudio.ts +14 -0
  58. package/src/host/sceneController.ts +96 -0
  59. package/src/host/sceneStart.ts +25 -0
  60. package/src/host/shellConfig.ts +384 -0
  61. package/src/host/skipGesture.ts +24 -0
  62. package/src/host/slotPlay.ts +62 -0
  63. package/src/host/types.ts +74 -0
  64. package/src/scenes/IntroScene.ts +66 -0
  65. package/src/shell/index.ts +20 -0
  66. package/src/slot/anim/CascadeController.ts +102 -0
  67. package/src/slot/anim/ReelSpinController.ts +81 -0
  68. package/src/slot/anim/easing-map.ts +14 -0
  69. package/src/slot/freeSpins/FreeSpinsSession.ts +40 -0
  70. package/src/slot/grid/AnimatedSymbol.ts +68 -0
  71. package/src/slot/grid/ReelGrid.ts +92 -0
  72. package/src/slot/grid/SymbolCell.ts +127 -0
  73. package/src/slot/grid/SymbolView.ts +13 -0
  74. package/src/slot/index.ts +21 -0
  75. package/src/slot/multiplier/MultiplierAccumulator.ts +29 -0
  76. package/src/slot/overlay/BigWinOverlay.ts +89 -0
  77. package/src/slot/overlay/CountUpDisplay.ts +56 -0
  78. package/src/slot/overlay/tiers.ts +29 -0
  79. package/src/types.ts +3 -0
  80. package/src/viewport/ViewportManager.ts +19 -9
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import * as _energy8platform_platform_core_shell from '@energy8platform/platform-core/shell';
1
2
  import { Container, ApplicationOptions, Application, Texture, AnimatedSprite, Spritesheet, TextStyle, ColorSource } from 'pixi.js';
2
3
  import { CasinoGameSDK, InitData, GameConfigData, SessionData } from '@energy8platform/game-sdk';
3
4
  export { AnywhereWinData, BalanceData, BridgeDestroyedError, BridgeNotReadyError, GameConfigData, InitData, PaylineData, PlayParams, PlayResultData, SDKError, SessionData, SymbolData, TimeoutError, WinLineData } from '@energy8platform/game-sdk';
@@ -60,6 +61,8 @@ interface GameApplicationConfig {
60
61
  pixi?: Partial<ApplicationOptions>;
61
62
  /** Enable debug overlay (FPS, draw calls) */
62
63
  debug?: boolean;
64
+ /** When set, GameApplication mounts the branded game shell after the SDK handshake. */
65
+ shell?: _energy8platform_platform_core_shell.ShellConfig | false;
63
66
  }
64
67
  interface SceneConstructor {
65
68
  new (): IScene;
@@ -339,6 +342,7 @@ declare class AudioManager {
339
342
  private _persist;
340
343
  private _storageKey;
341
344
  private _categories;
345
+ private _masterGain;
342
346
  private _currentMusic;
343
347
  private _unlocked;
344
348
  private _unlockHandler;
@@ -379,6 +383,10 @@ declare class AudioManager {
379
383
  * Stop all sounds.
380
384
  */
381
385
  stopAll(): void;
386
+ /** Global gain (0..1) folded into every category's effective volume. Driven by the shell's
387
+ * 'master' settingChange. Does not affect the persisted per-category volumes. */
388
+ setMasterVolume(volume: number): void;
389
+ getMasterVolume(): number;
382
390
  /**
383
391
  * Set volume for a category.
384
392
  */
@@ -581,6 +589,7 @@ declare class ViewportManager extends EventEmitter<ViewportEvents> {
581
589
  private _app;
582
590
  private _container;
583
591
  private _config;
592
+ private _target;
584
593
  private _resizeObserver;
585
594
  private _currentOrientation;
586
595
  private _currentWidth;
@@ -588,7 +597,7 @@ declare class ViewportManager extends EventEmitter<ViewportEvents> {
588
597
  private _currentScale;
589
598
  private _destroyed;
590
599
  private _resizeTimeout;
591
- constructor(app: Application, container: HTMLElement, config: ViewportConfig);
600
+ constructor(app: Application, container: HTMLElement, config: ViewportConfig, target?: Container);
592
601
  /** Current canvas width in game units */
593
602
  get width(): number;
594
603
  /** Current canvas height in game units */
@@ -689,6 +698,12 @@ declare class GameApplication extends EventEmitter<GameEngineEvents> {
689
698
  input: InputManager;
690
699
  /** Viewport manager */
691
700
  viewport: ViewportManager;
701
+ /** Scaled world root (holds scenes). Transformed by the ViewportManager to fit the design
702
+ * resolution; lives below the UI layer on app.stage. */
703
+ worldRoot: Container;
704
+ /** Unscaled, screen-space UI layer. Sits above {@link worldRoot} and is NOT touched by the
705
+ * viewport transform — children fill the real screen (e.g. the host's shell + overlay). */
706
+ uiLayer: Container;
692
707
  /** SDK instance (null in offline mode) */
693
708
  sdk: CasinoGameSDK | null;
694
709
  /** FPS overlay instance (only when debug: true) */
@@ -697,6 +712,8 @@ declare class GameApplication extends EventEmitter<GameEngineEvents> {
697
712
  initData: InitData | null;
698
713
  /** Platform session (SDK + optional DevBridge). null until start() runs. */
699
714
  platformSession: PlatformSession | null;
715
+ /** Branded game shell (only when config.shell is set). */
716
+ shell?: _energy8platform_platform_core_shell.GameShell;
700
717
  /** Configuration */
701
718
  readonly config: GameApplicationConfig;
702
719
  private _running;
@@ -723,7 +740,7 @@ declare class GameApplication extends EventEmitter<GameEngineEvents> {
723
740
  /**
724
741
  * Destroy the engine and free all resources.
725
742
  */
726
- destroy(): void;
743
+ destroy(): Promise<void>;
727
744
  private resolveContainer;
728
745
  private initPixi;
729
746
  private initSDK;
package/dist/index.esm.js CHANGED
@@ -734,6 +734,7 @@ class AudioManager {
734
734
  _persist;
735
735
  _storageKey;
736
736
  _categories;
737
+ _masterGain = 1.0;
737
738
  _currentMusic = null;
738
739
  _unlocked = false;
739
740
  _unlockHandler = null;
@@ -793,7 +794,7 @@ class AudioManager {
793
794
  if (this._globalMuted || this._categories[category].muted)
794
795
  return;
795
796
  const { sound } = this._soundModule;
796
- const vol = (options?.volume ?? 1) * this._categories[category].volume;
797
+ const vol = (options?.volume ?? 1) * this._categories[category].volume * this._masterGain;
797
798
  try {
798
799
  sound.play(alias, {
799
800
  volume: vol,
@@ -822,7 +823,7 @@ class AudioManager {
822
823
  if (this._globalMuted || this._categories.music.muted)
823
824
  return;
824
825
  // Fade out the previous track
825
- this.fadeVolume(prevAlias, this._categories.music.volume, 0, fadeDuration, () => {
826
+ this.fadeVolume(prevAlias, this._categories.music.volume * this._masterGain, 0, fadeDuration, () => {
826
827
  try {
827
828
  sound.stop(prevAlias);
828
829
  }
@@ -834,7 +835,7 @@ class AudioManager {
834
835
  volume: 0,
835
836
  loop: true,
836
837
  });
837
- this.fadeVolume(alias, 0, this._categories.music.volume, fadeDuration);
838
+ this.fadeVolume(alias, 0, this._categories.music.volume * this._masterGain, fadeDuration);
838
839
  }
839
840
  catch (e) {
840
841
  console.warn(`[AudioManager] Failed to play music "${alias}":`, e);
@@ -853,7 +854,7 @@ class AudioManager {
853
854
  return;
854
855
  try {
855
856
  sound.play(alias, {
856
- volume: this._categories.music.volume,
857
+ volume: this._categories.music.volume * this._masterGain,
857
858
  loop: true,
858
859
  });
859
860
  }
@@ -887,6 +888,15 @@ class AudioManager {
887
888
  sound.stopAll();
888
889
  this._currentMusic = null;
889
890
  }
891
+ /** Global gain (0..1) folded into every category's effective volume. Driven by the shell's
892
+ * 'master' settingChange. Does not affect the persisted per-category volumes. */
893
+ setMasterVolume(volume) {
894
+ this._masterGain = Math.max(0, Math.min(1, volume));
895
+ this.applyVolumes();
896
+ }
897
+ getMasterVolume() {
898
+ return this._masterGain;
899
+ }
890
900
  /**
891
901
  * Set volume for a category.
892
902
  */
@@ -1032,7 +1042,7 @@ class AudioManager {
1032
1042
  const { sound } = this._soundModule;
1033
1043
  // Global mute is owned by sound.muteAll()/unmuteAll() (context.muted),
1034
1044
  // not by volumeAll — mixing both leaves mute un-undoable after reload.
1035
- sound.volumeAll = 1;
1045
+ sound.volumeAll = this._masterGain; // master multiplies the global bus
1036
1046
  }
1037
1047
  setupMobileUnlock() {
1038
1048
  if (this._unlocked)
@@ -1300,6 +1310,7 @@ class ViewportManager extends EventEmitter {
1300
1310
  _app;
1301
1311
  _container;
1302
1312
  _config;
1313
+ _target;
1303
1314
  _resizeObserver = null;
1304
1315
  _currentOrientation = Orientation.LANDSCAPE;
1305
1316
  _currentWidth = 0;
@@ -1307,11 +1318,15 @@ class ViewportManager extends EventEmitter {
1307
1318
  _currentScale = 1;
1308
1319
  _destroyed = false;
1309
1320
  _resizeTimeout = null;
1310
- constructor(app, container, config) {
1321
+ constructor(app, container, config, target) {
1311
1322
  super();
1312
1323
  this._app = app;
1313
1324
  this._container = container;
1314
1325
  this._config = config;
1326
+ // The container this manager scales/offsets. Defaults to app.stage for backward
1327
+ // compatibility; the engine passes a dedicated scaled world root so app.stage stays
1328
+ // identity (screen space) for unscaled UI layers.
1329
+ this._target = target ?? app.stage;
1315
1330
  this.setupObserver();
1316
1331
  }
1317
1332
  /** Current canvas width in game units */
@@ -1400,19 +1415,19 @@ class ViewportManager extends EventEmitter {
1400
1415
  const stageScale = scaleMode === ScaleMode.STRETCH
1401
1416
  ? Math.min(containerWidth / designWidth, containerHeight / designHeight)
1402
1417
  : scale;
1403
- this._app.stage.scale.set(stageScale);
1418
+ this._target.scale.set(stageScale);
1404
1419
  // Center the stage for FIT mode
1405
1420
  if (scaleMode === ScaleMode.FIT) {
1406
- this._app.stage.x = Math.round((containerWidth - designWidth * stageScale) / 2);
1407
- this._app.stage.y = Math.round((containerHeight - designHeight * stageScale) / 2);
1421
+ this._target.x = Math.round((containerWidth - designWidth * stageScale) / 2);
1422
+ this._target.y = Math.round((containerHeight - designHeight * stageScale) / 2);
1408
1423
  }
1409
1424
  else if (scaleMode === ScaleMode.FILL) {
1410
- this._app.stage.x = Math.round((containerWidth - gameWidth * stageScale) / 2);
1411
- this._app.stage.y = Math.round((containerHeight - gameHeight * stageScale) / 2);
1425
+ this._target.x = Math.round((containerWidth - gameWidth * stageScale) / 2);
1426
+ this._target.y = Math.round((containerHeight - gameHeight * stageScale) / 2);
1412
1427
  }
1413
1428
  else {
1414
- this._app.stage.x = 0;
1415
- this._app.stage.y = 0;
1429
+ this._target.x = 0;
1430
+ this._target.y = 0;
1416
1431
  }
1417
1432
  this._currentWidth = gameWidth;
1418
1433
  this._currentHeight = gameHeight;
@@ -1903,6 +1918,12 @@ class GameApplication extends EventEmitter {
1903
1918
  input;
1904
1919
  /** Viewport manager */
1905
1920
  viewport;
1921
+ /** Scaled world root (holds scenes). Transformed by the ViewportManager to fit the design
1922
+ * resolution; lives below the UI layer on app.stage. */
1923
+ worldRoot;
1924
+ /** Unscaled, screen-space UI layer. Sits above {@link worldRoot} and is NOT touched by the
1925
+ * viewport transform — children fill the real screen (e.g. the host's shell + overlay). */
1926
+ uiLayer;
1906
1927
  /** SDK instance (null in offline mode) */
1907
1928
  sdk = null;
1908
1929
  /** FPS overlay instance (only when debug: true) */
@@ -1911,6 +1932,8 @@ class GameApplication extends EventEmitter {
1911
1932
  initData = null;
1912
1933
  /** Platform session (SDK + optional DevBridge). null until start() runs. */
1913
1934
  platformSession = null;
1935
+ /** Branded game shell (only when config.shell is set). */
1936
+ shell;
1914
1937
  /** Configuration */
1915
1938
  config;
1916
1939
  // ─── Private state ──────────────────────────────────────
@@ -1972,6 +1995,11 @@ class GameApplication extends EventEmitter {
1972
1995
  await this.initPixi();
1973
1996
  // 4. Initialize SDK (if enabled)
1974
1997
  await this.initSDK();
1998
+ // 4b. Mount the branded game shell after the SDK handshake (optional)
1999
+ if (this.config.shell) {
2000
+ const { createGameShell } = await import('@energy8platform/platform-core/shell');
2001
+ this.shell = createGameShell(this.config.shell);
2002
+ }
1975
2003
  // 5. Merge design dimensions from SDK config
1976
2004
  this.applySDKConfig();
1977
2005
  // 6. Initialize sub-systems
@@ -1995,11 +2023,16 @@ class GameApplication extends EventEmitter {
1995
2023
  /**
1996
2024
  * Destroy the engine and free all resources.
1997
2025
  */
1998
- destroy() {
2026
+ async destroy() {
1999
2027
  if (this._destroyed)
2000
2028
  return;
2001
2029
  this._destroyed = true;
2002
2030
  this._running = false;
2031
+ if (this.shell) {
2032
+ const { removeGameShell } = await import('@energy8platform/platform-core/shell');
2033
+ await removeGameShell();
2034
+ this.shell = undefined;
2035
+ }
2003
2036
  this.scenes?.destroy();
2004
2037
  this.input?.destroy();
2005
2038
  this.audio?.destroy();
@@ -2070,20 +2103,27 @@ class GameApplication extends EventEmitter {
2070
2103
  this.audio = new AudioManager(this.config.audio);
2071
2104
  // Input Manager
2072
2105
  this.input = new InputManager(this.app.canvas);
2073
- // Viewport Manager
2106
+ // Stage layers: a scaled world root (scenes, transformed to design resolution by the
2107
+ // viewport) below an unscaled UI layer (screen space). app.stage itself stays identity.
2108
+ this.worldRoot = new Container();
2109
+ this.worldRoot.label = 'world';
2110
+ this.uiLayer = new Container();
2111
+ this.uiLayer.label = 'ui';
2112
+ this.app.stage.addChild(this.worldRoot, this.uiLayer);
2113
+ // Viewport Manager — scales worldRoot (NOT app.stage), so the UI layer is unscaled.
2074
2114
  this.viewport = new ViewportManager(this.app, this._container, {
2075
2115
  designWidth: this.config.designWidth,
2076
2116
  designHeight: this.config.designHeight,
2077
2117
  scaleMode: this.config.scaleMode,
2078
2118
  orientation: this.config.orientation,
2079
- });
2080
- // Wire SceneManager to the PixiJS stage
2081
- this.scenes.setRoot(this.app.stage);
2119
+ }, this.worldRoot);
2120
+ // Wire SceneManager to the scaled world root
2121
+ this.scenes.setRoot(this.worldRoot);
2082
2122
  this.scenes.setApp(this);
2083
2123
  // Wire viewport resize → scene manager + input manager
2084
2124
  this.viewport.on('resize', ({ width, height, scale }) => {
2085
2125
  this.scenes.resize(width, height);
2086
- this.input.setViewportTransform(scale, this.app.stage.x, this.app.stage.y);
2126
+ this.input.setViewportTransform(scale, this.worldRoot.x, this.worldRoot.y);
2087
2127
  this.emit('resize', { width, height });
2088
2128
  });
2089
2129
  this.viewport.on('orientationChange', (orientation) => {