@energy8platform/game-engine 0.15.1 → 0.16.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 (62) hide show
  1. package/README.md +22 -12
  2. package/dist/assets.d.ts +2 -14
  3. package/dist/core.cjs.js +16 -201
  4. package/dist/core.cjs.js.map +1 -1
  5. package/dist/core.d.ts +4 -35
  6. package/dist/core.esm.js +12 -197
  7. package/dist/core.esm.js.map +1 -1
  8. package/dist/debug.cjs.js +5 -233
  9. package/dist/debug.cjs.js.map +1 -1
  10. package/dist/debug.d.ts +2 -140
  11. package/dist/debug.esm.js +2 -233
  12. package/dist/debug.esm.js.map +1 -1
  13. package/dist/index.cjs.js +36 -442
  14. package/dist/index.cjs.js.map +1 -1
  15. package/dist/index.d.ts +10 -195
  16. package/dist/index.esm.js +29 -439
  17. package/dist/index.esm.js.map +1 -1
  18. package/dist/lua.cjs.js +7 -1493
  19. package/dist/lua.cjs.js.map +1 -1
  20. package/dist/lua.d.ts +1 -403
  21. package/dist/lua.esm.js +1 -1483
  22. package/dist/lua.esm.js.map +1 -1
  23. package/dist/react.cjs.js +15 -10
  24. package/dist/react.cjs.js.map +1 -1
  25. package/dist/react.d.ts +4 -35
  26. package/dist/react.esm.js +15 -10
  27. package/dist/react.esm.js.map +1 -1
  28. package/dist/ui.cjs.js +15 -10
  29. package/dist/ui.cjs.js.map +1 -1
  30. package/dist/ui.esm.js +15 -10
  31. package/dist/ui.esm.js.map +1 -1
  32. package/dist/vite.cjs.js +19 -175
  33. package/dist/vite.cjs.js.map +1 -1
  34. package/dist/vite.d.ts +8 -3
  35. package/dist/vite.esm.js +10 -173
  36. package/dist/vite.esm.js.map +1 -1
  37. package/package.json +6 -16
  38. package/src/core/GameApplication.ts +14 -18
  39. package/src/debug/index.ts +4 -2
  40. package/src/index.ts +3 -3
  41. package/src/loading/LoadingScene.ts +1 -1
  42. package/src/loading/index.ts +9 -2
  43. package/src/lua/index.ts +3 -31
  44. package/src/types.ts +16 -41
  45. package/src/ui/FlexContainer.ts +15 -10
  46. package/src/vite/index.ts +13 -196
  47. package/bin/simulate.ts +0 -139
  48. package/scripts/install-simulate.mjs +0 -101
  49. package/src/debug/DevBridge.ts +0 -305
  50. package/src/loading/CSSPreloader.ts +0 -129
  51. package/src/loading/logo.ts +0 -95
  52. package/src/lua/ActionRouter.ts +0 -132
  53. package/src/lua/LuaEngine.ts +0 -412
  54. package/src/lua/LuaEngineAPI.ts +0 -314
  55. package/src/lua/NativeSimulationRunner.ts +0 -367
  56. package/src/lua/ParallelSimulationRunner.ts +0 -156
  57. package/src/lua/PersistentState.ts +0 -80
  58. package/src/lua/SessionManager.ts +0 -227
  59. package/src/lua/SimulationRunner.ts +0 -192
  60. package/src/lua/SimulationWorker.ts +0 -44
  61. package/src/lua/fengari.d.ts +0 -10
  62. package/src/lua/types.ts +0 -149
package/dist/index.cjs.js CHANGED
@@ -1,7 +1,10 @@
1
1
  'use strict';
2
2
 
3
3
  var pixi_js = require('pixi.js');
4
+ var platformCore = require('@energy8platform/platform-core');
5
+ var loading = require('@energy8platform/platform-core/loading');
4
6
  var gameSdk = require('@energy8platform/game-sdk');
7
+ var devBridge = require('@energy8platform/platform-core/dev-bridge');
5
8
 
6
9
  // ─── Scale Modes ───────────────────────────────────────────
7
10
  exports.ScaleMode = void 0;
@@ -1499,81 +1502,12 @@ class Scene {
1499
1502
  }
1500
1503
  }
1501
1504
 
1502
- /**
1503
- * Shared Energy8 SVG logo with an embedded loader bar.
1504
- *
1505
- * The loader bar fill is controlled via a `<clipPath>` whose `<rect>` width
1506
- * is animatable. Different consumers customise gradient IDs and the clip
1507
- * element's ID/class to avoid collisions when both CSSPreloader and
1508
- * LoadingScene appear in the same DOM.
1509
- */
1510
- /** SVG path data for the Energy8 wordmark — reused across loaders */
1511
- const WORDMARK_PATHS = `
1512
- <path d="m241 81.75h-19.28c-1.77 0-6.73 4.98-7.43 6.99l-4.36 12.22c-0.49 1.37 0.05 2.92 1.06 4.32-2.07 1.19-3.69 3.08-4.36 5.43l-3.25 10.41c-0.86 2.89 2.39 6.63 4.31 6.63h19.28c1.96 0 7.4-5.56 7.96-7.51l2.96-10.22c0.63-2.25 0.1-3.98-1.22-4.99 2.55-1.56 3.86-4.14 4.55-6.31l2.77-9.31c0.74-2.57-1.37-7.66-2.99-7.66zm-13.36 28.31-2.27 7.03h-8.28l2.58-8.28h8.28l-0.31 1.25zm4.06-16.97-2.11 6.7h-7.04l2.25-7.34h7.26l-0.36 0.64z" fill="url(#GID0)"/>
1513
- <path d="m202.5 81.75-9.31 14.97-2.32-14.97h-11.82l4.32 25.15-0.57 4.91-8.64 26.44 15.31-12.76 5.63-16.48 19.96-27.26h-12.56z" fill="url(#GID1)"/>
1514
- <path d="m174.2 81.75h-19.78l-5.75 5.16-10.79 33.2c-0.77 2.53 2.48 6.93 4.87 6.93h17.38c2.63 0 7.85-5.34 8.32-6.83l5.37-18.14h-15.17l-2.2 7.64h3.78l-2.25 7.2h-8.01l7.1-25.52h7.58l-1.48 8.4 12.78-5.98c1.28-0.63 1.97-3.99 1.61-6.61-0.36-2.34-1.64-5.45-3.36-5.45z" fill="url(#GID2)"/>
1515
- <path d="m140.6 81.75h-70.6l-5.36 19.37-4.26-19.37h-46.76l2.95 5.88-10.58 39.28h26.84l2.95-9.52-15.63-0.13 2.55-8.34h8.74l8.47-9.81h-14.61l2.11-7.3h15.47l2.54-8.71 2.58 4.74-11.4 39.07h11.05l6.46-21.49 8.84 36.33 19.18-55.67-1.83-3.36 3.68 4.09-12.07 40.1h28.18l3.39-10.31h-17.01l2.67-8.03h9.98l7.58-9.52h-14.28l1.93-6.6h14.61l3.25-9.73 2.81 5.12-11.3 38.89h11.05l5.23-17.81h1.62l1.48 17.6h10.69l-1.48-16.81c4.75-1.28 7.52-5.9 8.64-9.81l2.95-11.3c0.86-2.73-1.43-6.85-3.3-6.85zm-9.8 17.3h-8.69l2.54-7.84h8.35l-2.2 7.84z" fill="url(#GID3)"/>
1516
- <path d="m205.9 148.9h-122.6l-2.61-3.12h-32.4l-2.51 3.12h-1.59c-5.34 0-7.94 4.88-7.94 7.65v0.03c0 4.2 3.55 7.6 7.74 7.6h103.6l2.11 3.12h36.09l1.82-3.12h18.3c5.25 0 6.64-5.3 6.64-7.35v-0.25c0-4.23-2.9-7.68-6.64-7.68zm-0.7 12.83h-160.6c-3.69 0-6.11-2.58-6.11-5.47v-0.03c0-2.89 2.1-5.47 5.61-5.47h161.1c3.45 0 4.89 3.12 4.89 5.65v0.17c0 2.57-2.11 5.15-4.89 5.15z" fill="url(#GID4)"/>`;
1517
- /** Gradient definitions template (gradient IDs are replaced per-consumer) */
1518
- const GRADIENT_DEFS = `
1519
- <linearGradient id="GID0" x1="223.7" x2="223.7" y1="81.75" y2="127.8" gradientUnits="userSpaceOnUse">
1520
- <stop stop-color="#663BA6"/><stop stop-color="#7939C2" offset=".349"/><stop stop-color="#8A2FC0" offset=".6615"/><stop stop-color="#791BA3" offset="1"/>
1521
- </linearGradient>
1522
- <linearGradient id="GID1" x1="194.6" x2="194.6" y1="81.75" y2="138.3" gradientUnits="userSpaceOnUse">
1523
- <stop stop-color="#663BA6"/><stop stop-color="#7939C2" offset=".349"/><stop stop-color="#8A2FC0" offset=".6615"/><stop stop-color="#791BA3" offset="1"/>
1524
- </linearGradient>
1525
- <linearGradient id="GID2" x1="157.8" x2="157.8" y1="81.75" y2="127" gradientUnits="userSpaceOnUse">
1526
- <stop stop-color="#663BA6"/><stop stop-color="#7939C2" offset=".349"/><stop stop-color="#8A2FC0" offset=".6615"/><stop stop-color="#791BA3" offset="1"/>
1527
- </linearGradient>
1528
- <linearGradient id="GID3" x1="79.96" x2="79.96" y1="81.75" y2="141.8" gradientUnits="userSpaceOnUse">
1529
- <stop stop-color="#663BA6"/><stop stop-color="#7939C2" offset=".349"/><stop stop-color="#8A2FC0" offset=".6615"/><stop stop-color="#791BA3" offset="1"/>
1530
- </linearGradient>
1531
- <linearGradient id="GID4" x1="36.18" x2="212.5" y1="156.6" y2="156.6" gradientUnits="userSpaceOnUse">
1532
- <stop stop-color="#316FB0"/><stop stop-color="#1FCDE6" offset=".5"/><stop stop-color="#29FEE7" offset="1"/>
1533
- </linearGradient>
1534
- <linearGradient id="GID5" x1="40.27" x2="208.2" y1="156.4" y2="156.4" gradientUnits="userSpaceOnUse">
1535
- <stop stop-color="#316FB0"/><stop stop-color="#1FCDE6" offset=".5"/><stop stop-color="#29FEE7" offset="1"/>
1536
- </linearGradient>`;
1537
- /** Max width of the loader bar in SVG units */
1538
- const LOADER_BAR_MAX_WIDTH = 174;
1539
- /**
1540
- * Build the Energy8 SVG logo with a loader bar, using unique IDs.
1541
- *
1542
- * @param opts - Configuration to avoid element ID collisions
1543
- * @returns SVG markup string
1544
- */
1545
- function buildLogoSVG(opts) {
1546
- const { idPrefix, svgClass, svgStyle, clipRectClass, clipRectId, textId, textContent, textClass } = opts;
1547
- // Replace gradient ID placeholders with prefixed versions
1548
- const paths = WORDMARK_PATHS.replace(/GID(\d)/g, `${idPrefix}$1`);
1549
- const defs = GRADIENT_DEFS.replace(/GID(\d)/g, `${idPrefix}$1`);
1550
- const clipId = `${idPrefix}-loader-clip`;
1551
- const fillGradientId = `${idPrefix}5`;
1552
- const classAttr = svgClass ? ` class="${svgClass}"` : '';
1553
- const styleAttr = svgStyle ? ` style="${svgStyle}"` : '';
1554
- const rectClassAttr = clipRectClass ? ` class="${clipRectClass}"` : '';
1555
- const rectIdAttr = clipRectId ? ` id="${clipRectId}"` : '';
1556
- const txtIdAttr = textId ? ` id="${textId}"` : '';
1557
- const txtClassAttr = textClass ? ` class="${textClass}"` : '';
1558
- return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 200" fill="none"${classAttr}${styleAttr}>
1559
- ${paths}
1560
- <clipPath id="${clipId}">
1561
- <rect${rectIdAttr} x="37" y="148" width="0" height="20"${rectClassAttr}/>
1562
- </clipPath>
1563
- <path d="m204.5 152.6h-159.8c-2.78 0-4.45 1.69-4.45 3.99v0.11c0 2.04 1.42 3.43 3.64 3.43h160.6c2.88 0 3.67-2.07 3.67-3.43v-0.25c0-2.04-1.48-3.85-3.67-3.85z" fill="url(#${fillGradientId})" clip-path="url(#${clipId})"/>
1564
- <text${txtIdAttr} x="125" y="196" text-anchor="middle" fill="rgba(255,255,255,0.6)" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif" font-size="8" font-weight="600" letter-spacing="1.5"${txtClassAttr}>${textContent ?? 'Loading...'}</text>
1565
- <defs>
1566
- ${defs}
1567
- </defs>
1568
- </svg>`;
1569
- }
1570
-
1571
1505
  /**
1572
1506
  * Build the loading scene variant of the logo SVG.
1573
1507
  * Uses unique IDs (prefixed with 'ls') to avoid collisions with CSSPreloader.
1574
1508
  */
1575
1509
  function buildLoadingLogoSVG() {
1576
- return buildLogoSVG({
1510
+ return loading.buildLogoSVG({
1577
1511
  idPrefix: 'ls',
1578
1512
  svgStyle: 'width:100%;height:auto;',
1579
1513
  clipRectId: 'ge-loader-rect',
@@ -1754,7 +1688,7 @@ class LoadingScene extends Scene {
1754
1688
  // ─── Progress ──────────────────────────────────────────
1755
1689
  updateLoaderBar(progress) {
1756
1690
  if (this._loaderRect) {
1757
- this._loaderRect.setAttribute('width', String(LOADER_BAR_MAX_WIDTH * progress));
1691
+ this._loaderRect.setAttribute('width', String(loading.LOADER_BAR_MAX_WIDTH * progress));
1758
1692
  }
1759
1693
  if (this._percentEl) {
1760
1694
  const pct = Math.round(progress * 100);
@@ -1836,120 +1770,6 @@ class LoadingScene extends Scene {
1836
1770
  }
1837
1771
  }
1838
1772
 
1839
- const PRELOADER_ID = '__ge-css-preloader__';
1840
- /**
1841
- * Inline SVG logo with animated loader bar.
1842
- * The `#loader` path acts as the progress fill — animated via clipPath.
1843
- */
1844
- const LOGO_SVG = buildLogoSVG({
1845
- idPrefix: 'pl',
1846
- svgClass: 'ge-logo-svg',
1847
- clipRectClass: 'ge-clip-rect',
1848
- textClass: 'ge-preloader-svg-text',
1849
- });
1850
- /**
1851
- * Creates a lightweight CSS-only preloader that appears instantly,
1852
- * BEFORE PixiJS/WebGL is initialized.
1853
- *
1854
- * Displays the Energy8 logo SVG with an animated loader bar.
1855
- */
1856
- function createCSSPreloader(container, config) {
1857
- if (document.getElementById(PRELOADER_ID))
1858
- return;
1859
- const bgColor = typeof config?.backgroundColor === 'string'
1860
- ? config.backgroundColor
1861
- : typeof config?.backgroundColor === 'number'
1862
- ? `#${config.backgroundColor.toString(16).padStart(6, '0')}`
1863
- : '#0a0a1a';
1864
- const bgGradient = config?.backgroundGradient ?? `linear-gradient(135deg, ${bgColor} 0%, #1a1a3e 100%)`;
1865
- const customHTML = config?.cssPreloaderHTML ?? '';
1866
- const el = document.createElement('div');
1867
- el.id = PRELOADER_ID;
1868
- el.innerHTML = customHTML || `
1869
- <div class="ge-preloader-content">
1870
- ${LOGO_SVG}
1871
- </div>
1872
- `;
1873
- const style = document.createElement('style');
1874
- style.textContent = `
1875
- #${PRELOADER_ID} {
1876
- position: absolute;
1877
- top: 0; left: 0;
1878
- width: 100%; height: 100%;
1879
- background: ${bgGradient};
1880
- display: flex;
1881
- align-items: center;
1882
- justify-content: center;
1883
- z-index: 10000;
1884
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
1885
- transition: opacity 0.4s ease-out;
1886
- }
1887
-
1888
- #${PRELOADER_ID}.ge-preloader-hidden {
1889
- opacity: 0;
1890
- pointer-events: none;
1891
- }
1892
-
1893
- .ge-preloader-content {
1894
- display: flex;
1895
- flex-direction: column;
1896
- align-items: center;
1897
- width: 80%;
1898
- max-width: 700px;
1899
- }
1900
-
1901
- .ge-logo-svg {
1902
- width: 100%;
1903
- height: auto;
1904
- filter: drop-shadow(0 0 30px rgba(121, 57, 194, 0.4));
1905
- }
1906
-
1907
- /* Animate the loader clip-rect to shimmer while waiting */
1908
- .ge-clip-rect {
1909
- animation: ge-loader-fill 2s ease-in-out infinite;
1910
- }
1911
-
1912
- @keyframes ge-loader-fill {
1913
- 0% { width: 0; }
1914
- 50% { width: 174; }
1915
- 100% { width: 0; }
1916
- }
1917
-
1918
- /* Animate the SVG text opacity */
1919
- .ge-preloader-svg-text {
1920
- animation: ge-pulse 1.5s ease-in-out infinite;
1921
- }
1922
-
1923
- @keyframes ge-pulse {
1924
- 0%, 100% { opacity: 0.4; }
1925
- 50% { opacity: 1; }
1926
- }
1927
- `;
1928
- container.style.position = container.style.position || 'relative';
1929
- container.appendChild(style);
1930
- container.appendChild(el);
1931
- }
1932
- /**
1933
- * Remove the CSS preloader with a smooth fade-out transition.
1934
- */
1935
- function removeCSSPreloader(container) {
1936
- const el = document.getElementById(PRELOADER_ID);
1937
- if (!el)
1938
- return;
1939
- el.classList.add('ge-preloader-hidden');
1940
- // Remove after transition
1941
- el.addEventListener('transitionend', () => {
1942
- el.remove();
1943
- // Also remove the style element
1944
- const styles = container.querySelectorAll('style');
1945
- for (const style of styles) {
1946
- if (style.textContent?.includes(PRELOADER_ID)) {
1947
- style.remove();
1948
- }
1949
- }
1950
- });
1951
- }
1952
-
1953
1773
  /**
1954
1774
  * FPS overlay for debugging performance.
1955
1775
  *
@@ -2091,6 +1911,8 @@ class GameApplication extends EventEmitter {
2091
1911
  fpsOverlay = null;
2092
1912
  /** Data received from SDK initialization */
2093
1913
  initData = null;
1914
+ /** Platform session (SDK + optional DevBridge). null until start() runs. */
1915
+ platformSession = null;
2094
1916
  /** Configuration */
2095
1917
  config;
2096
1918
  // ─── Private state ──────────────────────────────────────
@@ -2147,7 +1969,7 @@ class GameApplication extends EventEmitter {
2147
1969
  // 1. Resolve container element
2148
1970
  this._container = this.resolveContainer();
2149
1971
  // 2. Show CSS preloader immediately (before PixiJS)
2150
- createCSSPreloader(this._container, this.config.loading);
1972
+ loading.createCSSPreloader(this._container, this.config.loading);
2151
1973
  // 3. Initialize PixiJS
2152
1974
  await this.initPixi();
2153
1975
  // 4. Initialize SDK (if enabled)
@@ -2158,7 +1980,7 @@ class GameApplication extends EventEmitter {
2158
1980
  this.initSubSystems();
2159
1981
  this.emit('initialized');
2160
1982
  // 7. Remove CSS preloader, show Canvas loading screen
2161
- removeCSSPreloader(this._container);
1983
+ loading.removeCSSPreloader(this._container);
2162
1984
  // 8. Load assets with loading screen
2163
1985
  await this.loadAssets(firstScene, sceneData);
2164
1986
  this.emit('loaded');
@@ -2184,7 +2006,7 @@ class GameApplication extends EventEmitter {
2184
2006
  this.input?.destroy();
2185
2007
  this.audio?.destroy();
2186
2008
  this.viewport?.destroy();
2187
- this.sdk?.destroy();
2009
+ this.platformSession?.destroy();
2188
2010
  this.app?.destroy(true, { children: true, texture: true });
2189
2011
  this.emit('destroyed');
2190
2012
  this.removeAllListeners();
@@ -2220,20 +2042,15 @@ class GameApplication extends EventEmitter {
2220
2042
  this.app.canvas.style.height = '100%';
2221
2043
  }
2222
2044
  async initSDK() {
2223
- if (this.config.sdk === false) {
2224
- // Offline / development mode no SDK
2225
- this.initData = null;
2226
- return;
2227
- }
2228
- const sdkOpts = typeof this.config.sdk === 'object' ? this.config.sdk : {};
2229
- this.sdk = new gameSdk.CasinoGameSDK(sdkOpts);
2230
- // Perform the handshake
2231
- this.initData = await this.sdk.ready();
2232
- // Forward SDK events
2233
- this.sdk.on('error', (err) => {
2045
+ // Delegate the SDK handshake (and any optional in-process DevBridge
2046
+ // wiring) to platform-core. The session forwards SDK events upward.
2047
+ this.platformSession = await platformCore.createPlatformSession({ sdk: this.config.sdk });
2048
+ this.sdk = this.platformSession.sdk;
2049
+ this.initData = this.platformSession.initData;
2050
+ this.platformSession.on('error', (err) => {
2234
2051
  this.emit('error', err);
2235
2052
  });
2236
- this.sdk.on('balanceUpdate', (data) => {
2053
+ this.platformSession.on('balanceUpdate', (data) => {
2237
2054
  this.emit('balanceUpdate', data);
2238
2055
  });
2239
2056
  }
@@ -2945,24 +2762,29 @@ function measureChild(child, parentContentW = 0, parentContentH = 0) {
2945
2762
  const cfg = child._flexConfig;
2946
2763
  const resolvedLW = resolveDimension(cfg?.layoutWidth, parentContentW);
2947
2764
  const resolvedLH = resolveDimension(cfg?.layoutHeight, parentContentH);
2948
- if (resolvedLW !== undefined && resolvedLH !== undefined) {
2949
- return { w: resolvedLW, h: resolvedLH, ox: 0, oy: 0 };
2950
- }
2951
- // For FlexContainers, use their explicit or computed size
2765
+ // FlexContainer children are top-left origin by construction, so `ox/oy = 0`
2766
+ // is correct and we can skip the `getLocalBounds()` call entirely.
2952
2767
  if (child instanceof FlexContainer) {
2953
2768
  const fc = child;
2954
2769
  const w = fc._explicitWidth > 0 ? fc._explicitWidth : (fc._computedWidth > 0 ? fc._computedWidth : undefined);
2955
2770
  const h = fc._explicitHeight > 0 ? fc._explicitHeight : (fc._computedHeight > 0 ? fc._computedHeight : undefined);
2956
2771
  if (w !== undefined && h !== undefined) {
2957
- return { w, h, ox: 0, oy: 0 };
2772
+ return { w: resolvedLW ?? w, h: resolvedLH ?? h, ox: 0, oy: 0 };
2958
2773
  }
2959
2774
  }
2960
- // Use localBounds to get the true visual extent and origin offset.
2961
- // This handles children with non-zero anchors (e.g. Button, Label with centered text).
2775
+ // Use localBounds to get the true visual origin offset.
2776
+ // We take `ox/oy` from bounds unconditionally a user-supplied `layoutWidth/Height`
2777
+ // overrides the SIZE used by flex (protection against pre-paint bounds of 0), but
2778
+ // it must NOT erase the center-anchor compensation: Button/Label views are drawn at
2779
+ // `(-w/2..w/2)` and their bounds.x = -w/2 is what lets `layoutLine` place the
2780
+ // visible rectangle where the layout intends.
2962
2781
  const bounds = child.getLocalBounds();
2963
- const w = resolvedLW ?? bounds.width;
2964
- const h = resolvedLH ?? bounds.height;
2965
- return { w, h, ox: bounds.x, oy: bounds.y };
2782
+ return {
2783
+ w: resolvedLW ?? bounds.width,
2784
+ h: resolvedLH ?? bounds.height,
2785
+ ox: bounds.x,
2786
+ oy: bounds.y,
2787
+ };
2966
2788
  }
2967
2789
  /**
2968
2790
  * Set a child's main or cross dimension.
@@ -5230,237 +5052,6 @@ class ScrollContainer extends pixi_js.Container {
5230
5052
  }
5231
5053
  }
5232
5054
 
5233
- const DEFAULT_CONFIG = {
5234
- balance: 10000,
5235
- currency: 'USD',
5236
- gameConfig: {
5237
- id: 'dev-game',
5238
- type: 'slot',
5239
- version: '1.0.0',
5240
- viewport: { width: 1920, height: 1080 },
5241
- betLevels: [0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 50],
5242
- },
5243
- assetsUrl: '/assets/',
5244
- session: null,
5245
- onPlay: () => ({}),
5246
- networkDelay: 200,
5247
- debug: true,
5248
- };
5249
- /**
5250
- * Mock host bridge for local development.
5251
- *
5252
- * Uses the SDK's `Bridge` class in `devMode` to communicate with
5253
- * `CasinoGameSDK` via a shared in-memory `MemoryChannel`, removing
5254
- * the need for postMessage and iframes.
5255
- *
5256
- * When `luaScript` is set, play requests are sent to the Vite dev server
5257
- * which runs LuaEngine in Node.js — no fengari in the browser.
5258
- *
5259
- * @example
5260
- * ```ts
5261
- * import { DevBridge } from '@energy8platform/game-engine/debug';
5262
- *
5263
- * const devBridge = new DevBridge({
5264
- * balance: 5000,
5265
- * currency: 'EUR',
5266
- * gameConfig: { id: 'my-slot', type: 'slot', betLevels: [0.2, 0.5, 1, 2] },
5267
- * onPlay: ({ action, bet }) => ({
5268
- * totalWin: Math.random() > 0.5 ? bet * (Math.random() * 20) : 0,
5269
- * data: { matrix: [[1,2,3],[4,5,6],[7,8,9]] },
5270
- * }),
5271
- * });
5272
- * devBridge.start();
5273
- * ```
5274
- */
5275
- class DevBridge {
5276
- _config;
5277
- _balance;
5278
- _roundCounter = 0;
5279
- _bridge = null;
5280
- _useLuaServer;
5281
- constructor(config = {}) {
5282
- this._config = { ...DEFAULT_CONFIG, ...config };
5283
- this._balance = this._config.balance;
5284
- this._useLuaServer = !!(this._config.luaScript && this._config.gameDefinition);
5285
- }
5286
- /** Current mock balance */
5287
- get balance() {
5288
- return this._balance;
5289
- }
5290
- /** Start listening for SDK messages */
5291
- start() {
5292
- if (this._bridge)
5293
- return;
5294
- console.debug('[DevBridge] Starting with config:', this._config);
5295
- this._bridge = new gameSdk.Bridge({ devMode: true, debug: this._config.debug });
5296
- this._bridge.on('GAME_READY', (_payload, id) => {
5297
- this.handleGameReady(id);
5298
- });
5299
- this._bridge.on('PLAY_REQUEST', (payload, id) => {
5300
- this.handlePlayRequest(payload, id);
5301
- });
5302
- this._bridge.on('PLAY_RESULT_ACK', (payload) => {
5303
- this.handlePlayAck(payload);
5304
- });
5305
- this._bridge.on('GET_BALANCE', (_payload, id) => {
5306
- this.handleGetBalance(id);
5307
- });
5308
- this._bridge.on('GET_STATE', (_payload, id) => {
5309
- this.handleGetState(id);
5310
- });
5311
- this._bridge.on('OPEN_DEPOSIT', () => {
5312
- this.handleOpenDeposit();
5313
- });
5314
- if (this._config.debug) {
5315
- const mode = this._useLuaServer ? 'Lua (server-side)' : 'onPlay callback';
5316
- console.log(`[DevBridge] Started — mode: ${mode}`);
5317
- }
5318
- }
5319
- /** Stop listening */
5320
- stop() {
5321
- if (this._bridge) {
5322
- this._bridge.destroy();
5323
- this._bridge = null;
5324
- }
5325
- if (this._config.debug) {
5326
- console.log('[DevBridge] Stopped');
5327
- }
5328
- }
5329
- /** Set mock balance */
5330
- setBalance(balance) {
5331
- this._balance = balance;
5332
- this._bridge?.send('BALANCE_UPDATE', { balance: this._balance });
5333
- }
5334
- /** Destroy the dev bridge */
5335
- destroy() {
5336
- this.stop();
5337
- }
5338
- // ─── Message Handling ──────────────────────────────────
5339
- handleGameReady(id) {
5340
- const initData = {
5341
- balance: this._balance,
5342
- currency: this._config.currency,
5343
- config: this._config.gameConfig,
5344
- session: this._config.session,
5345
- assetsUrl: this._config.assetsUrl,
5346
- };
5347
- this.delayedSend('INIT', initData, id);
5348
- }
5349
- handlePlayRequest(payload, id) {
5350
- const { action, bet, roundId, params } = payload;
5351
- this._roundCounter++;
5352
- if (this._useLuaServer) {
5353
- // Debit bet (server deducts before Lua execution)
5354
- // For session actions (free spins), debit is 0 — LuaEngine handles bet from session
5355
- this._balance -= bet;
5356
- this.executeLuaOnServer({ action, bet, roundId, params })
5357
- .then((result) => {
5358
- this._bridge?.send('PLAY_RESULT', result, id);
5359
- })
5360
- .catch((err) => {
5361
- console.error('[DevBridge] Lua server error:', err);
5362
- this._balance += bet;
5363
- this._bridge?.send('PLAY_RESULT', this.buildFallbackResult(action, bet, roundId), id);
5364
- });
5365
- }
5366
- else {
5367
- // Fallback to onPlay callback
5368
- const customResult = this._config.onPlay({ action, bet, roundId, params });
5369
- const totalWin = customResult.totalWin ?? (Math.random() > 0.6 ? bet * (1 + Math.random() * 10) : 0);
5370
- this._balance += totalWin;
5371
- const result = {
5372
- roundId: roundId ?? `dev-round-${this._roundCounter}`,
5373
- action,
5374
- balanceAfter: this._balance,
5375
- totalWin: Math.round(totalWin * 100) / 100,
5376
- data: customResult.data ?? {},
5377
- nextActions: customResult.nextActions ?? ['spin'],
5378
- session: customResult.session ?? null,
5379
- creditPending: false,
5380
- bonusFreeSpin: customResult.bonusFreeSpin ?? null,
5381
- currency: this._config.currency,
5382
- gameId: this._config.gameConfig?.id ?? 'dev-game',
5383
- };
5384
- this.delayedSend('PLAY_RESULT', result, id);
5385
- }
5386
- }
5387
- async executeLuaOnServer(params) {
5388
- const response = await fetch('/__lua-play', {
5389
- method: 'POST',
5390
- headers: { 'Content-Type': 'application/json' },
5391
- body: JSON.stringify(params),
5392
- });
5393
- if (!response.ok) {
5394
- const err = await response.json().catch(() => ({ error: 'Unknown error' }));
5395
- throw new Error(err.error ?? `HTTP ${response.status}`);
5396
- }
5397
- const luaResult = await response.json();
5398
- // Server credit logic:
5399
- // shouldCredit = (no session) OR (session.completed)
5400
- // creditAmount = result.totalWin
5401
- const shouldCredit = !luaResult.session || luaResult.session.completed;
5402
- if (shouldCredit && luaResult.totalWin > 0) {
5403
- this._balance += luaResult.totalWin;
5404
- }
5405
- return {
5406
- roundId: params.roundId ?? `dev-round-${this._roundCounter}`,
5407
- action: params.action,
5408
- balanceAfter: this._balance,
5409
- totalWin: Math.round(luaResult.totalWin * 100) / 100,
5410
- data: luaResult.data,
5411
- nextActions: luaResult.nextActions,
5412
- session: luaResult.session,
5413
- creditPending: !shouldCredit,
5414
- bonusFreeSpin: null,
5415
- currency: this._config.currency,
5416
- gameId: this._config.gameConfig?.id ?? 'dev-game',
5417
- };
5418
- }
5419
- buildFallbackResult(action, bet, roundId) {
5420
- return {
5421
- roundId: roundId ?? `dev-round-${this._roundCounter}`,
5422
- action,
5423
- balanceAfter: this._balance,
5424
- totalWin: 0,
5425
- data: { error: 'Lua execution failed' },
5426
- nextActions: ['spin'],
5427
- session: null,
5428
- creditPending: false,
5429
- bonusFreeSpin: null,
5430
- currency: this._config.currency,
5431
- gameId: this._config.gameConfig?.id ?? 'dev-game',
5432
- };
5433
- }
5434
- handlePlayAck(_payload) {
5435
- if (this._config.debug) {
5436
- console.log('[DevBridge] Play acknowledged');
5437
- }
5438
- }
5439
- handleGetBalance(id) {
5440
- this.delayedSend('BALANCE_UPDATE', { balance: this._balance }, id);
5441
- }
5442
- handleGetState(id) {
5443
- this.delayedSend('STATE_RESPONSE', { session: this._config.session ?? null }, id);
5444
- }
5445
- handleOpenDeposit() {
5446
- if (this._config.debug) {
5447
- console.log('[DevBridge] Open deposit requested (mock: adding 1000)');
5448
- }
5449
- this._balance += 1000;
5450
- this._bridge?.send('BALANCE_UPDATE', { balance: this._balance });
5451
- }
5452
- // ─── Communication ─────────────────────────────────────
5453
- delayedSend(type, payload, id) {
5454
- const delay = this._config.networkDelay;
5455
- if (delay > 0) {
5456
- setTimeout(() => this._bridge?.send(type, payload, id), delay);
5457
- }
5458
- else {
5459
- this._bridge?.send(type, payload, id);
5460
- }
5461
- }
5462
- }
5463
-
5464
5055
  Object.defineProperty(exports, "BridgeDestroyedError", {
5465
5056
  enumerable: true,
5466
5057
  get: function () { return gameSdk.BridgeDestroyedError; }
@@ -5477,11 +5068,14 @@ Object.defineProperty(exports, "TimeoutError", {
5477
5068
  enumerable: true,
5478
5069
  get: function () { return gameSdk.TimeoutError; }
5479
5070
  });
5071
+ Object.defineProperty(exports, "DevBridge", {
5072
+ enumerable: true,
5073
+ get: function () { return devBridge.DevBridge; }
5074
+ });
5480
5075
  exports.AssetManager = AssetManager;
5481
5076
  exports.AudioManager = AudioManager;
5482
5077
  exports.BalanceDisplay = BalanceDisplay;
5483
5078
  exports.Button = Button;
5484
- exports.DevBridge = DevBridge;
5485
5079
  exports.Easing = Easing;
5486
5080
  exports.EventEmitter = EventEmitter;
5487
5081
  exports.FPSOverlay = FPSOverlay;