@energy8platform/platform-core 0.28.2 → 0.29.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 +127 -150
- package/bin/simulate.ts +35 -98
- package/dist/dev-bridge.cjs.js +3 -3
- package/dist/dev-bridge.cjs.js.map +1 -1
- package/dist/dev-bridge.d.ts +9 -2
- package/dist/dev-bridge.esm.js +3 -3
- package/dist/dev-bridge.esm.js.map +1 -1
- package/dist/game-spec.cjs.js +70 -27
- package/dist/game-spec.cjs.js.map +1 -1
- package/dist/game-spec.d.ts +47 -11
- package/dist/game-spec.esm.js +68 -25
- package/dist/game-spec.esm.js.map +1 -1
- package/dist/index.cjs.js +3 -3
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +28 -2
- package/dist/index.esm.js +3 -3
- package/dist/index.esm.js.map +1 -1
- package/dist/lua.cjs.js +0 -1234
- package/dist/lua.cjs.js.map +1 -1
- package/dist/lua.d.ts +8 -206
- package/dist/lua.esm.js +0 -1225
- package/dist/lua.esm.js.map +1 -1
- package/dist/simulation.cjs.js +48 -179
- package/dist/simulation.cjs.js.map +1 -1
- package/dist/simulation.d.ts +33 -60
- package/dist/simulation.esm.js +48 -178
- package/dist/simulation.esm.js.map +1 -1
- package/dist/vite.cjs.js +323 -109
- package/dist/vite.cjs.js.map +1 -1
- package/dist/vite.d.ts +19 -9
- package/dist/vite.esm.js +322 -109
- package/dist/vite.esm.js.map +1 -1
- package/package.json +6 -5
- package/scripts/install-e8.mjs +113 -0
- package/src/dev-bridge/DevBridge.ts +3 -3
- package/src/game-spec/defineGame.ts +2 -2
- package/src/game-spec/derive.ts +46 -17
- package/src/game-spec/export.ts +28 -8
- package/src/game-spec/index.ts +3 -2
- package/src/game-spec/types.ts +11 -1
- package/src/index.ts +6 -12
- package/src/lua/index.ts +4 -11
- package/src/lua/types.ts +7 -0
- package/src/simulation/NativeSimulationRunner.ts +71 -45
- package/src/simulation/index.ts +2 -4
- package/src/vite/index.ts +4 -121
- package/src/vite/spinPlugin.ts +338 -0
- package/scripts/install-simulate.mjs +0 -101
- package/src/lua/ActionRouter.ts +0 -132
- package/src/lua/LuaEngine.ts +0 -520
- package/src/lua/LuaEngineAPI.ts +0 -314
- package/src/lua/PersistentState.ts +0 -80
- package/src/lua/SessionManager.ts +0 -249
- package/src/lua/SimulationRunner.ts +0 -190
- package/src/lua/fengari.d.ts +0 -10
- package/src/simulation/ParallelSimulationRunner.ts +0 -156
- package/src/simulation/SimulationWorker.ts +0 -44
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @energy8platform/platform-core
|
|
2
2
|
|
|
3
|
-
Renderer-agnostic core for games on the Energy8 casino platform. Pair it with PixiJS, Phaser, Three.js, DOM, or your own engine — `platform-core` ships everything that is platform-specific (Energy8 SDK lifecycle,
|
|
3
|
+
Renderer-agnostic core for games on the Energy8 casino platform. Pair it with PixiJS, Phaser, Three.js, DOM, or your own engine — `platform-core` ships everything that is platform-specific (Energy8 SDK lifecycle, the SpinML math runtime, RTP simulation, mock host bridge for local dev, branded loading frame, Vite plugins) without dragging in a renderer.
|
|
4
4
|
|
|
5
5
|
If you want the full PixiJS engine on top of this, install [`@energy8platform/game-engine`](../game-engine/README.md) instead — it depends on `platform-core` and adds scenes, UI, animation, viewport, and React integration.
|
|
6
6
|
|
|
@@ -13,8 +13,8 @@ If you want the full PixiJS engine on top of this, install [`@energy8platform/ga
|
|
|
13
13
|
- [Quick Start](#quick-start)
|
|
14
14
|
- [Public API](#public-api)
|
|
15
15
|
- [PlatformSession](#platformsession)
|
|
16
|
-
- [Writing your game (
|
|
17
|
-
- [
|
|
16
|
+
- [Writing your game (spec + SpinML)](#writing-your-game-spec--spinml)
|
|
17
|
+
- [SpinML Runtime (e8)](#spinml-runtime-e8)
|
|
18
18
|
- [DevBridge (mock casino host)](#devbridge-mock-casino-host)
|
|
19
19
|
- [RTP Simulation CLI](#rtp-simulation-cli)
|
|
20
20
|
- [Branded Loading Screen](#branded-loading-screen)
|
|
@@ -29,7 +29,7 @@ If you want the full PixiJS engine on top of this, install [`@energy8platform/ga
|
|
|
29
29
|
|
|
30
30
|
## Why this package exists
|
|
31
31
|
|
|
32
|
-
The Energy8 casino platform has a contract every game must speak: an SDK handshake, a play-action lifecycle, a
|
|
32
|
+
The Energy8 casino platform has a contract every game must speak: an SDK handshake, a play-action lifecycle, a SpinML execution model used both server-side and locally for development and RTP verification, and a host-side branded loading frame.
|
|
33
33
|
|
|
34
34
|
That contract is identical regardless of how you render. So it lives here, with **zero rendering or DOM-coupled code** in the bundle (the only DOM API used is `window` in the dev-mode `MemoryChannel` and `document` in the CSS preloader — neither touches a canvas/WebGL).
|
|
35
35
|
|
|
@@ -40,15 +40,20 @@ You bring the renderer; `platform-core` brings the platform.
|
|
|
40
40
|
## Installation
|
|
41
41
|
|
|
42
42
|
```bash
|
|
43
|
-
npm install @energy8platform/platform-core @energy8platform/game-sdk
|
|
43
|
+
npm install @energy8platform/platform-core @energy8platform/game-sdk
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
+
Postinstall downloads the `e8` / `e8-server` engine binaries for your
|
|
47
|
+
platform from the game-engine repo's GitHub Releases (tag `e8-v<version>`)
|
|
48
|
+
into `platform-core/bin`. Overrides: `E8_BINARY` / `E8_SERVER_BINARY` (local
|
|
49
|
+
build), `E8_RELEASE_REPO` / `E8_DOWNLOAD_BASE` (mirror). The download is
|
|
50
|
+
non-fatal — without it, spin games need one of the overrides.
|
|
51
|
+
|
|
46
52
|
### Peer dependencies
|
|
47
53
|
|
|
48
54
|
| Package | Version | Required |
|
|
49
55
|
| --- | --- | --- |
|
|
50
56
|
| `@energy8platform/game-sdk` | `^2.7.0` | Yes |
|
|
51
|
-
| `fengari` | `^0.1.4` | Yes — Lua engine runtime |
|
|
52
57
|
| `vite` | `^5.0.0 \|\| ^6.0.0` | Optional — only if you import `/vite` |
|
|
53
58
|
|
|
54
59
|
No `pixi.js`, no `react`, no `phaser`, no DOM rendering library is required.
|
|
@@ -59,7 +64,10 @@ No `pixi.js`, no `react`, no `phaser`, no DOM rendering library is required.
|
|
|
59
64
|
|
|
60
65
|
```typescript
|
|
61
66
|
import { createPlatformSession, createCSSPreloader, removeCSSPreloader } from '@energy8platform/platform-core';
|
|
62
|
-
|
|
67
|
+
// The .spin math source. The DevBridge field is still called `luaScript` —
|
|
68
|
+
// for the client it is only the "play via POST /__lua-play" marker; the
|
|
69
|
+
// route is served by e8-server through the `spinPlugin` vite plugin.
|
|
70
|
+
import mathScript from './game/script.spin?raw';
|
|
63
71
|
import { gameDefinition } from './gameDefinition';
|
|
64
72
|
|
|
65
73
|
const container = document.getElementById('app')!;
|
|
@@ -70,7 +78,7 @@ createCSSPreloader(container);
|
|
|
70
78
|
// 2. Boot the platform session — DevBridge in dev, real SDK in prod.
|
|
71
79
|
const session = await createPlatformSession({
|
|
72
80
|
dev: {
|
|
73
|
-
luaScript,
|
|
81
|
+
luaScript: mathScript,
|
|
74
82
|
gameDefinition,
|
|
75
83
|
balance: 10000,
|
|
76
84
|
currency: 'EUR',
|
|
@@ -100,13 +108,10 @@ import {
|
|
|
100
108
|
createPlatformSession, PlatformSession,
|
|
101
109
|
type PlatformSessionConfig, type PlatformSessionEvents, type SDKOptions,
|
|
102
110
|
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
SimulationRunner, formatSimulationResult,
|
|
108
|
-
ParallelSimulationRunner,
|
|
109
|
-
NativeSimulationRunner, findNativeBinary, formatNativeResult,
|
|
111
|
+
// Simulation types (runtime classes live in the Node-only /simulation
|
|
112
|
+
// sub-path: NativeSimulationRunner, findE8Binary, formatNativeResult)
|
|
113
|
+
type NativeSimulationConfig, type NativeSimulationResult,
|
|
114
|
+
type StageStats, type DistributionBucket,
|
|
110
115
|
|
|
111
116
|
// DevBridge mock host
|
|
112
117
|
DevBridge, type DevBridgeConfig,
|
|
@@ -119,16 +124,21 @@ import {
|
|
|
119
124
|
// Internal utility
|
|
120
125
|
EventEmitter,
|
|
121
126
|
|
|
122
|
-
// Platform types (re-exported from @energy8platform/game-sdk +
|
|
127
|
+
// Platform types (re-exported from @energy8platform/game-sdk + legacy /lua types)
|
|
123
128
|
type InitData, type GameConfigData, type SessionData,
|
|
124
129
|
type PlayParams, type PlayResultData, type BalanceData,
|
|
125
130
|
type GameDefinition, type ActionDefinition, type TransitionRule,
|
|
126
|
-
type
|
|
131
|
+
type SessionConfig,
|
|
127
132
|
type BuyBonusConfig, type AnteBetConfig, type MaxWinConfig,
|
|
128
133
|
type AssetManifest, type AssetBundle, type AssetEntry,
|
|
129
134
|
type LoadingScreenConfig,
|
|
130
135
|
// …more — see src/types.ts
|
|
131
136
|
} from '@energy8platform/platform-core';
|
|
137
|
+
|
|
138
|
+
// Game-spec derivation (spec → SpinML prelude → platform bundle):
|
|
139
|
+
import {
|
|
140
|
+
defineGame, buildSpinScript, exportGameSpin,
|
|
141
|
+
} from '@energy8platform/platform-core/game-spec';
|
|
132
142
|
```
|
|
133
143
|
|
|
134
144
|
---
|
|
@@ -186,122 +196,100 @@ In dev, set up the recorded rounds via [`DevBridge` replay mode](#replay-mode-hi
|
|
|
186
196
|
const fs = await session.play({ action: 'free_spin', bet: triggeringBet, roundId: result.roundId });
|
|
187
197
|
```
|
|
188
198
|
|
|
189
|
-
The platform validates `bet` against `bet_levels` and rejects `bet: 0`. No double debit happens —
|
|
199
|
+
The platform validates `bet` against `bet_levels` and rejects `bet: 0`. No double debit happens — session actions don't debit, and the engine reads the actual session bet from server-side session state regardless of what the client sends. See [Game Development Guide §13.16](https://github.com/energy8platform/game-engine/blob/main/game_development_guide.md#13-conventions-and-best-practices) for the full conventions list.
|
|
190
200
|
|
|
191
201
|
---
|
|
192
202
|
|
|
193
|
-
## Writing your game (
|
|
203
|
+
## Writing your game (spec + SpinML)
|
|
194
204
|
|
|
195
|
-
Each game on the Energy8 platform consists of two
|
|
205
|
+
Each game on the Energy8 platform consists of two sources:
|
|
196
206
|
|
|
197
|
-
1. A
|
|
198
|
-
2. A **
|
|
207
|
+
1. A **game spec** (`src/game.spec.ts`, for scaffolded games) — symbols, paytable, bet levels, max win, modes, RTP targets. One source of truth; `GameDefinition`, the SpinML prelude, and the math-pipeline mode map are all derived from it via `/game-spec`.
|
|
208
|
+
2. A **SpinML script** (`.spin`) — a statically-typed, Lua-flavored DSL that owns *all* game math **and** declares its actions, costs, and session transitions. JIT-compiled to native code by the `e8` engine.
|
|
199
209
|
|
|
200
|
-
The same
|
|
210
|
+
The same `.spin` runs in dev (e8-server behind the Vite plugin), in RTP simulation (`e8 simulate`), and in production (the platform's `engine_mode: "spin"`).
|
|
201
211
|
|
|
202
|
-
### Minimal slot — `
|
|
212
|
+
### Minimal slot — `script.spin`
|
|
203
213
|
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
import type { GameDefinition } from '@energy8platform/platform-core';
|
|
207
|
-
|
|
208
|
-
const gameDefinition: GameDefinition = {
|
|
209
|
-
id: 'my-slot',
|
|
210
|
-
type: 'SLOT',
|
|
211
|
-
script_path: 'games/my-slot/script.lua', // S3 key in production
|
|
212
|
-
bet_levels: [0.20, 0.50, 1.00, 2.00, 5.00],
|
|
213
|
-
max_win: { multiplier: 10000 }, // cap = bet × 10000
|
|
214
|
-
|
|
215
|
-
actions: {
|
|
216
|
-
spin: {
|
|
217
|
-
stage: 'base_game',
|
|
218
|
-
debit: 'bet', // deducts the bet
|
|
219
|
-
credit: 'win', // credits total_win
|
|
220
|
-
transitions: [
|
|
221
|
-
// Could branch into a free-spins session here. See full guide.
|
|
222
|
-
{ condition: 'always', next_actions: ['spin'] },
|
|
223
|
-
],
|
|
224
|
-
},
|
|
225
|
-
},
|
|
226
|
-
};
|
|
214
|
+
```spin
|
|
215
|
+
record Vars { free_spins_awarded: int }
|
|
227
216
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
networkDelay: 200,
|
|
232
|
-
luaScript,
|
|
233
|
-
gameDefinition,
|
|
234
|
-
};
|
|
235
|
-
```
|
|
217
|
+
enum SpinData tag stage {
|
|
218
|
+
base_game { matrix: [[int]] win_line: int }
|
|
219
|
+
}
|
|
236
220
|
|
|
237
|
-
|
|
221
|
+
game "my-slot" {
|
|
222
|
+
bet_levels = [0.20, 0.50, 1.00, 2.00, 5.00]
|
|
223
|
+
max_win = 10000.0
|
|
224
|
+
vars = Vars
|
|
225
|
+
data = SpinData
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
action spin { stage = base_game cost = 1.0 }
|
|
238
229
|
|
|
239
|
-
```lua
|
|
240
|
-
local SYMBOLS = { 'A', 'K', 'Q', 'J', '10', '9' }
|
|
241
230
|
-- Payouts are *bet multipliers*. The platform scales by the player's
|
|
242
231
|
-- actual bet on the way out — never multiply by bet inside the script.
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
}
|
|
266
|
-
|
|
232
|
+
const PAYS: [float; 6] = [50.0, 30.0, 20.0, 10.0, 5.0, 2.0]
|
|
233
|
+
|
|
234
|
+
fn execute(c: ctx, v: Vars) -> outcome {
|
|
235
|
+
let matrix: [[int]] = list()
|
|
236
|
+
for col in 0..3 {
|
|
237
|
+
let rows: [int] = list()
|
|
238
|
+
for row in 0..3 { push(rows, rng(c, 0, 5)) }
|
|
239
|
+
push(matrix, rows)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
-- pay if all 3 middle-row symbols match
|
|
243
|
+
let a = matrix[0][1]
|
|
244
|
+
let win = 0.0
|
|
245
|
+
let line = 0
|
|
246
|
+
if a == matrix[1][1] && a == matrix[2][1] {
|
|
247
|
+
win = PAYS[a]
|
|
248
|
+
line = 1
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return outcome {
|
|
252
|
+
win: win,
|
|
253
|
+
vars: Vars { free_spins_awarded: 0 },
|
|
254
|
+
data: SpinData.base_game { matrix: matrix, win_line: line },
|
|
255
|
+
}
|
|
256
|
+
}
|
|
267
257
|
```
|
|
268
258
|
|
|
269
|
-
That's the entire contract:
|
|
259
|
+
That's the entire contract: `outcome { win, vars, data }` — a bet-multiplier
|
|
260
|
+
win, the typed persisted state, and a stage-tagged payload. The platform
|
|
261
|
+
handles the rest — debit/credit (`real_win = bet × win`), balance updates,
|
|
262
|
+
session lifecycle (from the `opens`/`extends`/`ends when` action
|
|
263
|
+
declarations), cap enforcement.
|
|
270
264
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
The mini-example above covers a base-game spin only. For everything else — free spins via `creates_session` + `next_actions`, retrigger logic, persistent meters across spins (`_persist_*`), buy-bonus and ante-bet configuration, table-game session models, the full `engine.*` Lua API, JSON-Schema input/output validation, deployment and S3 layout — see the comprehensive guide:
|
|
265
|
+
In dev, `dev.config.ts` carries the script source and the (spec-derived)
|
|
266
|
+
`GameDefinition` — see [Quick Start](#quick-start).
|
|
274
267
|
|
|
275
|
-
|
|
268
|
+
### Full reference
|
|
276
269
|
|
|
277
|
-
|
|
270
|
+
- **[SpinML language guide](../../docs/spinml.md)** — declarations, types, const groups, builtins, limits, simulation dialect.
|
|
271
|
+
- **[Lua → SpinML migration](../../docs/lua-to-spin-migration.md)** — porting an existing game.
|
|
272
|
+
- **[Game Development Guide](../../game_development_guide.md)** — the platform contract: `GameDefinition` shape, actions, deployment, S3 layout, table games.
|
|
278
273
|
|
|
279
274
|
---
|
|
280
275
|
|
|
281
|
-
##
|
|
276
|
+
## SpinML Runtime (e8)
|
|
282
277
|
|
|
283
|
-
|
|
278
|
+
The math runtime is the Rust `e8` engine (SpinML → Cranelift JIT → native
|
|
279
|
+
code), delivered as per-platform binaries by postinstall:
|
|
284
280
|
|
|
285
|
-
|
|
286
|
-
|
|
281
|
+
- **`e8`** — CLI: `check` (compile + type-check a `.spin`) and `simulate`
|
|
282
|
+
(Go-CLI-compatible dialect — same flags, JSON shape, and statistics as the
|
|
283
|
+
old simulate binary; ~4M rounds/sec per core).
|
|
284
|
+
- **`e8-server`** — the round server. In dev the Vite `spinPlugin` spawns it
|
|
285
|
+
(`--sessions memory --watch`: hot-reloads the `.spin` on save, open rounds
|
|
286
|
+
finish on the old version). In production the platform talks to the same
|
|
287
|
+
domain API over gRPC — dev exercises the exact prod contract.
|
|
287
288
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
const result = engine.execute({
|
|
295
|
-
variables: { bet: 1, balance: 5000 },
|
|
296
|
-
stage: 'base_game',
|
|
297
|
-
});
|
|
298
|
-
// → { total_win, data, next_actions, session, persistent_state }
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
Companion classes:
|
|
302
|
-
- `ActionRouter` — dispatch a play request to the matching action and evaluate transition conditions (`&&`, `||`, comparisons, `"always"`).
|
|
303
|
-
- `SessionManager` — track session lifecycle: creation, spin counting, retrigger, `_persist_` data roundtrip, completion. Supports both fixed-spin slot sessions and unlimited table sessions.
|
|
304
|
-
- `PersistentState` — cross-spin persistent vars (`persistent_state.vars` and `_persist_game_*` convention).
|
|
289
|
+
The old in-process classes (`LuaEngine`, `ActionRouter`, `SessionManager`,
|
|
290
|
+
`SimulationRunner`) were removed with the fengari runtime — the engine now
|
|
291
|
+
owns rounds, sessions, idempotency, and RNG. Legacy Lua games stay on
|
|
292
|
+
platform-core ≤ 0.28.x; see the migration guide to move.
|
|
305
293
|
|
|
306
294
|
---
|
|
307
295
|
|
|
@@ -324,7 +312,8 @@ const bridge = new DevBridge({
|
|
|
324
312
|
totalWin: Math.random() < 0.4 ? bet * 5 : 0,
|
|
325
313
|
}),
|
|
326
314
|
|
|
327
|
-
// Or: hand it your
|
|
315
|
+
// Or: hand it your .spin game math (preferred — same code as prod;
|
|
316
|
+
// served by e8-server via spinPlugin, the field name is legacy)
|
|
328
317
|
// luaScript, gameDefinition, luaSeed,
|
|
329
318
|
});
|
|
330
319
|
|
|
@@ -336,9 +325,9 @@ bridge.destroy();
|
|
|
336
325
|
|
|
337
326
|
Most of the time you don't construct DevBridge yourself — `createPlatformSession({ dev: { … } })` does it for you.
|
|
338
327
|
|
|
339
|
-
### Platform-parity behavior (
|
|
328
|
+
### Platform-parity behavior (server mode)
|
|
340
329
|
|
|
341
|
-
|
|
330
|
+
With `luaScript` + `gameDefinition` set (the field name is legacy — it carries the `.spin` source and marks "play via `POST /__lua-play`", served by e8-server through `spinPlugin`), DevBridge mirrors the server's `PlayRound` contract so error-handling code written against dev runs unchanged in prod. Invalid requests come back as `PLAY_ERROR` and the SDK's `play()` rejects with `SDKError(code, message)`:
|
|
342
331
|
|
|
343
332
|
| code | when |
|
|
344
333
|
|-------------------------|----------------------------------------------------------|
|
|
@@ -348,7 +337,7 @@ In Lua mode (`luaScript` + `gameDefinition`), DevBridge mirrors the server's `Pl
|
|
|
348
337
|
| `ACTIVE_SESSION_EXISTS` | non-session action while a session is in progress |
|
|
349
338
|
| `NO_ACTIVE_SESSION` | session-required action without an active session |
|
|
350
339
|
| `SESSION_EXPIRED` | session past `gameDefinition.session_ttl` (default 24h) |
|
|
351
|
-
| `ENGINE_ERROR` |
|
|
340
|
+
| `ENGINE_ERROR` | script execution failed (debit is rolled back) |
|
|
352
341
|
|
|
353
342
|
Other contract details DevBridge enforces:
|
|
354
343
|
|
|
@@ -356,7 +345,7 @@ Other contract details DevBridge enforces:
|
|
|
356
345
|
- **`STATE_RESPONSE`** returns the last `PlayResultData` (with `session.history` populated) while a session is active and not yet completed, mirroring `GET /api/games/{id}/session`.
|
|
357
346
|
- **`creditPending`** is `false` in the normal path. The wire flag means "wallet credit failed, queued for retry" — never "credit deferred until session completes".
|
|
358
347
|
- **`session.history`** is appended on every session round (`{spinIndex, win, data}`), so the client can rebuild the screen after reload.
|
|
359
|
-
- **`MapState` parity** — `multiplier`, `global_multiplier`, `free_spins_total`, `max_win_reached` are auto-injected into `result.data` from engine variables when the
|
|
348
|
+
- **`MapState` parity** — `multiplier`, `global_multiplier`, `free_spins_total`, `max_win_reached` are auto-injected into `result.data` from engine variables when the script doesn't set them explicitly.
|
|
360
349
|
|
|
361
350
|
### Replay mode (historical rounds)
|
|
362
351
|
|
|
@@ -391,28 +380,25 @@ The game reacts via a single flag — see [`session.isReplay`](#platformsession)
|
|
|
391
380
|
|
|
392
381
|
## RTP Simulation CLI
|
|
393
382
|
|
|
394
|
-
`platform-core` ships a
|
|
383
|
+
`platform-core` ships a dev CLI that runs your `.spin` through millions of iterations via `e8 simulate` to verify math and stage distributions. It picks up the script and `gameDefinition` from your `dev.config.ts` automatically. (For the full book-bundle pipeline — pool/curate/publish — use `e8-math` from `@energy8platform/stake-math-tools`.)
|
|
395
384
|
|
|
396
385
|
```bash
|
|
397
386
|
# 1M spins (default)
|
|
398
387
|
npx platform-core-simulate
|
|
399
388
|
|
|
400
|
-
# Buy-bonus action (
|
|
389
|
+
# Buy-bonus action (just simulate the action by name)
|
|
401
390
|
npx platform-core-simulate --action buy_bonus
|
|
402
391
|
|
|
403
|
-
# Ante bet — also a regular action
|
|
392
|
+
# Ante bet — also a regular action
|
|
404
393
|
npx platform-core-simulate --action ante_spin
|
|
405
394
|
|
|
406
395
|
# Custom: 5M iterations, custom config path
|
|
407
396
|
npx platform-core-simulate --iterations 5000000 --bet 1 --config ./dev.config.ts
|
|
408
|
-
|
|
409
|
-
# Force the JS runner (skip native binary)
|
|
410
|
-
npx platform-core-simulate --js
|
|
411
397
|
```
|
|
412
398
|
|
|
413
399
|
### Reproducibility: seeds, RNG backend, and replay
|
|
414
400
|
|
|
415
|
-
The
|
|
401
|
+
The engine keeps the provably-fair seeding contract of the old Go simulate tool. Pass `--seed=<hex>` to reproduce a previous run bit-for-bit (results are also **core-count independent** — the master seed derives 64 RNG lanes, `round % 64`); if you omit it, a seed is generated and reported (`Master seed: …`) so you can rerun the exact distribution later.
|
|
416
402
|
|
|
417
403
|
```bash
|
|
418
404
|
# Reproducible run — supply the master seed yourself
|
|
@@ -420,12 +406,12 @@ npx platform-core-simulate \
|
|
|
420
406
|
--iterations 1000000 \
|
|
421
407
|
--seed 00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff
|
|
422
408
|
|
|
423
|
-
# Fast PCG RNG —
|
|
409
|
+
# Fast PCG RNG — faster but diverges from production. Local iteration only;
|
|
424
410
|
# do NOT publish RTP numbers from --rng=fast.
|
|
425
411
|
npx platform-core-simulate --rng fast
|
|
426
412
|
|
|
427
|
-
# Replay a single round captured in `provably_fair_rounds`.
|
|
428
|
-
#
|
|
413
|
+
# Replay a single round captured in `provably_fair_rounds`. All three flags
|
|
414
|
+
# are required and require provably-fair RNG.
|
|
429
415
|
npx platform-core-simulate \
|
|
430
416
|
--iterations 1 \
|
|
431
417
|
--replay-server-seed <hex> \
|
|
@@ -433,32 +419,24 @@ npx platform-core-simulate \
|
|
|
433
419
|
--replay-nonce-start 42
|
|
434
420
|
```
|
|
435
421
|
|
|
436
|
-
The result echoes `masterSeed`, `rngKind`, `workerSeeds[]
|
|
422
|
+
The result echoes `masterSeed`, `rngKind`, `workerSeeds[]`, and `replay` when in replay mode — all also surface on `NativeSimulationResult` for programmatic use. Output matches the old server-side simulation format field-for-field, plus stddev/CV, the 0–10 volatility score, and the win-distribution buckets.
|
|
437
423
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
Programmatic use:
|
|
424
|
+
Programmatic use (Node-only sub-path):
|
|
441
425
|
|
|
442
426
|
```typescript
|
|
443
|
-
import {
|
|
444
|
-
|
|
445
|
-
const runner = new ParallelSimulationRunner({
|
|
446
|
-
script, gameDefinition,
|
|
447
|
-
iterations: 1_000_000,
|
|
448
|
-
workers: 8,
|
|
449
|
-
});
|
|
450
|
-
const result = await runner.run();
|
|
451
|
-
console.log(formatSimulationResult(result));
|
|
427
|
+
import { NativeSimulationRunner, findE8Binary, formatNativeResult } from '@energy8platform/platform-core/simulation';
|
|
452
428
|
|
|
453
|
-
// Native runner with the full provably-fair contract:
|
|
454
429
|
const native = new NativeSimulationRunner({
|
|
455
|
-
binaryPath
|
|
430
|
+
binaryPath: findE8Binary() ?? process.env.E8_BINARY!,
|
|
431
|
+
argsPrefix: ['simulate'],
|
|
432
|
+
script, scriptExt: 'spin', gameDefinition,
|
|
456
433
|
iterations: 1_000_000, bet: 1,
|
|
457
434
|
rng: 'provably-fair', // default; use 'fast' for local iteration only
|
|
458
435
|
seed: '00112233...eeff', // hex master seed; omit to auto-generate
|
|
459
436
|
// replay: { serverSeed, clientSeed, nonceStart }, // single-round reproduction
|
|
460
437
|
});
|
|
461
438
|
const r = await native.run();
|
|
439
|
+
console.log(formatNativeResult(r));
|
|
462
440
|
console.log(`Reproduce with seed=${r.masterSeed}, RTP=${r.totalRtp.toFixed(4)}%`);
|
|
463
441
|
```
|
|
464
442
|
|
|
@@ -518,22 +496,19 @@ The animated shimmer inside the SVG is pure CSS keyframes, so it appears in offl
|
|
|
518
496
|
```typescript
|
|
519
497
|
// vite.config.ts (Phaser/Three/custom — full control over your config)
|
|
520
498
|
import { defineConfig } from 'vite';
|
|
521
|
-
import { devBridgePlugin,
|
|
499
|
+
import { devBridgePlugin, spinPlugin } from '@energy8platform/platform-core/vite';
|
|
522
500
|
|
|
523
501
|
export default defineConfig({
|
|
524
502
|
plugins: [
|
|
525
503
|
devBridgePlugin('./dev.config'),
|
|
526
|
-
|
|
504
|
+
spinPlugin({ spinPath: './src/game/script.spin', gameId: 'my-slot' }),
|
|
527
505
|
],
|
|
528
506
|
});
|
|
529
507
|
```
|
|
530
508
|
|
|
531
509
|
What they do:
|
|
532
510
|
- **`devBridgePlugin`** injects a virtual entry that boots `DevBridge` from your `./dev.config` *before* your real entry imports. Dev-only.
|
|
533
|
-
- **`
|
|
534
|
-
1. Lets you `import luaScript from './game.lua?raw'` — Vite returns the file contents.
|
|
535
|
-
2. Spins up a server-side `LuaEngine` and exposes `POST /__lua-play`. `DevBridge` calls this endpoint, so `fengari` only ever runs in Node and never ships to the browser bundle.
|
|
536
|
-
3. HMR-reloads the Lua engine when `*.lua` or `dev.config*` changes.
|
|
511
|
+
- **`spinPlugin`** spawns `e8-server` (`--sessions memory --watch`) and exposes `POST /__lua-play` (the route name is the frozen DevBridge contract). The server owns rounds/sessions/idempotency — dev exercises the exact production domain API. Saving the `.spin` hot-reloads the math; open rounds finish on the old version. Binary resolution: `binPath` option → `E8_SERVER_BINARY` → `platform-core/bin` (postinstall) → `PATH`.
|
|
537
512
|
|
|
538
513
|
If you're building a Pixi game, prefer `defineGameConfig` from `@energy8platform/game-engine/vite` — it wires both plugins for you and adds Pixi-flavored Vite defaults (chunk splitting, dedupe, etc.).
|
|
539
514
|
|
|
@@ -837,14 +812,16 @@ section, all three bar modes, theme/social toggles, viewport presets, and event
|
|
|
837
812
|
| Path | What's there |
|
|
838
813
|
| --- | --- |
|
|
839
814
|
| `@energy8platform/platform-core` | Everything — re-exports from all sub-paths |
|
|
840
|
-
| `@energy8platform/platform-core/
|
|
841
|
-
| `@energy8platform/platform-core/
|
|
815
|
+
| `@energy8platform/platform-core/game-spec` | `defineGame`, `validateSpec`, `toGameDefinition`, `toSpinPrelude`, `buildSpinScript`, `exportGameSpin` — the one-source-of-truth spec layer |
|
|
816
|
+
| `@energy8platform/platform-core/lua` | **Types only** (GameDefinition, ActionDefinition, …). The fengari runtime was removed in 0.29 — legacy Lua games stay on ≤ 0.28.x |
|
|
817
|
+
| `@energy8platform/platform-core/simulation` | **Node-only.** `NativeSimulationRunner` / `findE8Binary` / `formatNativeResult` (wraps `e8 simulate`). Don't import from a browser bundle |
|
|
842
818
|
| `@energy8platform/platform-core/dev-bridge` | `DevBridge`, `DevBridgeConfig`, `ReplayConfig`, `ReplayLaunch` |
|
|
843
|
-
| `@energy8platform/platform-core/vite` | `devBridgePlugin`, `
|
|
819
|
+
| `@energy8platform/platform-core/vite` | `devBridgePlugin`, `spinPlugin` |
|
|
844
820
|
| `@energy8platform/platform-core/loading` | `createCSSPreloader`, `setCSSPreloaderProgress`, `waitCSSPreloaderTap`, `removeCSSPreloader`, `buildLogoSVG`, `LOADER_BAR_MAX_WIDTH` |
|
|
845
|
-
| `@energy8platform/platform-core/
|
|
821
|
+
| `@energy8platform/platform-core/slot-result` | Slot result normalization helpers shared by scaffolded games |
|
|
822
|
+
| `@energy8platform/platform-core/shell` | Moved to `@energy8platform/shell` (subpaths `/html`, `/pixi`) |
|
|
846
823
|
|
|
847
|
-
The sub-paths exist for tree-shaking — pulling only `/
|
|
824
|
+
The sub-paths exist for tree-shaking — pulling only `/game-spec` doesn't drag in DevBridge or vite types. The main entry is convenient for app-level code where size hardly matters.
|
|
848
825
|
|
|
849
826
|
---
|
|
850
827
|
|