@energy8platform/platform-core 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 (57) hide show
  1. package/README.md +482 -0
  2. package/bin/simulate.ts +139 -0
  3. package/dist/dev-bridge.cjs.js +237 -0
  4. package/dist/dev-bridge.cjs.js.map +1 -0
  5. package/dist/dev-bridge.d.ts +141 -0
  6. package/dist/dev-bridge.esm.js +235 -0
  7. package/dist/dev-bridge.esm.js.map +1 -0
  8. package/dist/index.cjs.js +569 -0
  9. package/dist/index.cjs.js.map +1 -0
  10. package/dist/index.d.ts +439 -0
  11. package/dist/index.esm.js +560 -0
  12. package/dist/index.esm.js.map +1 -0
  13. package/dist/loading.cjs.js +190 -0
  14. package/dist/loading.cjs.js.map +1 -0
  15. package/dist/loading.d.ts +86 -0
  16. package/dist/loading.esm.js +185 -0
  17. package/dist/loading.esm.js.map +1 -0
  18. package/dist/lua.cjs.js +1129 -0
  19. package/dist/lua.cjs.js.map +1 -0
  20. package/dist/lua.d.ts +319 -0
  21. package/dist/lua.esm.js +1119 -0
  22. package/dist/lua.esm.js.map +1 -0
  23. package/dist/simulation.cjs.js +374 -0
  24. package/dist/simulation.cjs.js.map +1 -0
  25. package/dist/simulation.d.ts +190 -0
  26. package/dist/simulation.esm.js +368 -0
  27. package/dist/simulation.esm.js.map +1 -0
  28. package/dist/vite.cjs.js +179 -0
  29. package/dist/vite.cjs.js.map +1 -0
  30. package/dist/vite.d.ts +13 -0
  31. package/dist/vite.esm.js +176 -0
  32. package/dist/vite.esm.js.map +1 -0
  33. package/package.json +100 -0
  34. package/scripts/install-simulate.mjs +101 -0
  35. package/src/EventEmitter.ts +55 -0
  36. package/src/PlatformSession.ts +156 -0
  37. package/src/dev-bridge/DevBridge.ts +305 -0
  38. package/src/dev-bridge/index.ts +2 -0
  39. package/src/index.ts +98 -0
  40. package/src/loading/CSSPreloader.ts +129 -0
  41. package/src/loading/index.ts +3 -0
  42. package/src/loading/logo.ts +95 -0
  43. package/src/lua/ActionRouter.ts +132 -0
  44. package/src/lua/LuaEngine.ts +412 -0
  45. package/src/lua/LuaEngineAPI.ts +314 -0
  46. package/src/lua/PersistentState.ts +80 -0
  47. package/src/lua/SessionManager.ts +227 -0
  48. package/src/lua/SimulationRunner.ts +192 -0
  49. package/src/lua/fengari.d.ts +10 -0
  50. package/src/lua/index.ts +28 -0
  51. package/src/lua/types.ts +149 -0
  52. package/src/simulation/NativeSimulationRunner.ts +367 -0
  53. package/src/simulation/ParallelSimulationRunner.ts +156 -0
  54. package/src/simulation/SimulationWorker.ts +44 -0
  55. package/src/simulation/index.ts +21 -0
  56. package/src/types.ts +85 -0
  57. package/src/vite/index.ts +196 -0
@@ -0,0 +1,367 @@
1
+ import { spawn } from 'child_process';
2
+ import { writeFile, unlink } from 'fs/promises';
3
+ import { accessSync, constants as fsConstants } from 'fs';
4
+ import { join, dirname } from 'path';
5
+ import { tmpdir } from 'os';
6
+ import { randomBytes } from 'crypto';
7
+ import { execSync } from 'child_process';
8
+ import { fileURLToPath } from 'url';
9
+ import type { GameDefinition, SimulationResult } from '../lua/types';
10
+
11
+ // ─── Types ──────────────────────────────────────────────
12
+
13
+ export interface NativeSimulationConfig {
14
+ /** Path to native simulation binary */
15
+ binaryPath: string;
16
+ /** Lua script source code */
17
+ script: string;
18
+ /** Platform game definition */
19
+ gameDefinition: GameDefinition;
20
+ /** Number of iterations */
21
+ iterations: number;
22
+ /** Bet amount */
23
+ bet: number;
24
+ /** Action to simulate (default: auto-detect by binary) */
25
+ action?: string;
26
+ /** Action params (buy_bonus, ante_bet, etc.) */
27
+ params?: Record<string, unknown>;
28
+ /** Progress callback */
29
+ onProgress?: (completed: number, total: number) => void;
30
+ }
31
+
32
+ export interface StageStats {
33
+ totalWin: number;
34
+ spinCount: number;
35
+ hitCount: number;
36
+ maxWin: number;
37
+ rtp: number;
38
+ perSpinRtp: number;
39
+ hitFrequency: number;
40
+ avgWin: number;
41
+ }
42
+
43
+ export interface DistributionBucket {
44
+ label: string;
45
+ count: number;
46
+ pct: number;
47
+ }
48
+
49
+ export interface NativeSimulationResult extends SimulationResult {
50
+ /** Iterations per second */
51
+ speed?: number;
52
+ /** Number of parallel workers used */
53
+ workersUsed?: number;
54
+ /** Per-stage breakdown */
55
+ perStage?: Record<string, StageStats>;
56
+ /** Win distribution histogram */
57
+ winDistribution?: DistributionBucket[];
58
+ }
59
+
60
+ // ─── Go JSON output shape (snake_case) ──────────────────
61
+
62
+ interface GoSimulationOutput {
63
+ game_id: string;
64
+ speed: number;
65
+ total_rtp: number;
66
+ hit_frequency: number;
67
+ max_win: number;
68
+ max_win_hits: number;
69
+ total_bet: number;
70
+ total_win: number;
71
+ iterations: number;
72
+ workers_used: number;
73
+ duration_sec: number;
74
+ bonus_triggered: number;
75
+ bonus_spins_total: number;
76
+ per_stage_stats?: Record<string, {
77
+ total_win: number;
78
+ spin_count: number;
79
+ hit_count: number;
80
+ max_win: number;
81
+ rtp: number;
82
+ per_spin_rtp: number;
83
+ hit_frequency: number;
84
+ avg_win: number;
85
+ }>;
86
+ win_distribution?: Array<{
87
+ label: string;
88
+ count: number;
89
+ pct: number;
90
+ }>;
91
+ }
92
+
93
+ // ─── Runner ─────────────────────────────────────────────
94
+
95
+ export class NativeSimulationRunner {
96
+ private config: NativeSimulationConfig;
97
+
98
+ constructor(config: NativeSimulationConfig) {
99
+ this.config = config;
100
+ }
101
+
102
+ async run(): Promise<NativeSimulationResult> {
103
+ const { binaryPath, script, gameDefinition, iterations, bet, action, params } = this.config;
104
+ const id = randomBytes(8).toString('hex');
105
+ const tmpDir = tmpdir();
106
+ const luaPath = join(tmpDir, `sim-${id}.lua`);
107
+ const configPath = join(tmpDir, `sim-${id}.json`);
108
+
109
+ try {
110
+ // Write temp files
111
+ await Promise.all([
112
+ writeFile(luaPath, script, 'utf-8'),
113
+ writeFile(configPath, JSON.stringify({ ...gameDefinition, script_path: luaPath }), 'utf-8'),
114
+ ]);
115
+
116
+ // Build CLI args
117
+ const args = [
118
+ '-config', configPath,
119
+ '-iterations', String(iterations),
120
+ '-bet', String(bet),
121
+ '-format', 'json',
122
+ ];
123
+ if (action) {
124
+ args.push('-action', action);
125
+ }
126
+ if (params && Object.keys(params).length > 0) {
127
+ args.push('-params', JSON.stringify(params));
128
+ }
129
+
130
+ // Execute binary
131
+ const output = await this.exec(binaryPath, args);
132
+
133
+ // Parse JSON output
134
+ const json: GoSimulationOutput = JSON.parse(output);
135
+ return mapGoResult(json);
136
+ } finally {
137
+ // Cleanup temp files
138
+ await Promise.allSettled([unlink(luaPath), unlink(configPath)]);
139
+ }
140
+ }
141
+
142
+ private exec(binary: string, args: string[]): Promise<string> {
143
+ return new Promise((resolve, reject) => {
144
+ const child = spawn(binary, args, { stdio: ['ignore', 'pipe', 'pipe'] });
145
+
146
+ let stdout = '';
147
+ let stderr = '';
148
+
149
+ child.stdout.on('data', (chunk: Buffer) => {
150
+ stdout += chunk.toString();
151
+ });
152
+
153
+ child.stderr.on('data', (chunk: Buffer) => {
154
+ stderr += chunk.toString();
155
+ });
156
+
157
+ child.on('error', (err) => {
158
+ reject(new Error(`Failed to execute simulation binary: ${err.message}`));
159
+ });
160
+
161
+ child.on('close', (code) => {
162
+ if (code !== 0) {
163
+ reject(new Error(`Simulation binary exited with code ${code}: ${stderr.trim()}`));
164
+ } else {
165
+ resolve(stdout);
166
+ }
167
+ });
168
+ });
169
+ }
170
+ }
171
+
172
+ // ─── Result mapping ─────────────────────────────────────
173
+
174
+ function mapGoResult(json: GoSimulationOutput): NativeSimulationResult {
175
+ const baseStage = json.per_stage_stats?.base_game;
176
+ const baseGameRtp = baseStage?.rtp ?? 0;
177
+ const baseGameWin = baseStage?.total_win ?? 0;
178
+
179
+ const perStage = json.per_stage_stats
180
+ ? Object.fromEntries(
181
+ Object.entries(json.per_stage_stats).map(([key, s]) => [
182
+ key,
183
+ {
184
+ totalWin: s.total_win,
185
+ spinCount: s.spin_count,
186
+ hitCount: s.hit_count,
187
+ maxWin: s.max_win,
188
+ rtp: s.rtp,
189
+ perSpinRtp: s.per_spin_rtp,
190
+ hitFrequency: s.hit_frequency,
191
+ avgWin: s.avg_win,
192
+ },
193
+ ]),
194
+ )
195
+ : undefined;
196
+
197
+ return {
198
+ gameId: json.game_id,
199
+ action: 'spin',
200
+ iterations: json.iterations,
201
+ durationMs: Math.round(json.duration_sec * 1000),
202
+ totalRtp: json.total_rtp,
203
+ baseGameRtp,
204
+ bonusRtp: json.total_rtp - baseGameRtp,
205
+ hitFrequency: json.hit_frequency,
206
+ maxWin: json.max_win,
207
+ maxWinHits: json.max_win_hits,
208
+ bonusTriggered: json.bonus_triggered,
209
+ bonusSpinsPlayed: json.bonus_spins_total,
210
+ speed: json.speed,
211
+ workersUsed: json.workers_used,
212
+ perStage,
213
+ winDistribution: json.win_distribution,
214
+ _raw: {
215
+ totalWagered: json.total_bet,
216
+ totalWon: json.total_win,
217
+ baseGameWin,
218
+ bonusWin: json.total_win - baseGameWin,
219
+ hits: json.iterations > 0 ? Math.round((json.hit_frequency * json.iterations) / 100) : 0,
220
+ },
221
+ };
222
+ }
223
+
224
+ // ─── Binary discovery ───────────────────────────────────
225
+
226
+ /**
227
+ * Search for a native simulation binary in standard locations.
228
+ * Returns the absolute path if found, null otherwise.
229
+ */
230
+ export function findNativeBinary(baseDir?: string): string | null {
231
+ // 1. Explicit env var
232
+ const envPath = process.env.SIMULATE_BINARY;
233
+ if (envPath && isExecutable(envPath)) {
234
+ return envPath;
235
+ }
236
+
237
+ const platform = process.platform; // darwin, linux, win32
238
+ const nodeArch = process.arch; // arm64, x64
239
+ const goArch = nodeArch === 'x64' ? 'amd64' : nodeArch;
240
+ const goPlatform = platform === 'win32' ? 'windows' : platform;
241
+ const ext = platform === 'win32' ? '.exe' : '';
242
+
243
+ const names = [
244
+ `simulate-${goPlatform}-${goArch}${ext}`,
245
+ `simulation-${goPlatform}-${goArch}${ext}`,
246
+ `simulate${ext}`,
247
+ `simulation${ext}`,
248
+ ];
249
+
250
+ // Search directories: user's project first, then this package's bin/
251
+ const searchDirs: string[] = [];
252
+ if (baseDir) searchDirs.push(baseDir);
253
+
254
+ // This package's root (where postinstall downloads the binary)
255
+ try {
256
+ const pkgRoot = join(dirname(fileURLToPath(import.meta.url)), '..');
257
+ if (!searchDirs.includes(pkgRoot)) searchDirs.push(pkgRoot);
258
+ } catch {
259
+ // fallback for CJS
260
+ if (typeof __dirname !== 'undefined') {
261
+ const pkgRoot = join(__dirname, '..');
262
+ if (!searchDirs.includes(pkgRoot)) searchDirs.push(pkgRoot);
263
+ }
264
+ }
265
+
266
+ for (const dir of searchDirs) {
267
+ for (const name of names) {
268
+ const candidate = join(dir, 'bin', name);
269
+ if (isExecutable(candidate)) return candidate;
270
+ }
271
+ }
272
+
273
+ // Check $PATH
274
+ for (const bin of ['simulate', 'simulation']) {
275
+ try {
276
+ const cmd = platform === 'win32' ? `where ${bin}` : `which ${bin}`;
277
+ const result = execSync(cmd, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
278
+ if (result) return result.split('\n')[0];
279
+ } catch {
280
+ // not found
281
+ }
282
+ }
283
+
284
+ return null;
285
+ }
286
+
287
+ function isExecutable(path: string): boolean {
288
+ try {
289
+ accessSync(path, fsConstants.X_OK);
290
+ return true;
291
+ } catch {
292
+ return false;
293
+ }
294
+ }
295
+
296
+ // ─── Extended formatting ────────────────────────────────
297
+
298
+ /** Format a NativeSimulationResult with per-stage and distribution data */
299
+ export function formatNativeResult(result: NativeSimulationResult): string {
300
+ const lines: string[] = [
301
+ '',
302
+ '--- Simulation Results ---',
303
+ `Game: ${result.gameId}`,
304
+ `Iterations: ${result.iterations.toLocaleString()}`,
305
+ `Duration: ${(result.durationMs / 1000).toFixed(1)}s`,
306
+ ];
307
+
308
+ if (result.speed) {
309
+ lines.push(`Speed: ${Math.round(result.speed).toLocaleString()} iterations/sec`);
310
+ }
311
+ if (result.workersUsed) {
312
+ lines.push(`Workers: ${result.workersUsed}`);
313
+ }
314
+
315
+ lines.push(
316
+ '',
317
+ '--- Total ---',
318
+ `Total RTP: ${result.totalRtp.toFixed(2)}%`,
319
+ `Base Game RTP: ${result.baseGameRtp.toFixed(2)}%`,
320
+ `Bonus RTP: ${result.bonusRtp.toFixed(2)}%`,
321
+ `Hit Frequency: ${result.hitFrequency.toFixed(2)}%`,
322
+ `Max Win: ${result.maxWin.toFixed(2)}x`,
323
+ `Max Win Cap Hits: ${result.maxWinHits}`,
324
+ );
325
+
326
+ if (result.bonusTriggered > 0) {
327
+ const frequency = Math.round(result.iterations / result.bonusTriggered);
328
+ lines.push(
329
+ '',
330
+ '--- Bonus Stats ---',
331
+ `Bonus Triggered: ${result.bonusTriggered.toLocaleString()} (1 in ${frequency} spins)`,
332
+ `Bonus Spins Total: ${result.bonusSpinsPlayed.toLocaleString()}`,
333
+ );
334
+ }
335
+
336
+ // Per-stage breakdown
337
+ if (result.perStage && Object.keys(result.perStage).length > 0) {
338
+ lines.push('', '--- Per-Stage Breakdown ---');
339
+ const header = 'Stage | Spins | RTP (contrib) | Per-Spin RTP | Hit Freq | Avg Win | Max Win';
340
+ lines.push(header);
341
+ lines.push('-'.repeat(header.length));
342
+
343
+ for (const [stage, stats] of Object.entries(result.perStage)) {
344
+ lines.push(
345
+ `${stage.padEnd(20)} | ${String(stats.spinCount).padStart(10)} | ` +
346
+ `${stats.rtp.toFixed(2).padStart(12)}% | ` +
347
+ `${stats.perSpinRtp.toFixed(2).padStart(11)}% | ` +
348
+ `${stats.hitFrequency.toFixed(2).padStart(8)}% | ` +
349
+ `${stats.avgWin.toFixed(3).padStart(8)}x | ` +
350
+ `${stats.maxWin.toFixed(2).padStart(8)}x`,
351
+ );
352
+ }
353
+ }
354
+
355
+ // Win distribution
356
+ if (result.winDistribution && result.winDistribution.length > 0) {
357
+ lines.push('', '--- Win Distribution ---');
358
+ for (const bucket of result.winDistribution) {
359
+ const bar = '█'.repeat(Math.round(bucket.pct / 2));
360
+ lines.push(
361
+ `${bucket.label.padEnd(10)} ${String(bucket.count).padStart(10)} (${bucket.pct.toFixed(2).padStart(6)}%) ${bar}`,
362
+ );
363
+ }
364
+ }
365
+
366
+ return lines.join('\n');
367
+ }
@@ -0,0 +1,156 @@
1
+ /// <reference types="node" />
2
+ import { Worker } from 'worker_threads';
3
+ import { cpus } from 'os';
4
+ import { fileURLToPath } from 'url';
5
+ import { dirname, join } from 'path';
6
+ import type { SimulationConfig, SimulationResult, SimulationRawAccumulators } from '../lua/types';
7
+ import type { WorkerMessage, WorkerConfig } from './SimulationWorker';
8
+
9
+ const SEED_STRIDE = 1 << 20; // 2^20 — gap between worker seeds to avoid overlap
10
+
11
+ /**
12
+ * Runs simulation across multiple worker threads for parallel speedup.
13
+ * Each worker gets an independent LuaEngine with a partitioned seed range.
14
+ *
15
+ * Results are statistically equivalent to single-threaded mode but not
16
+ * bit-identical (different RNG sequence ordering).
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * const runner = new ParallelSimulationRunner({
21
+ * script: luaSource,
22
+ * gameDefinition,
23
+ * iterations: 1_000_000,
24
+ * bet: 1.0,
25
+ * workerCount: 8,
26
+ * onProgress: (done, total) => console.log(`${done}/${total}`),
27
+ * });
28
+ * const result = await runner.run();
29
+ * ```
30
+ */
31
+ export class ParallelSimulationRunner {
32
+ private config: SimulationConfig;
33
+ private workerCount: number;
34
+
35
+ constructor(config: SimulationConfig) {
36
+ this.config = config;
37
+ const maxWorkers = cpus().length;
38
+ this.workerCount = Math.max(1, Math.min(
39
+ config.workerCount ?? maxWorkers,
40
+ maxWorkers,
41
+ config.iterations, // no point having more workers than iterations
42
+ ));
43
+ }
44
+
45
+ async run(): Promise<SimulationResult> {
46
+ const {
47
+ iterations,
48
+ seed,
49
+ onProgress,
50
+ workerCount: _,
51
+ ...restConfig
52
+ } = this.config;
53
+
54
+ const workerCount = this.workerCount;
55
+
56
+ // Split iterations evenly, remainder goes to last worker
57
+ const baseChunk = Math.floor(iterations / workerCount);
58
+ const remainder = iterations - baseChunk * workerCount;
59
+
60
+ const workerPath = join(dirname(fileURLToPath(import.meta.url)), 'SimulationWorker.ts');
61
+
62
+ const progressPerWorker = new Array<number>(workerCount).fill(0);
63
+ const totalIterations = iterations;
64
+
65
+ const promises = Array.from({ length: workerCount }, (_, i) => {
66
+ const workerIterations = baseChunk + (i < remainder ? 1 : 0);
67
+ const workerSeed = seed !== undefined ? seed + i * SEED_STRIDE : undefined;
68
+
69
+ const workerConfig: WorkerConfig = {
70
+ config: {
71
+ ...restConfig,
72
+ iterations: workerIterations,
73
+ seed: workerSeed,
74
+ progressInterval: this.config.progressInterval,
75
+ },
76
+ };
77
+
78
+ return new Promise<SimulationResult>((resolve, reject) => {
79
+ const worker = new Worker(workerPath, {
80
+ workerData: workerConfig,
81
+ // tsx registers itself via --require/--import; pass through to workers
82
+ execArgv: process.execArgv,
83
+ });
84
+
85
+ worker.on('message', (msg: WorkerMessage) => {
86
+ if (msg.type === 'progress' && onProgress) {
87
+ progressPerWorker[i] = msg.progress!.completed;
88
+ const totalCompleted = progressPerWorker.reduce((a, b) => a + b, 0);
89
+ onProgress(totalCompleted, totalIterations);
90
+ } else if (msg.type === 'result') {
91
+ resolve(msg.result!);
92
+ } else if (msg.type === 'error') {
93
+ reject(new Error(`Worker ${i} failed: ${msg.error}`));
94
+ }
95
+ });
96
+
97
+ worker.on('error', (err: Error) => reject(new Error(`Worker ${i} error: ${err.message}`)));
98
+ worker.on('exit', (code) => {
99
+ if (code !== 0) reject(new Error(`Worker ${i} exited with code ${code}`));
100
+ });
101
+ });
102
+ });
103
+
104
+ const results = await Promise.all(promises);
105
+ return aggregateResults(results);
106
+ }
107
+ }
108
+
109
+ function aggregateResults(results: SimulationResult[]): SimulationResult {
110
+ const raw: SimulationRawAccumulators = {
111
+ totalWagered: 0,
112
+ totalWon: 0,
113
+ baseGameWin: 0,
114
+ bonusWin: 0,
115
+ hits: 0,
116
+ };
117
+
118
+ let iterations = 0;
119
+ let maxWin = 0;
120
+ let maxWinHits = 0;
121
+ let bonusTriggered = 0;
122
+ let bonusSpinsPlayed = 0;
123
+ let maxDurationMs = 0;
124
+
125
+ for (const r of results) {
126
+ const rr = r._raw!;
127
+ raw.totalWagered += rr.totalWagered;
128
+ raw.totalWon += rr.totalWon;
129
+ raw.baseGameWin += rr.baseGameWin;
130
+ raw.bonusWin += rr.bonusWin;
131
+ raw.hits += rr.hits;
132
+
133
+ iterations += r.iterations;
134
+ if (r.maxWin > maxWin) maxWin = r.maxWin;
135
+ maxWinHits += r.maxWinHits;
136
+ bonusTriggered += r.bonusTriggered;
137
+ bonusSpinsPlayed += r.bonusSpinsPlayed;
138
+ if (r.durationMs > maxDurationMs) maxDurationMs = r.durationMs;
139
+ }
140
+
141
+ return {
142
+ gameId: results[0].gameId,
143
+ action: results[0].action,
144
+ iterations,
145
+ durationMs: maxDurationMs,
146
+ totalRtp: raw.totalWagered > 0 ? (raw.totalWon / raw.totalWagered) * 100 : 0,
147
+ baseGameRtp: raw.totalWagered > 0 ? (raw.baseGameWin / raw.totalWagered) * 100 : 0,
148
+ bonusRtp: raw.totalWagered > 0 ? (raw.bonusWin / raw.totalWagered) * 100 : 0,
149
+ hitFrequency: iterations > 0 ? (raw.hits / iterations) * 100 : 0,
150
+ maxWin,
151
+ maxWinHits,
152
+ bonusTriggered,
153
+ bonusSpinsPlayed,
154
+ _raw: raw,
155
+ };
156
+ }
@@ -0,0 +1,44 @@
1
+ /// <reference types="node" />
2
+ import { parentPort, workerData } from 'worker_threads';
3
+ import { SimulationRunner } from '../lua/SimulationRunner';
4
+ import type { SimulationConfig, SimulationResult } from '../lua/types';
5
+
6
+ export interface WorkerMessage {
7
+ type: 'progress' | 'result' | 'error';
8
+ progress?: { completed: number; total: number };
9
+ result?: SimulationResult;
10
+ error?: string;
11
+ }
12
+
13
+ export interface WorkerConfig {
14
+ config: Omit<SimulationConfig, 'onProgress' | 'workerCount'>;
15
+ }
16
+
17
+ function run() {
18
+ const { config } = workerData as WorkerConfig;
19
+
20
+ const runner = new SimulationRunner({
21
+ ...config,
22
+ onProgress: (completed, total) => {
23
+ parentPort!.postMessage({
24
+ type: 'progress',
25
+ progress: { completed, total },
26
+ } satisfies WorkerMessage);
27
+ },
28
+ });
29
+
30
+ try {
31
+ const result = runner.run();
32
+ parentPort!.postMessage({
33
+ type: 'result',
34
+ result,
35
+ } satisfies WorkerMessage);
36
+ } catch (e: any) {
37
+ parentPort!.postMessage({
38
+ type: 'error',
39
+ error: e.message ?? String(e),
40
+ } satisfies WorkerMessage);
41
+ }
42
+ }
43
+
44
+ run();
@@ -0,0 +1,21 @@
1
+ // Node-only simulation runners.
2
+ //
3
+ // These import worker_threads / child_process / fs / os, which makes
4
+ // them unsuitable for browser bundles. Keep them in this dedicated
5
+ // sub-path so consumers who only need browser-safe Lua execution can
6
+ // pull from `/lua` (or the main entry) without dragging Node modules
7
+ // into their browser bundle.
8
+
9
+ export {
10
+ NativeSimulationRunner,
11
+ findNativeBinary,
12
+ formatNativeResult,
13
+ } from './NativeSimulationRunner';
14
+ export type {
15
+ NativeSimulationConfig,
16
+ NativeSimulationResult,
17
+ StageStats,
18
+ DistributionBucket,
19
+ } from './NativeSimulationRunner';
20
+
21
+ export { ParallelSimulationRunner } from './ParallelSimulationRunner';
package/src/types.ts ADDED
@@ -0,0 +1,85 @@
1
+ // Platform-level type re-exports.
2
+ //
3
+ // Phaser/Three/custom-engine consumers import these from
4
+ // `@energy8platform/platform-core` instead of pulling in game-engine.
5
+
6
+ // SDK types
7
+ export type {
8
+ InitData,
9
+ GameConfigData,
10
+ SessionData,
11
+ PlayParams,
12
+ PlayResultData,
13
+ BalanceData,
14
+ SymbolData,
15
+ PaylineData,
16
+ WinLineData,
17
+ AnywhereWinData,
18
+ } from '@energy8platform/game-sdk';
19
+
20
+ // ─── Asset Manifest ────────────────────────────────────────
21
+ // Renderer-agnostic declaration: "what to load, in which bundles".
22
+ // Each renderer implements its own loader (pixi.Assets, Phaser.Loader,
23
+ // THREE.TextureLoader, …) but the manifest format is the same.
24
+
25
+ export interface AssetEntry {
26
+ alias: string;
27
+ src: string | string[];
28
+ /** Optional loader-specific data (e.g. parser hints) */
29
+ data?: Record<string, unknown>;
30
+ }
31
+
32
+ export interface AssetBundle {
33
+ name: string;
34
+ assets: AssetEntry[];
35
+ }
36
+
37
+ export interface AssetManifest {
38
+ bundles: AssetBundle[];
39
+ }
40
+
41
+ // ─── Loading Screen Config ─────────────────────────────────
42
+ // Used by the Energy8 CSS preloader (in this package) and by
43
+ // engine-specific loading scenes (in @energy8platform/game-engine etc.).
44
+
45
+ export interface LoadingScreenConfig {
46
+ /** Background color (hex number or CSS string) */
47
+ backgroundColor?: number | string;
48
+ /** Background gradient (CSS string applied to the CSS preloader) */
49
+ backgroundGradient?: string;
50
+ /** Logo texture alias (must be in 'preload' bundle — engine-specific) */
51
+ logoAsset?: string;
52
+ /** Logo scale (default: 1) */
53
+ logoScale?: number;
54
+ /** Show percentage text below the loader bar */
55
+ showPercentage?: boolean;
56
+ /** Custom progress text formatter */
57
+ progressTextFormatter?: (progress: number) => string;
58
+ /** Show "Tap to start" after loading (needed for mobile audio unlock) */
59
+ tapToStart?: boolean;
60
+ /** "Tap to start" label text */
61
+ tapToStartText?: string;
62
+ /** Minimum display time in ms (so the user sees the brand, even if loading is fast) */
63
+ minDisplayTime?: number;
64
+ /** CSS preloader custom HTML (shown before the renderer is ready) */
65
+ cssPreloaderHTML?: string;
66
+ }
67
+
68
+ // Lua / game-definition types
69
+ export type {
70
+ GameDefinition,
71
+ ActionDefinition,
72
+ TransitionRule,
73
+ SessionConfig,
74
+ LuaEngineConfig,
75
+ LuaPlayResult,
76
+ MaxWinConfig,
77
+ BuyBonusConfig,
78
+ BuyBonusMode,
79
+ AnteBetConfig,
80
+ PersistentStateConfig,
81
+ BetLevelsConfig,
82
+ SimulationConfig,
83
+ SimulationResult,
84
+ SimulationRawAccumulators,
85
+ } from './lua/types';