@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.esm.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import { Ticker, Assets, Container, Text, Application, AnimatedSprite, Texture, Sprite, Graphics, NineSliceSprite } from 'pixi.js';
2
- import { CasinoGameSDK, Bridge } from '@energy8platform/game-sdk';
2
+ import { createPlatformSession } from '@energy8platform/platform-core';
3
+ import { LOADER_BAR_MAX_WIDTH, buildLogoSVG, createCSSPreloader, removeCSSPreloader } from '@energy8platform/platform-core/loading';
3
4
  export { BridgeDestroyedError, BridgeNotReadyError, SDKError, TimeoutError } from '@energy8platform/game-sdk';
5
+ export { DevBridge } from '@energy8platform/platform-core/dev-bridge';
4
6
 
5
7
  // ─── Scale Modes ───────────────────────────────────────────
6
8
  var ScaleMode;
@@ -1498,75 +1500,6 @@ class Scene {
1498
1500
  }
1499
1501
  }
1500
1502
 
1501
- /**
1502
- * Shared Energy8 SVG logo with an embedded loader bar.
1503
- *
1504
- * The loader bar fill is controlled via a `<clipPath>` whose `<rect>` width
1505
- * is animatable. Different consumers customise gradient IDs and the clip
1506
- * element's ID/class to avoid collisions when both CSSPreloader and
1507
- * LoadingScene appear in the same DOM.
1508
- */
1509
- /** SVG path data for the Energy8 wordmark — reused across loaders */
1510
- const WORDMARK_PATHS = `
1511
- <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)"/>
1512
- <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)"/>
1513
- <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)"/>
1514
- <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)"/>
1515
- <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)"/>`;
1516
- /** Gradient definitions template (gradient IDs are replaced per-consumer) */
1517
- const GRADIENT_DEFS = `
1518
- <linearGradient id="GID0" x1="223.7" x2="223.7" y1="81.75" y2="127.8" gradientUnits="userSpaceOnUse">
1519
- <stop stop-color="#663BA6"/><stop stop-color="#7939C2" offset=".349"/><stop stop-color="#8A2FC0" offset=".6615"/><stop stop-color="#791BA3" offset="1"/>
1520
- </linearGradient>
1521
- <linearGradient id="GID1" x1="194.6" x2="194.6" y1="81.75" y2="138.3" gradientUnits="userSpaceOnUse">
1522
- <stop stop-color="#663BA6"/><stop stop-color="#7939C2" offset=".349"/><stop stop-color="#8A2FC0" offset=".6615"/><stop stop-color="#791BA3" offset="1"/>
1523
- </linearGradient>
1524
- <linearGradient id="GID2" x1="157.8" x2="157.8" y1="81.75" y2="127" gradientUnits="userSpaceOnUse">
1525
- <stop stop-color="#663BA6"/><stop stop-color="#7939C2" offset=".349"/><stop stop-color="#8A2FC0" offset=".6615"/><stop stop-color="#791BA3" offset="1"/>
1526
- </linearGradient>
1527
- <linearGradient id="GID3" x1="79.96" x2="79.96" y1="81.75" y2="141.8" gradientUnits="userSpaceOnUse">
1528
- <stop stop-color="#663BA6"/><stop stop-color="#7939C2" offset=".349"/><stop stop-color="#8A2FC0" offset=".6615"/><stop stop-color="#791BA3" offset="1"/>
1529
- </linearGradient>
1530
- <linearGradient id="GID4" x1="36.18" x2="212.5" y1="156.6" y2="156.6" gradientUnits="userSpaceOnUse">
1531
- <stop stop-color="#316FB0"/><stop stop-color="#1FCDE6" offset=".5"/><stop stop-color="#29FEE7" offset="1"/>
1532
- </linearGradient>
1533
- <linearGradient id="GID5" x1="40.27" x2="208.2" y1="156.4" y2="156.4" gradientUnits="userSpaceOnUse">
1534
- <stop stop-color="#316FB0"/><stop stop-color="#1FCDE6" offset=".5"/><stop stop-color="#29FEE7" offset="1"/>
1535
- </linearGradient>`;
1536
- /** Max width of the loader bar in SVG units */
1537
- const LOADER_BAR_MAX_WIDTH = 174;
1538
- /**
1539
- * Build the Energy8 SVG logo with a loader bar, using unique IDs.
1540
- *
1541
- * @param opts - Configuration to avoid element ID collisions
1542
- * @returns SVG markup string
1543
- */
1544
- function buildLogoSVG(opts) {
1545
- const { idPrefix, svgClass, svgStyle, clipRectClass, clipRectId, textId, textContent, textClass } = opts;
1546
- // Replace gradient ID placeholders with prefixed versions
1547
- const paths = WORDMARK_PATHS.replace(/GID(\d)/g, `${idPrefix}$1`);
1548
- const defs = GRADIENT_DEFS.replace(/GID(\d)/g, `${idPrefix}$1`);
1549
- const clipId = `${idPrefix}-loader-clip`;
1550
- const fillGradientId = `${idPrefix}5`;
1551
- const classAttr = svgClass ? ` class="${svgClass}"` : '';
1552
- const styleAttr = svgStyle ? ` style="${svgStyle}"` : '';
1553
- const rectClassAttr = clipRectClass ? ` class="${clipRectClass}"` : '';
1554
- const rectIdAttr = clipRectId ? ` id="${clipRectId}"` : '';
1555
- const txtIdAttr = textId ? ` id="${textId}"` : '';
1556
- const txtClassAttr = textClass ? ` class="${textClass}"` : '';
1557
- return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 200" fill="none"${classAttr}${styleAttr}>
1558
- ${paths}
1559
- <clipPath id="${clipId}">
1560
- <rect${rectIdAttr} x="37" y="148" width="0" height="20"${rectClassAttr}/>
1561
- </clipPath>
1562
- <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})"/>
1563
- <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>
1564
- <defs>
1565
- ${defs}
1566
- </defs>
1567
- </svg>`;
1568
- }
1569
-
1570
1503
  /**
1571
1504
  * Build the loading scene variant of the logo SVG.
1572
1505
  * Uses unique IDs (prefixed with 'ls') to avoid collisions with CSSPreloader.
@@ -1835,120 +1768,6 @@ class LoadingScene extends Scene {
1835
1768
  }
1836
1769
  }
1837
1770
 
1838
- const PRELOADER_ID = '__ge-css-preloader__';
1839
- /**
1840
- * Inline SVG logo with animated loader bar.
1841
- * The `#loader` path acts as the progress fill — animated via clipPath.
1842
- */
1843
- const LOGO_SVG = buildLogoSVG({
1844
- idPrefix: 'pl',
1845
- svgClass: 'ge-logo-svg',
1846
- clipRectClass: 'ge-clip-rect',
1847
- textClass: 'ge-preloader-svg-text',
1848
- });
1849
- /**
1850
- * Creates a lightweight CSS-only preloader that appears instantly,
1851
- * BEFORE PixiJS/WebGL is initialized.
1852
- *
1853
- * Displays the Energy8 logo SVG with an animated loader bar.
1854
- */
1855
- function createCSSPreloader(container, config) {
1856
- if (document.getElementById(PRELOADER_ID))
1857
- return;
1858
- const bgColor = typeof config?.backgroundColor === 'string'
1859
- ? config.backgroundColor
1860
- : typeof config?.backgroundColor === 'number'
1861
- ? `#${config.backgroundColor.toString(16).padStart(6, '0')}`
1862
- : '#0a0a1a';
1863
- const bgGradient = config?.backgroundGradient ?? `linear-gradient(135deg, ${bgColor} 0%, #1a1a3e 100%)`;
1864
- const customHTML = config?.cssPreloaderHTML ?? '';
1865
- const el = document.createElement('div');
1866
- el.id = PRELOADER_ID;
1867
- el.innerHTML = customHTML || `
1868
- <div class="ge-preloader-content">
1869
- ${LOGO_SVG}
1870
- </div>
1871
- `;
1872
- const style = document.createElement('style');
1873
- style.textContent = `
1874
- #${PRELOADER_ID} {
1875
- position: absolute;
1876
- top: 0; left: 0;
1877
- width: 100%; height: 100%;
1878
- background: ${bgGradient};
1879
- display: flex;
1880
- align-items: center;
1881
- justify-content: center;
1882
- z-index: 10000;
1883
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
1884
- transition: opacity 0.4s ease-out;
1885
- }
1886
-
1887
- #${PRELOADER_ID}.ge-preloader-hidden {
1888
- opacity: 0;
1889
- pointer-events: none;
1890
- }
1891
-
1892
- .ge-preloader-content {
1893
- display: flex;
1894
- flex-direction: column;
1895
- align-items: center;
1896
- width: 80%;
1897
- max-width: 700px;
1898
- }
1899
-
1900
- .ge-logo-svg {
1901
- width: 100%;
1902
- height: auto;
1903
- filter: drop-shadow(0 0 30px rgba(121, 57, 194, 0.4));
1904
- }
1905
-
1906
- /* Animate the loader clip-rect to shimmer while waiting */
1907
- .ge-clip-rect {
1908
- animation: ge-loader-fill 2s ease-in-out infinite;
1909
- }
1910
-
1911
- @keyframes ge-loader-fill {
1912
- 0% { width: 0; }
1913
- 50% { width: 174; }
1914
- 100% { width: 0; }
1915
- }
1916
-
1917
- /* Animate the SVG text opacity */
1918
- .ge-preloader-svg-text {
1919
- animation: ge-pulse 1.5s ease-in-out infinite;
1920
- }
1921
-
1922
- @keyframes ge-pulse {
1923
- 0%, 100% { opacity: 0.4; }
1924
- 50% { opacity: 1; }
1925
- }
1926
- `;
1927
- container.style.position = container.style.position || 'relative';
1928
- container.appendChild(style);
1929
- container.appendChild(el);
1930
- }
1931
- /**
1932
- * Remove the CSS preloader with a smooth fade-out transition.
1933
- */
1934
- function removeCSSPreloader(container) {
1935
- const el = document.getElementById(PRELOADER_ID);
1936
- if (!el)
1937
- return;
1938
- el.classList.add('ge-preloader-hidden');
1939
- // Remove after transition
1940
- el.addEventListener('transitionend', () => {
1941
- el.remove();
1942
- // Also remove the style element
1943
- const styles = container.querySelectorAll('style');
1944
- for (const style of styles) {
1945
- if (style.textContent?.includes(PRELOADER_ID)) {
1946
- style.remove();
1947
- }
1948
- }
1949
- });
1950
- }
1951
-
1952
1771
  /**
1953
1772
  * FPS overlay for debugging performance.
1954
1773
  *
@@ -2090,6 +1909,8 @@ class GameApplication extends EventEmitter {
2090
1909
  fpsOverlay = null;
2091
1910
  /** Data received from SDK initialization */
2092
1911
  initData = null;
1912
+ /** Platform session (SDK + optional DevBridge). null until start() runs. */
1913
+ platformSession = null;
2093
1914
  /** Configuration */
2094
1915
  config;
2095
1916
  // ─── Private state ──────────────────────────────────────
@@ -2183,7 +2004,7 @@ class GameApplication extends EventEmitter {
2183
2004
  this.input?.destroy();
2184
2005
  this.audio?.destroy();
2185
2006
  this.viewport?.destroy();
2186
- this.sdk?.destroy();
2007
+ this.platformSession?.destroy();
2187
2008
  this.app?.destroy(true, { children: true, texture: true });
2188
2009
  this.emit('destroyed');
2189
2010
  this.removeAllListeners();
@@ -2219,20 +2040,15 @@ class GameApplication extends EventEmitter {
2219
2040
  this.app.canvas.style.height = '100%';
2220
2041
  }
2221
2042
  async initSDK() {
2222
- if (this.config.sdk === false) {
2223
- // Offline / development mode no SDK
2224
- this.initData = null;
2225
- return;
2226
- }
2227
- const sdkOpts = typeof this.config.sdk === 'object' ? this.config.sdk : {};
2228
- this.sdk = new CasinoGameSDK(sdkOpts);
2229
- // Perform the handshake
2230
- this.initData = await this.sdk.ready();
2231
- // Forward SDK events
2232
- this.sdk.on('error', (err) => {
2043
+ // Delegate the SDK handshake (and any optional in-process DevBridge
2044
+ // wiring) to platform-core. The session forwards SDK events upward.
2045
+ this.platformSession = await createPlatformSession({ sdk: this.config.sdk });
2046
+ this.sdk = this.platformSession.sdk;
2047
+ this.initData = this.platformSession.initData;
2048
+ this.platformSession.on('error', (err) => {
2233
2049
  this.emit('error', err);
2234
2050
  });
2235
- this.sdk.on('balanceUpdate', (data) => {
2051
+ this.platformSession.on('balanceUpdate', (data) => {
2236
2052
  this.emit('balanceUpdate', data);
2237
2053
  });
2238
2054
  }
@@ -2944,24 +2760,29 @@ function measureChild(child, parentContentW = 0, parentContentH = 0) {
2944
2760
  const cfg = child._flexConfig;
2945
2761
  const resolvedLW = resolveDimension(cfg?.layoutWidth, parentContentW);
2946
2762
  const resolvedLH = resolveDimension(cfg?.layoutHeight, parentContentH);
2947
- if (resolvedLW !== undefined && resolvedLH !== undefined) {
2948
- return { w: resolvedLW, h: resolvedLH, ox: 0, oy: 0 };
2949
- }
2950
- // For FlexContainers, use their explicit or computed size
2763
+ // FlexContainer children are top-left origin by construction, so `ox/oy = 0`
2764
+ // is correct and we can skip the `getLocalBounds()` call entirely.
2951
2765
  if (child instanceof FlexContainer) {
2952
2766
  const fc = child;
2953
2767
  const w = fc._explicitWidth > 0 ? fc._explicitWidth : (fc._computedWidth > 0 ? fc._computedWidth : undefined);
2954
2768
  const h = fc._explicitHeight > 0 ? fc._explicitHeight : (fc._computedHeight > 0 ? fc._computedHeight : undefined);
2955
2769
  if (w !== undefined && h !== undefined) {
2956
- return { w, h, ox: 0, oy: 0 };
2770
+ return { w: resolvedLW ?? w, h: resolvedLH ?? h, ox: 0, oy: 0 };
2957
2771
  }
2958
2772
  }
2959
- // Use localBounds to get the true visual extent and origin offset.
2960
- // This handles children with non-zero anchors (e.g. Button, Label with centered text).
2773
+ // Use localBounds to get the true visual origin offset.
2774
+ // We take `ox/oy` from bounds unconditionally a user-supplied `layoutWidth/Height`
2775
+ // overrides the SIZE used by flex (protection against pre-paint bounds of 0), but
2776
+ // it must NOT erase the center-anchor compensation: Button/Label views are drawn at
2777
+ // `(-w/2..w/2)` and their bounds.x = -w/2 is what lets `layoutLine` place the
2778
+ // visible rectangle where the layout intends.
2961
2779
  const bounds = child.getLocalBounds();
2962
- const w = resolvedLW ?? bounds.width;
2963
- const h = resolvedLH ?? bounds.height;
2964
- return { w, h, ox: bounds.x, oy: bounds.y };
2780
+ return {
2781
+ w: resolvedLW ?? bounds.width,
2782
+ h: resolvedLH ?? bounds.height,
2783
+ ox: bounds.x,
2784
+ oy: bounds.y,
2785
+ };
2965
2786
  }
2966
2787
  /**
2967
2788
  * Set a child's main or cross dimension.
@@ -5229,236 +5050,5 @@ class ScrollContainer extends Container {
5229
5050
  }
5230
5051
  }
5231
5052
 
5232
- const DEFAULT_CONFIG = {
5233
- balance: 10000,
5234
- currency: 'USD',
5235
- gameConfig: {
5236
- id: 'dev-game',
5237
- type: 'slot',
5238
- version: '1.0.0',
5239
- viewport: { width: 1920, height: 1080 },
5240
- betLevels: [0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 50],
5241
- },
5242
- assetsUrl: '/assets/',
5243
- session: null,
5244
- onPlay: () => ({}),
5245
- networkDelay: 200,
5246
- debug: true,
5247
- };
5248
- /**
5249
- * Mock host bridge for local development.
5250
- *
5251
- * Uses the SDK's `Bridge` class in `devMode` to communicate with
5252
- * `CasinoGameSDK` via a shared in-memory `MemoryChannel`, removing
5253
- * the need for postMessage and iframes.
5254
- *
5255
- * When `luaScript` is set, play requests are sent to the Vite dev server
5256
- * which runs LuaEngine in Node.js — no fengari in the browser.
5257
- *
5258
- * @example
5259
- * ```ts
5260
- * import { DevBridge } from '@energy8platform/game-engine/debug';
5261
- *
5262
- * const devBridge = new DevBridge({
5263
- * balance: 5000,
5264
- * currency: 'EUR',
5265
- * gameConfig: { id: 'my-slot', type: 'slot', betLevels: [0.2, 0.5, 1, 2] },
5266
- * onPlay: ({ action, bet }) => ({
5267
- * totalWin: Math.random() > 0.5 ? bet * (Math.random() * 20) : 0,
5268
- * data: { matrix: [[1,2,3],[4,5,6],[7,8,9]] },
5269
- * }),
5270
- * });
5271
- * devBridge.start();
5272
- * ```
5273
- */
5274
- class DevBridge {
5275
- _config;
5276
- _balance;
5277
- _roundCounter = 0;
5278
- _bridge = null;
5279
- _useLuaServer;
5280
- constructor(config = {}) {
5281
- this._config = { ...DEFAULT_CONFIG, ...config };
5282
- this._balance = this._config.balance;
5283
- this._useLuaServer = !!(this._config.luaScript && this._config.gameDefinition);
5284
- }
5285
- /** Current mock balance */
5286
- get balance() {
5287
- return this._balance;
5288
- }
5289
- /** Start listening for SDK messages */
5290
- start() {
5291
- if (this._bridge)
5292
- return;
5293
- console.debug('[DevBridge] Starting with config:', this._config);
5294
- this._bridge = new Bridge({ devMode: true, debug: this._config.debug });
5295
- this._bridge.on('GAME_READY', (_payload, id) => {
5296
- this.handleGameReady(id);
5297
- });
5298
- this._bridge.on('PLAY_REQUEST', (payload, id) => {
5299
- this.handlePlayRequest(payload, id);
5300
- });
5301
- this._bridge.on('PLAY_RESULT_ACK', (payload) => {
5302
- this.handlePlayAck(payload);
5303
- });
5304
- this._bridge.on('GET_BALANCE', (_payload, id) => {
5305
- this.handleGetBalance(id);
5306
- });
5307
- this._bridge.on('GET_STATE', (_payload, id) => {
5308
- this.handleGetState(id);
5309
- });
5310
- this._bridge.on('OPEN_DEPOSIT', () => {
5311
- this.handleOpenDeposit();
5312
- });
5313
- if (this._config.debug) {
5314
- const mode = this._useLuaServer ? 'Lua (server-side)' : 'onPlay callback';
5315
- console.log(`[DevBridge] Started — mode: ${mode}`);
5316
- }
5317
- }
5318
- /** Stop listening */
5319
- stop() {
5320
- if (this._bridge) {
5321
- this._bridge.destroy();
5322
- this._bridge = null;
5323
- }
5324
- if (this._config.debug) {
5325
- console.log('[DevBridge] Stopped');
5326
- }
5327
- }
5328
- /** Set mock balance */
5329
- setBalance(balance) {
5330
- this._balance = balance;
5331
- this._bridge?.send('BALANCE_UPDATE', { balance: this._balance });
5332
- }
5333
- /** Destroy the dev bridge */
5334
- destroy() {
5335
- this.stop();
5336
- }
5337
- // ─── Message Handling ──────────────────────────────────
5338
- handleGameReady(id) {
5339
- const initData = {
5340
- balance: this._balance,
5341
- currency: this._config.currency,
5342
- config: this._config.gameConfig,
5343
- session: this._config.session,
5344
- assetsUrl: this._config.assetsUrl,
5345
- };
5346
- this.delayedSend('INIT', initData, id);
5347
- }
5348
- handlePlayRequest(payload, id) {
5349
- const { action, bet, roundId, params } = payload;
5350
- this._roundCounter++;
5351
- if (this._useLuaServer) {
5352
- // Debit bet (server deducts before Lua execution)
5353
- // For session actions (free spins), debit is 0 — LuaEngine handles bet from session
5354
- this._balance -= bet;
5355
- this.executeLuaOnServer({ action, bet, roundId, params })
5356
- .then((result) => {
5357
- this._bridge?.send('PLAY_RESULT', result, id);
5358
- })
5359
- .catch((err) => {
5360
- console.error('[DevBridge] Lua server error:', err);
5361
- this._balance += bet;
5362
- this._bridge?.send('PLAY_RESULT', this.buildFallbackResult(action, bet, roundId), id);
5363
- });
5364
- }
5365
- else {
5366
- // Fallback to onPlay callback
5367
- const customResult = this._config.onPlay({ action, bet, roundId, params });
5368
- const totalWin = customResult.totalWin ?? (Math.random() > 0.6 ? bet * (1 + Math.random() * 10) : 0);
5369
- this._balance += totalWin;
5370
- const result = {
5371
- roundId: roundId ?? `dev-round-${this._roundCounter}`,
5372
- action,
5373
- balanceAfter: this._balance,
5374
- totalWin: Math.round(totalWin * 100) / 100,
5375
- data: customResult.data ?? {},
5376
- nextActions: customResult.nextActions ?? ['spin'],
5377
- session: customResult.session ?? null,
5378
- creditPending: false,
5379
- bonusFreeSpin: customResult.bonusFreeSpin ?? null,
5380
- currency: this._config.currency,
5381
- gameId: this._config.gameConfig?.id ?? 'dev-game',
5382
- };
5383
- this.delayedSend('PLAY_RESULT', result, id);
5384
- }
5385
- }
5386
- async executeLuaOnServer(params) {
5387
- const response = await fetch('/__lua-play', {
5388
- method: 'POST',
5389
- headers: { 'Content-Type': 'application/json' },
5390
- body: JSON.stringify(params),
5391
- });
5392
- if (!response.ok) {
5393
- const err = await response.json().catch(() => ({ error: 'Unknown error' }));
5394
- throw new Error(err.error ?? `HTTP ${response.status}`);
5395
- }
5396
- const luaResult = await response.json();
5397
- // Server credit logic:
5398
- // shouldCredit = (no session) OR (session.completed)
5399
- // creditAmount = result.totalWin
5400
- const shouldCredit = !luaResult.session || luaResult.session.completed;
5401
- if (shouldCredit && luaResult.totalWin > 0) {
5402
- this._balance += luaResult.totalWin;
5403
- }
5404
- return {
5405
- roundId: params.roundId ?? `dev-round-${this._roundCounter}`,
5406
- action: params.action,
5407
- balanceAfter: this._balance,
5408
- totalWin: Math.round(luaResult.totalWin * 100) / 100,
5409
- data: luaResult.data,
5410
- nextActions: luaResult.nextActions,
5411
- session: luaResult.session,
5412
- creditPending: !shouldCredit,
5413
- bonusFreeSpin: null,
5414
- currency: this._config.currency,
5415
- gameId: this._config.gameConfig?.id ?? 'dev-game',
5416
- };
5417
- }
5418
- buildFallbackResult(action, bet, roundId) {
5419
- return {
5420
- roundId: roundId ?? `dev-round-${this._roundCounter}`,
5421
- action,
5422
- balanceAfter: this._balance,
5423
- totalWin: 0,
5424
- data: { error: 'Lua execution failed' },
5425
- nextActions: ['spin'],
5426
- session: null,
5427
- creditPending: false,
5428
- bonusFreeSpin: null,
5429
- currency: this._config.currency,
5430
- gameId: this._config.gameConfig?.id ?? 'dev-game',
5431
- };
5432
- }
5433
- handlePlayAck(_payload) {
5434
- if (this._config.debug) {
5435
- console.log('[DevBridge] Play acknowledged');
5436
- }
5437
- }
5438
- handleGetBalance(id) {
5439
- this.delayedSend('BALANCE_UPDATE', { balance: this._balance }, id);
5440
- }
5441
- handleGetState(id) {
5442
- this.delayedSend('STATE_RESPONSE', { session: this._config.session ?? null }, id);
5443
- }
5444
- handleOpenDeposit() {
5445
- if (this._config.debug) {
5446
- console.log('[DevBridge] Open deposit requested (mock: adding 1000)');
5447
- }
5448
- this._balance += 1000;
5449
- this._bridge?.send('BALANCE_UPDATE', { balance: this._balance });
5450
- }
5451
- // ─── Communication ─────────────────────────────────────
5452
- delayedSend(type, payload, id) {
5453
- const delay = this._config.networkDelay;
5454
- if (delay > 0) {
5455
- setTimeout(() => this._bridge?.send(type, payload, id), delay);
5456
- }
5457
- else {
5458
- this._bridge?.send(type, payload, id);
5459
- }
5460
- }
5461
- }
5462
-
5463
- export { AssetManager, AudioManager, BalanceDisplay, Button, DevBridge, Easing, EventEmitter, FPSOverlay, FlexContainer, GameApplication, InputManager, Label, Layout, LoadingScene, Modal, Orientation, Panel, ProgressBar, ScaleMode, Scene, SceneManager, ScrollContainer, SpineHelper, SpriteAnimation, StateMachine, Timeline, Toast, TransitionType, Tween, ViewportManager, WinDisplay };
5053
+ export { AssetManager, AudioManager, BalanceDisplay, Button, Easing, EventEmitter, FPSOverlay, FlexContainer, GameApplication, InputManager, Label, Layout, LoadingScene, Modal, Orientation, Panel, ProgressBar, ScaleMode, Scene, SceneManager, ScrollContainer, SpineHelper, SpriteAnimation, StateMachine, Timeline, Toast, TransitionType, Tween, ViewportManager, WinDisplay };
5464
5054
  //# sourceMappingURL=index.esm.js.map