@energy8platform/game-engine 0.10.7 → 0.10.9

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.
@@ -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: creation, spin counting, retriggers, and completion.
17
- * Handles both slot sessions (fixed spin count) and table game sessions (unlimited).
18
- * Also manages _persist_ data roundtrip between Lua calls.
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
- /** Create a new session from a transition rule */
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; // unlimited by default
68
+ let spinsRemaining = -1;
69
+ let spinsVarName: string | undefined;
43
70
  if (rule.session_config?.total_spins_var) {
44
- const varName = rule.session_config.total_spins_var;
45
- spinsRemaining = variables[varName] ?? -1;
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
- if (rule.session_config?.persistent_vars) {
50
- for (const varName of rule.session_config.persistent_vars) {
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: 0,
58
- totalWin: 0,
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
- /** Update session after a spin: decrement counter, accumulate win, handle retrigger */
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
- // Update persistent vars
95
- if (this.session.persistentVars) {
96
- for (const key of Object.keys(this.session.persistentVars)) {
97
- if (key in variables) {
98
- this.session.persistentVars[key] = variables[key];
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
- // Auto-complete if spins exhausted
104
- if (this.session.spinsRemaining === 0) {
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
- /** Complete the session, return accumulated totalWin and list of session-scoped vars to clean up */
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 = Object.keys(this.session.persistentVars);
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 _ps_* params to inject into next execute() call */
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
  }