@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 +49 -69
- package/dist/host.cjs.js +12 -2
- package/dist/host.cjs.js.map +1 -1
- package/dist/host.esm.js +12 -2
- package/dist/host.esm.js.map +1 -1
- package/dist/vite.cjs.js +7 -6
- package/dist/vite.cjs.js.map +1 -1
- package/dist/vite.d.ts +5 -1
- package/dist/vite.esm.js +7 -6
- package/dist/vite.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/host/createSlotGame.ts +12 -2
- package/src/index.ts +2 -2
- package/src/vite/index.ts +15 -6
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 (
|
|
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
|
-
- [
|
|
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 {
|
|
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
|
|
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
|
-
##
|
|
836
|
+
## Game Math (SpinML)
|
|
836
837
|
|
|
837
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
872
|
-
|
|
873
|
-
|
|
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
|
|
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 (
|
|
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
|
|
888
|
+
npx platform-core-simulate --iterations 5000000 --bet 1 --seed <hex> --config ./dev.config.ts
|
|
909
889
|
```
|
|
910
890
|
|
|
911
|
-
Output matches the
|
|
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:
|
|
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
|
|
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 {
|
|
914
|
+
import { NativeSimulationRunner, findE8Binary } from '@energy8platform/platform-core/simulation';
|
|
937
915
|
|
|
938
|
-
const runner = new
|
|
939
|
-
|
|
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
|
-
|
|
944
|
-
onProgress: (done, total) => console.log(`${done}/${total}`),
|
|
924
|
+
rng: 'provably-fair',
|
|
945
925
|
});
|
|
946
926
|
|
|
947
|
-
const result = runner.run();
|
|
948
|
-
console.log(
|
|
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
|
|
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 {
|
|
2290
|
-
|
|
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');
|