@energy8platform/game-engine 0.27.1 → 0.28.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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  A casino game engine built on [PixiJS v8](https://pixijs.com/) and [@energy8platform/game-sdk](https://github.com/energy8platform/game-sdk). Provides scene management, responsive scaling, audio, state machines, tweens, UI components, and React integration for developing slot machines, card games, and other iGaming titles.
4
4
 
5
- > **Building on a different renderer?** The platform-specific bits (Lua engine, RTP simulation, DevBridge, branded loading screen, SDK session orchestration) live in the renderer-agnostic [`@energy8platform/platform-core`](../platform-core/README.md). `game-engine` is the Pixi front-end on top of it; Phaser, Three.js, and custom-engine consumers depend only on `platform-core`.
5
+ > **Building on a different renderer?** The platform-specific bits (SpinML math runtime, RTP simulation, DevBridge, branded loading screen, SDK session orchestration) live in the renderer-agnostic [`@energy8platform/platform-core`](../platform-core/README.md). `game-engine` is the Pixi front-end on top of it; Phaser, Three.js, and custom-engine consumers depend only on `platform-core`.
6
6
 
7
7
  ---
8
8
 
@@ -23,7 +23,7 @@ A casino game engine built on [PixiJS v8](https://pixijs.com/) and [@energy8plat
23
23
  - [UI Components](#ui-components)
24
24
  - [Input](#input)
25
25
  - [Vite Configuration](#vite-configuration)
26
- - [Lua Engine](#lua-engine)
26
+ - [Game Math (SpinML)](#game-math-spinml)
27
27
  - [DevBridge](#devbridge)
28
28
  - [React Integration](#react-integration)
29
29
  - [Debug](#debug)
@@ -42,9 +42,11 @@ npm install pixi.js @energy8platform/game-sdk @energy8platform/game-engine
42
42
  npm install @pixi/sound # Audio
43
43
  npm install @esotericsoftware/spine-pixi-v8 # Spine animations
44
44
  npm install react react-dom react-reconciler # React integration
45
- npm install fengari # Lua engine (in dev/sim)
46
45
  ```
47
46
 
47
+ The SpinML engine binaries (`e8`, `e8-server`) come with `platform-core`'s
48
+ postinstall — no extra install step for math/dev/simulation.
49
+
48
50
  ```typescript
49
51
  // src/main.ts
50
52
  import { GameApplication, ScaleMode } from '@energy8platform/game-engine';
@@ -95,7 +97,6 @@ bootstrap();
95
97
  | `@esotericsoftware/spine-pixi-v8` | `~4.2.0` | Optional — Spine animations |
96
98
  | `react`, `react-dom` | `>=18.0.0` | Optional — ReactScene |
97
99
  | `react-reconciler` | `>=0.29.0` | Optional — ReactScene (custom PixiJS reconciler) |
98
- | `fengari` | `^0.1.4` | Optional — Lua engine (peer of `platform-core`, install only if you use Lua locally) |
99
100
 
100
101
  `@energy8platform/platform-core` is a regular dependency (not a peer) — npm pulls it in automatically when you install `game-engine`.
101
102
 
@@ -111,8 +112,8 @@ import { Tween, Timeline, Easing, SpriteAnimation } from '@energy8platform/game-
111
112
  import { createReelSystem, resolveReelConfig, PRESETS } from '@energy8platform/game-engine/slot';
112
113
  import { DevBridge, FPSOverlay } from '@energy8platform/game-engine/debug';
113
114
  import { ReactScene, extendPixiElements, extendUIElements, useSDK, useViewport } from '@energy8platform/game-engine/react';
114
- import { defineGameConfig } from '@energy8platform/game-engine/vite';
115
- import { LuaEngine, ActionRouter } from '@energy8platform/game-engine/lua';
115
+ import { defineGameConfig, spinPlugin } from '@energy8platform/game-engine/vite';
116
+ import type { GameDefinition, ActionDefinition } from '@energy8platform/game-engine/lua'; // types only
116
117
  ```
117
118
 
118
119
  ---
@@ -399,7 +400,7 @@ SpineHelper.setSkin(spine, 'warrior');
399
400
 
400
401
  ## Slot Reels
401
402
 
402
- A fully-configurable reel system driven by a single typed `ReelSystemConfig`. Import from `@energy8platform/game-engine/slot`. **Presentation only** — the engine draws the boards you feed it; the outcome (math) always comes from your Lua/server.
403
+ A fully-configurable reel system driven by a single typed `ReelSystemConfig`. Import from `@energy8platform/game-engine/slot`. **Presentation only** — the engine draws the boards you feed it; the outcome (math) always comes from your `.spin` script / server.
403
404
 
404
405
  > Try every knob interactively in the **[reel-lab playground](../../examples/reel-lab/)** — it tweaks the whole config live, triggers every feature, and copies the resulting config (TS/JSON) into your game.
405
406
 
@@ -832,67 +833,46 @@ export default defineGameConfig({
832
833
 
833
834
  ---
834
835
 
835
- ## Lua Engine
836
+ ## Game Math (SpinML)
836
837
 
837
- Runs platform Lua game scripts in the browser via `fengari` (Lua 5.3, pure JS). Replicates server-side execution for development — no backend required.
838
+ Game logic is a `.spin` script a statically-typed, Lua-flavored DSL
839
+ JIT-compiled to native code by the `e8` engine. In dev the Vite `spinPlugin`
840
+ (injected by `defineGameConfig({ devBridge: true })`) spawns `e8-server`
841
+ with hot reload; the same script runs in RTP simulation and in production.
838
842
 
839
- > The Lua engine, simulation runners, and game-definition types ship from [`@energy8platform/platform-core`](../platform-core/README.md). `game-engine` re-exports them at `@energy8platform/game-engine/lua` so existing import paths keep working.
843
+ > The runtime, the Vite plugins, and the game-definition types ship from
844
+ > [`@energy8platform/platform-core`](../platform-core/README.md) (a
845
+ > transitive dep). `game-engine` re-exports `/vite` and the legacy `/lua`
846
+ > **types** so existing import paths keep working. The fengari Lua engine
847
+ > was removed in 0.28 — legacy Lua games stay on ≤ 0.27.x; see the
848
+ > [migration guide](../../docs/lua-to-spin-migration.md).
849
+ >
850
+ > Language reference: [docs/spinml.md](../../docs/spinml.md).
840
851
 
841
852
  ### DevBridge Integration (recommended)
842
853
 
843
854
  ```typescript
844
- // dev.config.ts
845
- import luaScript from './game.lua?raw';
855
+ // dev.config.ts — the field is still named luaScript: for the client
856
+ // DevBridge it is only the "play via POST /__lua-play" marker (the route
857
+ // is served by e8-server through spinPlugin).
858
+ import mathScript from './src/game/script.spin?raw';
859
+ import gameDefinition from './src/game/config.json';
846
860
 
847
861
  export default {
848
862
  balance: 5000,
849
863
  currency: 'USD',
850
- luaScript,
851
- gameDefinition: {
852
- id: 'my-slot',
853
- type: 'SLOT',
854
- actions: {
855
- spin: {
856
- stage: 'base_game', debit: 'bet', credit: 'win',
857
- transitions: [
858
- { condition: 'free_spins_awarded > 0', creates_session: true, next_actions: ['free_spin'] },
859
- { condition: 'always', next_actions: ['spin'] },
860
- ],
861
- },
862
- free_spin: { stage: 'free_spins', debit: 'none', requires_session: true,
863
- transitions: [{ condition: 'always', next_actions: ['free_spin'] }],
864
- },
865
- },
866
- bet_levels: [0.2, 0.5, 1, 2, 5],
867
- },
864
+ luaScript: mathScript,
865
+ gameDefinition,
868
866
  };
869
867
  ```
870
868
 
871
- ### Standalone Usage
872
-
873
- ```typescript
874
- const engine = new LuaEngine({ script: luaSource, gameDefinition, seed: 42 });
875
- const result = engine.execute({ action: 'spin', bet: 1.0 });
876
- // result: { totalWin, data, nextActions, session }
877
- engine.destroy();
878
- ```
879
-
880
- ### Platform API (`engine.*` in Lua)
881
-
882
- | Function | Description |
883
- | --- | --- |
884
- | `engine.random(min, max)` | Random integer `[min, max]` |
885
- | `engine.random_float()` | Random float `[0.0, 1.0)` |
886
- | `engine.random_weighted(weights)` | 1-based index from weight table |
887
- | `engine.shuffle(arr)` | Fisher-Yates shuffle, returns copy |
888
- | `engine.log(level, msg)` | Log (`"debug"`, `"info"`, `"warn"`, `"error"`) |
889
- | `engine.get_config()` | Returns `{id, type, bet_levels}` |
890
-
891
- **Features:** Action routing, transition evaluation (`>`, `>=`, `==`, `!=`, `&&`, `||`, `"always"`), session management (free spins, retriggers), cross-spin persistent state, max win cap, buy bonus, deterministic seeded PRNG (xoshiro128**).
869
+ Actions, costs, and session transitions are declared inside the `.spin`
870
+ (`action spin { … opens = free_spin count free_spins_awarded }`), not in the
871
+ dev config — the scaffolded `config.json` is derived from `game.spec.ts`.
892
872
 
893
873
  ### RTP Simulation (CLI)
894
874
 
895
- Run the same Lua script from `dev.config.ts` through millions of iterations to verify math. The CLI ships with [`@energy8platform/platform-core`](../platform-core/README.md) (a transitive dep when you install `game-engine`):
875
+ Run the same `.spin` from `dev.config.ts` through millions of iterations to verify math (wraps `e8 simulate`; for the publishable book pipeline use `e8-math` from `stake-math-tools`):
896
876
 
897
877
  ```bash
898
878
  # Regular spins (1M iterations, default)
@@ -901,25 +881,21 @@ npx platform-core-simulate
901
881
  # Buy bonus simulation
902
882
  npx platform-core-simulate --action buy_bonus
903
883
 
904
- # Ante bet (v5: regular action, not a params flag)
884
+ # Ante bet (a regular action, not a params flag)
905
885
  npx platform-core-simulate --action ante_spin
906
886
 
907
887
  # Custom parameters
908
- npx platform-core-simulate --iterations 5000000 --bet 1 --seed 42 --config ./dev.config.ts
888
+ npx platform-core-simulate --iterations 5000000 --bet 1 --seed <hex> --config ./dev.config.ts
909
889
  ```
910
890
 
911
- Output matches the platform's server-side simulation format:
891
+ Output matches the old server-side simulation format, plus volatility and win-distribution sections:
912
892
 
913
893
  ```
914
- Starting simulation for my-slot (1000000 iterations, action: spin)...
915
- Progress: 100000/1000000 (10%)
916
- ...
917
-
918
894
  --- Simulation Results ---
919
895
  Game: my-slot
920
896
  Action: spin
921
897
  Iterations: 1,000,000
922
- Duration: 45.2s
898
+ Duration: 0.3s
923
899
  Total RTP: 96.48%
924
900
  Base Game RTP: 72.31%
925
901
  Bonus RTP: 24.17%
@@ -928,24 +904,28 @@ Max Win: 5234.50x
928
904
  Max Win Hits: 3 (rounds capped by max_win)
929
905
  Bonus Triggered: 4,521 (1 in 221 spins)
930
906
  Bonus Spins Played: 52,847
907
+ --- Volatility ---
908
+ Volatility: 6/10 (Medium-High) StdDev: 12.40 CV: 12.85
931
909
  ```
932
910
 
933
- The CLI reads `luaScript` and `gameDefinition` from your `dev.config.ts` — the same config used for DevBridge. Programmatic usage:
911
+ The CLI reads the script and `gameDefinition` from your `dev.config.ts` — the same config used for DevBridge. Programmatic usage:
934
912
 
935
913
  ```typescript
936
- import { SimulationRunner, formatSimulationResult } from '@energy8platform/game-engine/lua';
914
+ import { NativeSimulationRunner, findE8Binary } from '@energy8platform/platform-core/simulation';
937
915
 
938
- const runner = new SimulationRunner({
939
- script: luaSource,
916
+ const runner = new NativeSimulationRunner({
917
+ binaryPath: findE8Binary()!,
918
+ argsPrefix: ['simulate'],
919
+ script: spinSource,
920
+ scriptExt: 'spin',
940
921
  gameDefinition,
941
922
  iterations: 1_000_000,
942
923
  bet: 1.0,
943
- seed: 42,
944
- onProgress: (done, total) => console.log(`${done}/${total}`),
924
+ rng: 'provably-fair',
945
925
  });
946
926
 
947
- const result = runner.run();
948
- console.log(formatSimulationResult(result));
927
+ const result = await runner.run();
928
+ console.log(formatNativeResult(result));
949
929
  ```
950
930
 
951
931
  ---
@@ -980,7 +960,7 @@ bridge.destroy();
980
960
 
981
961
  > With the Vite plugin (`devBridge: true`), DevBridge is injected automatically before your app entry point.
982
962
 
983
- > **Error codes & session contract.** In Lua mode, DevBridge emits the same `PLAY_ERROR` codes the platform does (`INVALID_AMOUNT`, `INSUFFICIENT_FUNDS`, `ACTIVE_SESSION_EXISTS`, `NO_ACTIVE_SESSION`, `SESSION_EXPIRED`, `ENGINE_ERROR`, …) — see the [platform-core DevBridge section](../platform-core/README.md#platform-parity-behavior-lua-mode) for the full table and the contract details (server-generated round IDs, `session.history`, `STATE_RESPONSE` shape, `creditPending` semantics).
963
+ > **Error codes & session contract.** In server mode, DevBridge emits the same `PLAY_ERROR` codes the platform does (`INVALID_AMOUNT`, `INSUFFICIENT_FUNDS`, `ACTIVE_SESSION_EXISTS`, `NO_ACTIVE_SESSION`, `SESSION_EXPIRED`, `ENGINE_ERROR`, …) — see the [platform-core DevBridge section](../platform-core/README.md#platform-parity-behavior-server-mode) for the full table and the contract details (server-generated round IDs, `session.history`, `STATE_RESPONSE` shape, `creditPending` semantics).
984
964
 
985
965
  ---
986
966
 
package/dist/host.cjs.js CHANGED
@@ -2286,8 +2286,18 @@ async function createSlotGame(opts) {
2286
2286
  let stakeBridge = null;
2287
2287
  let isStakeNow = false;
2288
2288
  if (opts.stake) {
2289
- const { isStakeLaunch } = await import('@energy8platform/stake-bridge/detect');
2290
- isStakeNow = isStakeLaunch(location.href);
2289
+ const { classifyStakeLaunch } = await import('@energy8platform/stake-bridge/detect');
2290
+ // Security gate: a launch carrying Stake session markers (sessionID / replay) MUST also carry a
2291
+ // valid rgs_url. If the rgs_url was removed, blanked, or tampered to a non-Stake host, refuse to
2292
+ // run — WITHOUT this the launch would fail the Stake check and silently fall through to the
2293
+ // offline/dev bridge, letting the player spin for free. 'stake' = a valid launch (load the
2294
+ // bridge); 'offline' = a genuine non-Stake/dev launch (no session markers at all).
2295
+ const launch = classifyStakeLaunch(location.href);
2296
+ if (launch === 'blocked') {
2297
+ fatal('Invalid game server address. Please relaunch the game from the lobby.');
2298
+ throw new Error('createSlotGame: refusing to run — Stake launch with a missing or invalid rgs_url');
2299
+ }
2300
+ isStakeNow = launch === 'stake';
2291
2301
  if (isStakeNow) {
2292
2302
  try {
2293
2303
  const { StakeBridge } = await import('@energy8platform/stake-bridge');