@energy8platform/game-engine 0.10.6 → 0.10.8
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/dist/debug.cjs.js +11 -7
- package/dist/debug.cjs.js.map +1 -1
- package/dist/debug.esm.js +11 -7
- package/dist/debug.esm.js.map +1 -1
- package/dist/index.cjs.js +11 -7
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.esm.js +11 -7
- package/dist/index.esm.js.map +1 -1
- package/dist/lua.cjs.js +182 -111
- package/dist/lua.cjs.js.map +1 -1
- package/dist/lua.d.ts +32 -27
- package/dist/lua.esm.js +182 -111
- package/dist/lua.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/debug/DevBridge.ts +12 -8
- package/src/lua/LuaEngine.ts +120 -67
- package/src/lua/SessionManager.ts +74 -29
- package/src/lua/SimulationRunner.ts +11 -19
- package/src/lua/types.ts +2 -0
|
@@ -8,14 +8,21 @@ interface SessionState {
|
|
|
8
8
|
completed: boolean;
|
|
9
9
|
maxWinReached: boolean;
|
|
10
10
|
bet: number;
|
|
11
|
+
maxWinCap: number | undefined;
|
|
12
|
+
spinsVarName: string | undefined;
|
|
13
|
+
persistentVarNames: string[];
|
|
11
14
|
persistentVars: Record<string, number>;
|
|
12
15
|
persistentData: Record<string, unknown>;
|
|
13
16
|
}
|
|
14
17
|
|
|
18
|
+
const MAX_SESSION_SPINS = 200;
|
|
19
|
+
|
|
15
20
|
/**
|
|
16
|
-
* Manages session lifecycle
|
|
17
|
-
*
|
|
18
|
-
*
|
|
21
|
+
* Manages session lifecycle matching the platform server behavior:
|
|
22
|
+
* - createSession: initial spin counted (spinsPlayed=1, totalWin=spinWin)
|
|
23
|
+
* - updateSession: accumulates win, decrements spins, checks max win cap on session level
|
|
24
|
+
* - completeSession: returns cumulative totalWin, cleans up session vars
|
|
25
|
+
* - Safety cap: 200 spins max per session
|
|
19
26
|
*/
|
|
20
27
|
export class SessionManager {
|
|
21
28
|
private session: SessionState | null = null;
|
|
@@ -33,32 +40,54 @@ export class SessionManager {
|
|
|
33
40
|
return this.session?.totalWin ?? 0;
|
|
34
41
|
}
|
|
35
42
|
|
|
36
|
-
/**
|
|
43
|
+
/** Get the fixed bet amount from the session (server uses session bet, not request bet) */
|
|
44
|
+
get sessionBet(): number | undefined {
|
|
45
|
+
return this.session?.bet;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Get spinsVarName to restore free_spins_remaining into variables */
|
|
49
|
+
get spinsVarName(): string | undefined {
|
|
50
|
+
return this.session?.spinsVarName;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
get spinsRemaining(): number {
|
|
54
|
+
return this.session?.spinsRemaining ?? 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Create a new session from a transition rule.
|
|
59
|
+
* Server behavior: initial spin is already counted (spinsPlayed=1, totalWin includes spinWin).
|
|
60
|
+
*/
|
|
37
61
|
createSession(
|
|
38
62
|
rule: TransitionRule,
|
|
39
63
|
variables: Record<string, number>,
|
|
40
64
|
bet: number,
|
|
65
|
+
spinWin: number,
|
|
66
|
+
maxWinCap: number | undefined,
|
|
41
67
|
): SessionData {
|
|
42
|
-
let spinsRemaining = -1;
|
|
68
|
+
let spinsRemaining = -1;
|
|
69
|
+
let spinsVarName: string | undefined;
|
|
43
70
|
if (rule.session_config?.total_spins_var) {
|
|
44
|
-
|
|
45
|
-
spinsRemaining = variables[
|
|
71
|
+
spinsVarName = rule.session_config.total_spins_var;
|
|
72
|
+
spinsRemaining = variables[spinsVarName] ?? -1;
|
|
46
73
|
}
|
|
47
74
|
|
|
75
|
+
const persistentVarNames: string[] = rule.session_config?.persistent_vars ?? [];
|
|
48
76
|
const persistentVars: Record<string, number> = {};
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
persistentVars[varName] = variables[varName] ?? 0;
|
|
52
|
-
}
|
|
77
|
+
for (const varName of persistentVarNames) {
|
|
78
|
+
persistentVars[varName] = variables[varName] ?? 0;
|
|
53
79
|
}
|
|
54
80
|
|
|
55
81
|
this.session = {
|
|
56
82
|
spinsRemaining,
|
|
57
|
-
spinsPlayed:
|
|
58
|
-
totalWin:
|
|
83
|
+
spinsPlayed: 1, // initial spin counts
|
|
84
|
+
totalWin: spinWin, // initial spin win included
|
|
59
85
|
completed: false,
|
|
60
86
|
maxWinReached: false,
|
|
61
87
|
bet,
|
|
88
|
+
maxWinCap,
|
|
89
|
+
spinsVarName,
|
|
90
|
+
persistentVarNames,
|
|
62
91
|
persistentVars,
|
|
63
92
|
persistentData: {},
|
|
64
93
|
};
|
|
@@ -66,7 +95,11 @@ export class SessionManager {
|
|
|
66
95
|
return this.toSessionData();
|
|
67
96
|
}
|
|
68
97
|
|
|
69
|
-
/**
|
|
98
|
+
/**
|
|
99
|
+
* Update session after a bonus spin.
|
|
100
|
+
* Server behavior: accumulate win, decrement spins, check retrigger, check max win cap,
|
|
101
|
+
* safety cap at 200 spins.
|
|
102
|
+
*/
|
|
70
103
|
updateSession(
|
|
71
104
|
rule: TransitionRule,
|
|
72
105
|
variables: Record<string, number>,
|
|
@@ -74,7 +107,7 @@ export class SessionManager {
|
|
|
74
107
|
): SessionData {
|
|
75
108
|
if (!this.session) throw new Error('No active session');
|
|
76
109
|
|
|
77
|
-
// Accumulate win
|
|
110
|
+
// Accumulate win and count spin
|
|
78
111
|
this.session.totalWin += spinWin;
|
|
79
112
|
this.session.spinsPlayed++;
|
|
80
113
|
|
|
@@ -91,33 +124,45 @@ export class SessionManager {
|
|
|
91
124
|
}
|
|
92
125
|
}
|
|
93
126
|
|
|
94
|
-
//
|
|
95
|
-
if (this.session.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
127
|
+
// Safety cap: server limits sessions to 200 spins
|
|
128
|
+
if (this.session.spinsPlayed >= MAX_SESSION_SPINS) {
|
|
129
|
+
this.session.spinsRemaining = 0;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Update session persistent vars from current variables
|
|
133
|
+
for (const varName of this.session.persistentVarNames) {
|
|
134
|
+
if (varName in variables) {
|
|
135
|
+
this.session.persistentVars[varName] = variables[varName];
|
|
100
136
|
}
|
|
101
137
|
}
|
|
102
138
|
|
|
103
|
-
//
|
|
104
|
-
if (this.session.
|
|
139
|
+
// Check max win cap (on session level, not per spin)
|
|
140
|
+
if (this.session.maxWinCap !== undefined && this.session.totalWin >= this.session.maxWinCap) {
|
|
141
|
+
this.session.totalWin = this.session.maxWinCap;
|
|
142
|
+
this.session.spinsRemaining = 0;
|
|
143
|
+
this.session.maxWinReached = true;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Auto-complete if spins exhausted or explicit complete
|
|
147
|
+
if (this.session.spinsRemaining === 0 || rule.complete_session) {
|
|
105
148
|
this.session.completed = true;
|
|
106
149
|
}
|
|
107
150
|
|
|
108
151
|
return this.toSessionData();
|
|
109
152
|
}
|
|
110
153
|
|
|
111
|
-
/**
|
|
154
|
+
/**
|
|
155
|
+
* Complete the session explicitly.
|
|
156
|
+
* Returns cumulative totalWin and list of session-scoped var names to clean up.
|
|
157
|
+
*/
|
|
112
158
|
completeSession(): { totalWin: number; session: SessionData; sessionVarNames: string[] } {
|
|
113
159
|
if (!this.session) throw new Error('No active session to complete');
|
|
114
160
|
|
|
115
161
|
this.session.completed = true;
|
|
116
162
|
const totalWin = this.session.totalWin;
|
|
117
163
|
const session = this.toSessionData();
|
|
118
|
-
const sessionVarNames =
|
|
164
|
+
const sessionVarNames = [...this.session.persistentVarNames];
|
|
119
165
|
|
|
120
|
-
// Clear session state so it doesn't leak into the next round
|
|
121
166
|
this.session = null;
|
|
122
167
|
|
|
123
168
|
return { totalWin, session, sessionVarNames };
|
|
@@ -143,18 +188,18 @@ export class SessionManager {
|
|
|
143
188
|
}
|
|
144
189
|
}
|
|
145
190
|
|
|
146
|
-
/** Get
|
|
191
|
+
/** Get persistent params to inject into next execute() call */
|
|
147
192
|
getPersistentParams(): Record<string, unknown> {
|
|
148
193
|
if (!this.session) return {};
|
|
149
194
|
|
|
150
195
|
const params: Record<string, unknown> = {};
|
|
151
196
|
|
|
152
|
-
// Session persistent vars (float64)
|
|
197
|
+
// Session persistent vars (float64) → state.variables
|
|
153
198
|
for (const [key, value] of Object.entries(this.session.persistentVars)) {
|
|
154
199
|
params[key] = value;
|
|
155
200
|
}
|
|
156
201
|
|
|
157
|
-
// _persist_ complex data → _ps_*
|
|
202
|
+
// _persist_ complex data → _ps_* in state.params
|
|
158
203
|
for (const [key, value] of Object.entries(this.session.persistentData)) {
|
|
159
204
|
params[`_ps_${key}`] = value;
|
|
160
205
|
}
|
|
@@ -44,7 +44,8 @@ export class SimulationRunner {
|
|
|
44
44
|
script,
|
|
45
45
|
gameDefinition,
|
|
46
46
|
seed,
|
|
47
|
-
logger: () => {},
|
|
47
|
+
logger: () => {},
|
|
48
|
+
simulationMode: true,
|
|
48
49
|
});
|
|
49
50
|
|
|
50
51
|
const spinCost = this.calculateSpinCost(startAction, bet, gameDefinition, params);
|
|
@@ -65,9 +66,10 @@ export class SimulationRunner {
|
|
|
65
66
|
for (let i = 0; i < iterations; i++) {
|
|
66
67
|
totalWagered += spinCost;
|
|
67
68
|
let roundWin = 0;
|
|
69
|
+
let roundBonusWin = 0;
|
|
68
70
|
|
|
69
71
|
// Execute the starting action
|
|
70
|
-
|
|
72
|
+
let result = engine.execute({
|
|
71
73
|
action: startAction,
|
|
72
74
|
bet,
|
|
73
75
|
params,
|
|
@@ -76,33 +78,23 @@ export class SimulationRunner {
|
|
|
76
78
|
const baseWin = result.totalWin;
|
|
77
79
|
roundWin += baseWin;
|
|
78
80
|
|
|
79
|
-
// If a
|
|
81
|
+
// If a session was created, play through it using nextActions from the engine
|
|
80
82
|
if (result.session && !result.session.completed) {
|
|
81
83
|
bonusTriggered++;
|
|
82
84
|
|
|
83
|
-
// Find the bonus action from nextActions (different from startAction)
|
|
84
|
-
const bonusAction = result.nextActions.find(a => a !== startAction)
|
|
85
|
-
?? result.nextActions[0];
|
|
86
|
-
|
|
87
|
-
// Play bonus spins until session completes
|
|
88
|
-
let bonusSessionWin = 0;
|
|
89
85
|
let safetyLimit = 10_000;
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
action: bonusAction,
|
|
95
|
-
bet,
|
|
96
|
-
});
|
|
97
|
-
bonusSessionWin += lastResult.totalWin;
|
|
86
|
+
while (result.session && !result.session.completed && safetyLimit-- > 0) {
|
|
87
|
+
const nextAction = result.nextActions[0];
|
|
88
|
+
result = engine.execute({ action: nextAction, bet });
|
|
89
|
+
roundBonusWin += result.totalWin;
|
|
98
90
|
bonusSpinsPlayed++;
|
|
99
91
|
}
|
|
100
92
|
|
|
101
|
-
|
|
102
|
-
roundWin += bonusSessionWin;
|
|
93
|
+
roundWin += roundBonusWin;
|
|
103
94
|
}
|
|
104
95
|
|
|
105
96
|
baseGameWin += baseWin;
|
|
97
|
+
bonusWin += roundBonusWin;
|
|
106
98
|
totalWon += roundWin;
|
|
107
99
|
|
|
108
100
|
if (roundWin > 0) hits++;
|
package/src/lua/types.ts
CHANGED
|
@@ -80,6 +80,8 @@ export interface LuaEngineConfig {
|
|
|
80
80
|
seed?: number;
|
|
81
81
|
/** Custom logger function */
|
|
82
82
|
logger?: (level: string, msg: string) => void;
|
|
83
|
+
/** Skip marshalling data fields (matrix, wins, etc.) for faster simulation */
|
|
84
|
+
simulationMode?: boolean;
|
|
83
85
|
}
|
|
84
86
|
|
|
85
87
|
export interface LuaPlayResult {
|