@energy8platform/platform-core 0.16.0 → 0.16.2
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 +35 -5
- package/dist/dev-bridge.cjs.js +223 -23
- package/dist/dev-bridge.cjs.js.map +1 -1
- package/dist/dev-bridge.d.ts +42 -1
- package/dist/dev-bridge.esm.js +223 -23
- package/dist/dev-bridge.esm.js.map +1 -1
- package/dist/index.cjs.js +223 -23
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +42 -1
- package/dist/index.esm.js +223 -23
- package/dist/index.esm.js.map +1 -1
- package/dist/lua.cjs.js +89 -7
- package/dist/lua.cjs.js.map +1 -1
- package/dist/lua.d.ts +16 -3
- package/dist/lua.esm.js +89 -7
- package/dist/lua.esm.js.map +1 -1
- package/dist/simulation.d.ts +6 -0
- package/package.json +1 -1
- package/src/dev-bridge/DevBridge.ts +247 -26
- package/src/lua/LuaEngine.ts +88 -5
- package/src/lua/SessionManager.ts +23 -1
- package/src/lua/types.ts +6 -0
package/src/lua/LuaEngine.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { PlayParams, SessionData } from '@energy8platform/game-sdk';
|
|
2
|
-
import type { LuaEngineConfig, LuaPlayResult, GameDefinition } from './types';
|
|
2
|
+
import type { LuaEngineConfig, LuaPlayResult, GameDefinition, ActionDefinition } from './types';
|
|
3
3
|
import { LuaEngineAPI, createSeededRng, luaToJS, pushJSValue, cachedToLuastring } from './LuaEngineAPI';
|
|
4
4
|
import { ActionRouter } from './ActionRouter';
|
|
5
5
|
import { SessionManager } from './SessionManager';
|
|
@@ -193,6 +193,12 @@ export class LuaEngine {
|
|
|
193
193
|
}
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
+
// Apply MapState parity — server's state_mapper.go injects these
|
|
197
|
+
// variable-derived keys into the client data so scripts don't have to
|
|
198
|
+
// surface them manually. Lua-provided values take precedence (server
|
|
199
|
+
// also overwrites variable-derived keys with state.Data on merge).
|
|
200
|
+
this.applyMapStateInjection(stateVars, data);
|
|
201
|
+
|
|
196
202
|
// 7. Handle _persist_* and _persist_game_* keys
|
|
197
203
|
this.sessionManager.storePersistData(data);
|
|
198
204
|
this.persistentState.storeGameData(data);
|
|
@@ -214,7 +220,8 @@ export class LuaEngine {
|
|
|
214
220
|
}
|
|
215
221
|
|
|
216
222
|
// 8. Evaluate transitions (server uses state.Variables which is stateVars)
|
|
217
|
-
const { rule
|
|
223
|
+
const { rule } = this.actionRouter.evaluateTransitions(action, stateVars);
|
|
224
|
+
let nextActions = rule.next_actions;
|
|
218
225
|
|
|
219
226
|
// 9. Determine credit behavior (server: creditNow logic)
|
|
220
227
|
let creditDeferred = action.credit === 'defer' || rule.credit_override === 'defer';
|
|
@@ -227,9 +234,15 @@ export class LuaEngine {
|
|
|
227
234
|
// Calculate max win cap for session
|
|
228
235
|
const maxWinCap = this.calculateMaxWinCap(bet);
|
|
229
236
|
|
|
237
|
+
// Snapshot the round data for history (matches server's MapStateForHistory:
|
|
238
|
+
// strip _persist_* keys, but those are already removed below before
|
|
239
|
+
// returning — at this point in the flow they may still be in `data`,
|
|
240
|
+
// so we filter inline).
|
|
241
|
+
const roundData = stripPersistKeys(data);
|
|
242
|
+
|
|
230
243
|
if (rule.creates_session && !this.sessionManager.isActive) {
|
|
231
244
|
// CREATE SESSION — initial spin counted (server: createSession includes spinWin)
|
|
232
|
-
session = this.sessionManager.createSession(rule, stateVars, bet, spinWin, maxWinCap);
|
|
245
|
+
session = this.sessionManager.createSession(rule, stateVars, bet, spinWin, maxWinCap, roundData);
|
|
233
246
|
creditDeferred = true;
|
|
234
247
|
resultTotalWin = spinWin;
|
|
235
248
|
|
|
@@ -239,16 +252,23 @@ export class LuaEngine {
|
|
|
239
252
|
}
|
|
240
253
|
} else if (this.sessionManager.isActive) {
|
|
241
254
|
// UPDATE SESSION — accumulate win, check completion
|
|
242
|
-
session = this.sessionManager.updateSession(rule, stateVars, spinWin);
|
|
255
|
+
session = this.sessionManager.updateSession(rule, stateVars, spinWin, roundData);
|
|
243
256
|
|
|
244
257
|
if (session?.completed) {
|
|
245
|
-
// SESSION COMPLETED — server returns session.TotalWin as result.TotalWin
|
|
258
|
+
// SESSION COMPLETED — server returns session.TotalWin as result.TotalWin,
|
|
259
|
+
// and pulls next_actions from the explicit completion transition
|
|
260
|
+
// (findCompletionNextActions) rather than the matched 'continue' rule.
|
|
246
261
|
const completed = this.sessionManager.completeSession();
|
|
247
262
|
session = completed.session;
|
|
248
263
|
resultTotalWin = completed.totalWin;
|
|
249
264
|
sessionCompleted = true;
|
|
250
265
|
creditDeferred = false;
|
|
251
266
|
|
|
267
|
+
const completionNext = findCompletionNextActions(action);
|
|
268
|
+
if (completionNext) {
|
|
269
|
+
nextActions = completionNext;
|
|
270
|
+
}
|
|
271
|
+
|
|
252
272
|
// Clean up session-scoped variables
|
|
253
273
|
for (const varName of completed.sessionVarNames) {
|
|
254
274
|
delete this.variables[varName];
|
|
@@ -386,6 +406,35 @@ export class LuaEngine {
|
|
|
386
406
|
return result as Record<string, unknown>;
|
|
387
407
|
}
|
|
388
408
|
|
|
409
|
+
/**
|
|
410
|
+
* Mirror server's state_mapper.go MapState — surface variable-derived
|
|
411
|
+
* fields so scripts that don't manually echo them in the result table
|
|
412
|
+
* still produce a server-shaped data map. Lua keys win on conflict.
|
|
413
|
+
*/
|
|
414
|
+
private applyMapStateInjection(
|
|
415
|
+
vars: Record<string, number>,
|
|
416
|
+
data: Record<string, unknown>,
|
|
417
|
+
): void {
|
|
418
|
+
const m = vars.multiplier;
|
|
419
|
+
if (typeof m === 'number' && m > 1 && data.multiplier === undefined) {
|
|
420
|
+
data.multiplier = m;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const gm = vars.global_multiplier;
|
|
424
|
+
if (typeof gm === 'number' && gm > 1 && data.global_multiplier === undefined) {
|
|
425
|
+
data.global_multiplier = gm;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const fs = vars.free_spins_remaining;
|
|
429
|
+
if (typeof fs === 'number' && fs > 0 && data.free_spins_total === undefined) {
|
|
430
|
+
data.free_spins_total = Math.trunc(fs);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (vars.max_win_reached === 1 && data.max_win_reached === undefined) {
|
|
434
|
+
data.max_win_reached = true;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
389
438
|
private calculateMaxWinCap(bet: number): number | undefined {
|
|
390
439
|
const mw = this.gameDefinition.max_win;
|
|
391
440
|
if (!mw) return undefined;
|
|
@@ -410,3 +459,37 @@ export class LuaEngine {
|
|
|
410
459
|
return parseInt(entries[entries.length - 1][0], 10);
|
|
411
460
|
}
|
|
412
461
|
}
|
|
462
|
+
|
|
463
|
+
// ─── Module helpers ─────────────────────────────────────
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Strip _persist_* and _persist_game_* keys from a data map — matches
|
|
467
|
+
* server's MapStateForHistory used when recording session round history.
|
|
468
|
+
*/
|
|
469
|
+
function stripPersistKeys(data: Record<string, unknown>): Record<string, unknown> {
|
|
470
|
+
const out: Record<string, unknown> = {};
|
|
471
|
+
for (const k of Object.keys(data)) {
|
|
472
|
+
if (k.startsWith('_persist_') || k.startsWith('_persist_game_')) continue;
|
|
473
|
+
out[k] = data[k];
|
|
474
|
+
}
|
|
475
|
+
return out;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Mirror server's findCompletionNextActions: when a session naturally
|
|
480
|
+
* completes, the matched 'continue' rule's next_actions are NOT what
|
|
481
|
+
* the client should see — the explicit complete_session transition wins,
|
|
482
|
+
* with a fallback to the 'always' transition.
|
|
483
|
+
*/
|
|
484
|
+
function findCompletionNextActions(action: ActionDefinition): string[] | null {
|
|
485
|
+
let alwaysFallback: string[] | null = null;
|
|
486
|
+
for (const t of action.transitions) {
|
|
487
|
+
if (t.complete_session && t.next_actions && t.next_actions.length > 0) {
|
|
488
|
+
return t.next_actions;
|
|
489
|
+
}
|
|
490
|
+
if (t.condition.trim() === 'always' && t.next_actions && t.next_actions.length > 0 && alwaysFallback === null) {
|
|
491
|
+
alwaysFallback = t.next_actions;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
return alwaysFallback;
|
|
495
|
+
}
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import type { SessionData } from '@energy8platform/game-sdk';
|
|
2
2
|
import type { TransitionRule } from './types';
|
|
3
3
|
|
|
4
|
+
/** Per-round history entry — wire shape mirrors server SessionInfo.History. */
|
|
5
|
+
export interface SessionRound {
|
|
6
|
+
spinIndex: number;
|
|
7
|
+
win: number;
|
|
8
|
+
data: Record<string, unknown>;
|
|
9
|
+
}
|
|
10
|
+
|
|
4
11
|
interface SessionState {
|
|
5
12
|
spinsRemaining: number;
|
|
6
13
|
spinsPlayed: number;
|
|
@@ -13,6 +20,7 @@ interface SessionState {
|
|
|
13
20
|
persistentVarNames: string[];
|
|
14
21
|
persistentVars: Record<string, number>;
|
|
15
22
|
persistentData: Record<string, unknown>;
|
|
23
|
+
history: SessionRound[];
|
|
16
24
|
}
|
|
17
25
|
|
|
18
26
|
const MAX_SESSION_SPINS = 200;
|
|
@@ -56,7 +64,8 @@ export class SessionManager {
|
|
|
56
64
|
|
|
57
65
|
/**
|
|
58
66
|
* Create a new session from a transition rule.
|
|
59
|
-
* Server behavior: initial spin is already counted (spinsPlayed=1, totalWin includes spinWin)
|
|
67
|
+
* Server behavior: initial spin is already counted (spinsPlayed=1, totalWin includes spinWin),
|
|
68
|
+
* and history[0] holds the trigger spin's data.
|
|
60
69
|
*/
|
|
61
70
|
createSession(
|
|
62
71
|
rule: TransitionRule,
|
|
@@ -64,6 +73,7 @@ export class SessionManager {
|
|
|
64
73
|
bet: number,
|
|
65
74
|
spinWin: number,
|
|
66
75
|
maxWinCap: number | undefined,
|
|
76
|
+
triggerData: Record<string, unknown>,
|
|
67
77
|
): SessionData {
|
|
68
78
|
let spinsRemaining = -1;
|
|
69
79
|
let spinsVarName: string | undefined;
|
|
@@ -90,6 +100,7 @@ export class SessionManager {
|
|
|
90
100
|
persistentVarNames,
|
|
91
101
|
persistentVars,
|
|
92
102
|
persistentData: {},
|
|
103
|
+
history: [{ spinIndex: 0, win: spinWin, data: triggerData }],
|
|
93
104
|
};
|
|
94
105
|
|
|
95
106
|
return this.toSessionData();
|
|
@@ -104,9 +115,18 @@ export class SessionManager {
|
|
|
104
115
|
rule: TransitionRule,
|
|
105
116
|
variables: Record<string, number>,
|
|
106
117
|
spinWin: number,
|
|
118
|
+
roundData: Record<string, unknown>,
|
|
107
119
|
): SessionData {
|
|
108
120
|
if (!this.session) throw new Error('No active session');
|
|
109
121
|
|
|
122
|
+
// Append history with PRE-increment spin index (matches server's
|
|
123
|
+
// updateSession: append round, THEN increment SpinsPlayed).
|
|
124
|
+
this.session.history.push({
|
|
125
|
+
spinIndex: this.session.spinsPlayed,
|
|
126
|
+
win: spinWin,
|
|
127
|
+
data: roundData,
|
|
128
|
+
});
|
|
129
|
+
|
|
110
130
|
// Accumulate win and count spin
|
|
111
131
|
this.session.totalWin += spinWin;
|
|
112
132
|
this.session.spinsPlayed++;
|
|
@@ -222,6 +242,8 @@ export class SessionManager {
|
|
|
222
242
|
completed: this.session.completed,
|
|
223
243
|
maxWinReached: this.session.maxWinReached,
|
|
224
244
|
betAmount: this.session.bet,
|
|
245
|
+
// Snapshot the array so downstream mutation can't corrupt internal state.
|
|
246
|
+
history: [...this.session.history],
|
|
225
247
|
};
|
|
226
248
|
}
|
|
227
249
|
}
|
package/src/lua/types.ts
CHANGED
|
@@ -11,6 +11,12 @@ export interface GameDefinition {
|
|
|
11
11
|
buy_bonus?: BuyBonusConfig;
|
|
12
12
|
ante_bet?: AnteBetConfig;
|
|
13
13
|
persistent_state?: PersistentStateConfig;
|
|
14
|
+
/**
|
|
15
|
+
* Session expiry duration as a Go-style duration string ("24h", "2h", "5ms").
|
|
16
|
+
* Mirrors the platform's GameDefinition.SessionTTL — defaults to 24h on
|
|
17
|
+
* the server when absent. Used by DevBridge to surface SESSION_EXPIRED.
|
|
18
|
+
*/
|
|
19
|
+
session_ttl?: string;
|
|
14
20
|
}
|
|
15
21
|
|
|
16
22
|
export interface BetLevelsConfig {
|