@omnitronix/bonnys-fortune-game-engine 1.7.2 → 1.8.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 (79) hide show
  1. package/dist/__tests__/base-game.test.js +17 -10
  2. package/dist/__tests__/base-game.test.js.map +1 -1
  3. package/dist/__tests__/bonus-handlers.test.js +15 -21
  4. package/dist/__tests__/bonus-handlers.test.js.map +1 -1
  5. package/dist/__tests__/bonus-meters.test.js.map +1 -1
  6. package/dist/__tests__/collect-feature-handler.test.js +30 -24
  7. package/dist/__tests__/collect-feature-handler.test.js.map +1 -1
  8. package/dist/__tests__/comprehensive.test.js.map +1 -1
  9. package/dist/__tests__/error-paths.test.js +11 -7
  10. package/dist/__tests__/error-paths.test.js.map +1 -1
  11. package/dist/__tests__/helpers/test-engine-factory.d.ts +72 -18
  12. package/dist/__tests__/helpers/test-engine-factory.js +32 -7
  13. package/dist/__tests__/helpers/test-engine-factory.js.map +1 -1
  14. package/dist/__tests__/integration-1000-spin.test.js +33 -37
  15. package/dist/__tests__/integration-1000-spin.test.js.map +1 -1
  16. package/dist/__tests__/rng-gli19-compliance.test.js.map +1 -1
  17. package/dist/__tests__/rng-security.test.js +0 -1
  18. package/dist/__tests__/rng-security.test.js.map +1 -1
  19. package/dist/__tests__/rtp-simulation.test.js +11 -10
  20. package/dist/__tests__/rtp-simulation.test.js.map +1 -1
  21. package/dist/__tests__/state-transitions.test.js +0 -1
  22. package/dist/__tests__/state-transitions.test.js.map +1 -1
  23. package/dist/__tests__/steering-fortune-handler.test.js +18 -14
  24. package/dist/__tests__/steering-fortune-handler.test.js.map +1 -1
  25. package/dist/__tests__/symbol-distribution.test.js +13 -16
  26. package/dist/__tests__/symbol-distribution.test.js.map +1 -1
  27. package/dist/__tests__/treasure-hunt-handler.test.js +23 -16
  28. package/dist/__tests__/treasure-hunt-handler.test.js.map +1 -1
  29. package/dist/__tests__/win-calculator.test.js +10 -7
  30. package/dist/__tests__/win-calculator.test.js.map +1 -1
  31. package/dist/bonnys-fortune-v1.game-engine.d.ts +3 -3
  32. package/dist/bonnys-fortune-v1.game-engine.js +8 -5
  33. package/dist/bonnys-fortune-v1.game-engine.js.map +1 -1
  34. package/dist/game-engine.interface.d.ts +1 -1
  35. package/dist/helpers/optional-boolean-mapper.d.ts +1 -1
  36. package/dist/helpers/validation-helper.d.ts +1 -1
  37. package/dist/logic/bonnys-fortune.game-logic.js +2 -0
  38. package/dist/logic/bonnys-fortune.game-logic.js.map +1 -1
  39. package/dist/logic/bonnys-fortune.types.d.ts +7 -1
  40. package/dist/logic/handlers/collect-feature-bonus.handler.js.map +1 -1
  41. package/dist/logic/handlers/steering-to-the-fortune-bonus.handler.js +6 -5
  42. package/dist/logic/handlers/steering-to-the-fortune-bonus.handler.js.map +1 -1
  43. package/dist/logic/handlers/treasure-hunt-bonus.handler.js.map +1 -1
  44. package/dist/rng/rng-client.factory.js +3 -3
  45. package/dist/rng/rng-client.factory.js.map +1 -1
  46. package/dist/validation/bonnys-fortune/bonnys-fortune-config.dto.js.map +1 -1
  47. package/dist/validation/custom-decorators/IsNestedIntArray.js.map +1 -1
  48. package/dist/validation/game-logic-config-validation.service.d.ts +1 -1
  49. package/dist/validation/game-logic-config-validation.service.js.map +1 -1
  50. package/package.json +3 -3
  51. package/src/__tests__/base-game.test.ts +26 -17
  52. package/src/__tests__/bonus-handlers.test.ts +16 -21
  53. package/src/__tests__/bonus-meters.test.ts +13 -8
  54. package/src/__tests__/collect-feature-handler.test.ts +39 -26
  55. package/src/__tests__/comprehensive.test.ts +40 -36
  56. package/src/__tests__/error-paths.test.ts +29 -18
  57. package/src/__tests__/helpers/test-engine-factory.ts +127 -40
  58. package/src/__tests__/integration-1000-spin.test.ts +53 -48
  59. package/src/__tests__/rng-gli19-compliance.test.ts +17 -17
  60. package/src/__tests__/rng-security.test.ts +11 -8
  61. package/src/__tests__/rtp-simulation.test.ts +17 -10
  62. package/src/__tests__/state-transitions.test.ts +8 -4
  63. package/src/__tests__/steering-fortune-handler.test.ts +30 -18
  64. package/src/__tests__/symbol-distribution.test.ts +19 -16
  65. package/src/__tests__/treasure-hunt-handler.test.ts +36 -22
  66. package/src/__tests__/win-calculator.test.ts +14 -10
  67. package/src/bonnys-fortune-v1.game-engine.ts +18 -8
  68. package/src/domain/types/game-symbols.response.dto.ts +1 -1
  69. package/src/game-engine.interface.ts +1 -1
  70. package/src/helpers/validation-helper.ts +1 -1
  71. package/src/logic/bonnys-fortune.game-logic.ts +3 -0
  72. package/src/logic/bonnys-fortune.types.ts +10 -1
  73. package/src/logic/handlers/collect-feature-bonus.handler.ts +5 -2
  74. package/src/logic/handlers/steering-to-the-fortune-bonus.handler.ts +7 -6
  75. package/src/logic/handlers/treasure-hunt-bonus.handler.ts +2 -2
  76. package/src/rng/rng-client.factory.ts +3 -3
  77. package/src/validation/bonnys-fortune/bonnys-fortune-config.dto.ts +72 -72
  78. package/src/validation/custom-decorators/IsNestedIntArray.ts +1 -1
  79. package/src/validation/game-logic-config-validation.service.ts +2 -2
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Base Game Logic Tests Bonnys Fortune
2
+ * Base Game Logic Tests -- Bonnys Fortune
3
3
  *
4
4
  * Tests the core base game mechanics:
5
5
  * - Screen generation (5x3 grid, 243 ways)
@@ -20,9 +20,11 @@ import {
20
20
  nextCommandId,
21
21
  resetCommandIdCounter,
22
22
  validateRngOutcome,
23
+ getOutcome,
23
24
  } from './helpers/test-engine-factory';
25
+ import { MeterState } from '../logic/bonnys-fortune.types';
24
26
 
25
- describe('Base Game Logic Bonnys Fortune', () => {
27
+ describe('Base Game Logic -- Bonnys Fortune', () => {
26
28
  beforeEach(() => {
27
29
  resetCommandIdCounter();
28
30
  });
@@ -35,10 +37,11 @@ describe('Base Game Logic — Bonnys Fortune', () => {
35
37
  const result = await executeSpin(engine, session.publicState, session.privateState);
36
38
  expect(result.success).toBe(true);
37
39
 
38
- const screen = result.outcome?.result?.screen ?? result.outcome?.screen;
40
+ const outcome = getOutcome(result);
41
+ const screen = outcome.result?.screen ?? outcome.screen;
39
42
  expect(screen).toBeDefined();
40
- expect(screen.length).toBe(5);
41
- for (const reel of screen) {
43
+ expect(screen!.length).toBe(5);
44
+ for (const reel of screen!) {
42
45
  expect(reel.length).toBe(3);
43
46
  }
44
47
  });
@@ -50,8 +53,9 @@ describe('Base Game Logic — Bonnys Fortune', () => {
50
53
  const result = await executeSpin(engine, session.publicState, session.privateState);
51
54
  expect(result.success).toBe(true);
52
55
 
53
- const screen = result.outcome?.result?.screen ?? result.outcome?.screen;
54
- for (const reel of screen) {
56
+ const outcome = getOutcome(result);
57
+ const screen = outcome.result?.screen ?? outcome.screen;
58
+ for (const reel of screen!) {
55
59
  for (const symbol of reel) {
56
60
  expect(typeof symbol).toBe('number');
57
61
  expect(symbol).toBeGreaterThanOrEqual(0);
@@ -71,7 +75,7 @@ describe('Base Game Logic — Bonnys Fortune', () => {
71
75
 
72
76
  for (let i = 0; i < 50; i++) {
73
77
  if (priv.nextSpinType !== 'BASE_GAME_SPIN') {
74
- // Bonus triggered complete it via extractFinalBonusState
78
+ // Bonus triggered -- complete it via extractFinalBonusState
75
79
  if (priv.pendingBonuses?.length > 0) {
76
80
  const bonus = priv.pendingBonuses[0];
77
81
  const bonusResult = await startBonusRound(engine, pub, priv, bonus.bonusType);
@@ -92,7 +96,8 @@ describe('Base Game Logic — Bonnys Fortune', () => {
92
96
 
93
97
  const result = await executeSpin(engine, pub, priv);
94
98
  expect(result.success).toBe(true);
95
- const screen = result.outcome?.result?.screen ?? result.outcome?.screen ?? result.privateState.screen;
99
+ const outcome = getOutcome(result);
100
+ const screen = outcome.result?.screen ?? outcome.screen ?? result.privateState.screen;
96
101
  screens.push(JSON.stringify(screen));
97
102
 
98
103
  pub = result.publicState;
@@ -112,7 +117,8 @@ describe('Base Game Logic — Bonnys Fortune', () => {
112
117
  const result = await executeSpin(engine, session.publicState, session.privateState);
113
118
  expect(result.success).toBe(true);
114
119
 
115
- const playerWinning = result.outcome?.result?.playerWinning ?? result.outcome?.playerWinning;
120
+ const outcome = getOutcome(result);
121
+ const playerWinning = outcome.result?.playerWinning ?? outcome.playerWinning;
116
122
  if (playerWinning !== undefined) {
117
123
  expect(playerWinning).toBeGreaterThanOrEqual(0);
118
124
  expect(typeof playerWinning).toBe('number');
@@ -132,7 +138,8 @@ describe('Base Game Logic — Bonnys Fortune', () => {
132
138
  const result = await executeSpin(engine, pub, priv);
133
139
  expect(result.success).toBe(true);
134
140
 
135
- const playerWinning = result.outcome?.result?.playerWinning ?? result.outcome?.playerWinning;
141
+ const outcome = getOutcome(result);
142
+ const playerWinning = outcome.result?.playerWinning ?? outcome.playerWinning;
136
143
  if (playerWinning !== undefined) {
137
144
  expect(Number.isFinite(playerWinning)).toBe(true);
138
145
  }
@@ -159,7 +166,7 @@ describe('Base Game Logic — Bonnys Fortune', () => {
159
166
 
160
167
  const meters = session.publicState.bonusMeters;
161
168
  for (const [, meter] of Object.entries(meters || {})) {
162
- const m = meter as any;
169
+ const m = meter as MeterState;
163
170
  expect(m.threshold).toBeGreaterThan(0);
164
171
  expect(m.progress).toBeGreaterThanOrEqual(0);
165
172
  expect(typeof m.threshold).toBe('number');
@@ -183,7 +190,7 @@ describe('Base Game Logic — Bonnys Fortune', () => {
183
190
 
184
191
  // Verify meter structure integrity
185
192
  for (const [, meter] of Object.entries(result.publicState.bonusMeters || {})) {
186
- const m = meter as any;
193
+ const m = meter as MeterState;
187
194
  expect(m.threshold).toBeGreaterThan(0);
188
195
  expect(m.progress).toBeGreaterThanOrEqual(0);
189
196
  expect(m.progress).toBeLessThanOrEqual(m.threshold);
@@ -208,7 +215,7 @@ describe('Base Game Logic — Bonnys Fortune', () => {
208
215
  expect(result.success).toBe(true);
209
216
 
210
217
  for (const [, meter] of Object.entries(result.publicState.bonusMeters || {})) {
211
- const m = meter as any;
218
+ const m = meter as MeterState;
212
219
  expect(m.progress).toBeLessThanOrEqual(m.threshold);
213
220
  }
214
221
 
@@ -236,7 +243,7 @@ describe('Base Game Logic — Bonnys Fortune', () => {
236
243
  expect(result.success).toBe(true);
237
244
  expect(result.rngOutcome).toBeDefined();
238
245
 
239
- const validation = validateRngOutcome(result.rngOutcome);
246
+ const validation = validateRngOutcome(result.rngOutcome!);
240
247
  expect(validation.valid).toBe(true);
241
248
  });
242
249
  });
@@ -288,8 +295,10 @@ describe('Base Game Logic — Bonnys Fortune', () => {
288
295
  expect(result1.success).toBe(true);
289
296
  expect(result2.success).toBe(true);
290
297
 
291
- const screen1 = JSON.stringify(result1.outcome?.screen || result1.privateState.screen);
292
- const screen2 = JSON.stringify(result2.outcome?.screen || result2.privateState.screen);
298
+ const outcome1 = getOutcome(result1);
299
+ const outcome2 = getOutcome(result2);
300
+ const screen1 = JSON.stringify(outcome1.screen || result1.privateState.screen);
301
+ const screen2 = JSON.stringify(outcome2.screen || result2.privateState.screen);
293
302
  expect(screen1).toBe(screen2);
294
303
  });
295
304
  });
@@ -18,6 +18,7 @@ import {
18
18
  debugTriggerBonus,
19
19
  nextCommandId,
20
20
  resetCommandIdCounter,
21
+ getOutcome,
21
22
  } from './helpers/test-engine-factory';
22
23
 
23
24
  describe('Bonus Handlers — Bonnys Fortune', () => {
@@ -93,13 +94,11 @@ describe('Bonus Handlers — Bonnys Fortune', () => {
93
94
 
94
95
  expect(bonusResult.success).toBe(true);
95
96
 
96
- if (bonusResult.outcome) {
97
- const outcomes = Array.isArray(bonusResult.outcome)
98
- ? bonusResult.outcome
99
- : [bonusResult.outcome];
100
- for (const o of outcomes) {
101
- if (o.playerWinning !== undefined) {
102
- expect(o.playerWinning).toBeGreaterThanOrEqual(0);
97
+ const outcome = getOutcome(bonusResult);
98
+ if (outcome.results) {
99
+ for (const r of outcome.results) {
100
+ if (r.result?.playerWinning !== undefined) {
101
+ expect(r.result.playerWinning).toBeGreaterThanOrEqual(0);
103
102
  }
104
103
  }
105
104
  }
@@ -201,13 +200,11 @@ describe('Bonus Handlers — Bonnys Fortune', () => {
201
200
 
202
201
  expect(bonusResult.success).toBe(true);
203
202
 
204
- if (bonusResult.outcome) {
205
- const outcomes = Array.isArray(bonusResult.outcome)
206
- ? bonusResult.outcome
207
- : [bonusResult.outcome];
208
- for (const o of outcomes) {
209
- if (o.playerWinning !== undefined) {
210
- expect(o.playerWinning).toBeGreaterThanOrEqual(0);
203
+ const outcome = getOutcome(bonusResult);
204
+ if (outcome.results) {
205
+ for (const r of outcome.results) {
206
+ if (r.result?.playerWinning !== undefined) {
207
+ expect(r.result.playerWinning).toBeGreaterThanOrEqual(0);
211
208
  }
212
209
  }
213
210
  }
@@ -364,13 +361,11 @@ describe('Bonus Handlers — Bonnys Fortune', () => {
364
361
 
365
362
  expect(bonusResult.success).toBe(true);
366
363
 
367
- if (bonusResult.outcome) {
368
- const outcomes = Array.isArray(bonusResult.outcome)
369
- ? bonusResult.outcome
370
- : [bonusResult.outcome];
371
- for (const o of outcomes) {
372
- if (o.playerWinning !== undefined) {
373
- expect(o.playerWinning).toBeGreaterThanOrEqual(0);
364
+ const outcome = getOutcome(bonusResult);
365
+ if (outcome.results) {
366
+ for (const r of outcome.results) {
367
+ if (r.result?.playerWinning !== undefined) {
368
+ expect(r.result.playerWinning).toBeGreaterThanOrEqual(0);
374
369
  }
375
370
  }
376
371
  }
@@ -22,9 +22,14 @@ import {
22
22
  resetCommandIdCounter,
23
23
  MockRngClient,
24
24
  injectMockRng,
25
+ BonnysFortuneCommandResult,
25
26
  } from './helpers/test-engine-factory';
26
27
  import { gameLogicConfig } from '../config/game-logic-config/game-logic-config';
27
- import { MeterState } from '../logic/bonnys-fortune.types';
28
+ import {
29
+ MeterState,
30
+ BonnysFortunePublicState,
31
+ BonnysFortunePrivateState,
32
+ } from '../logic/bonnys-fortune.types';
28
33
  import { BonnysFortuneV1GameEngine } from '../bonnys-fortune-v1.game-engine';
29
34
 
30
35
  /**
@@ -33,11 +38,11 @@ import { BonnysFortuneV1GameEngine } from '../bonnys-fortune-v1.game-engine';
33
38
  */
34
39
  async function updateBonusMeterProgress(
35
40
  engine: BonnysFortuneV1GameEngine,
36
- publicState: any,
37
- privateState: any,
41
+ publicState: BonnysFortunePublicState,
42
+ privateState: BonnysFortunePrivateState,
38
43
  bonusType: string,
39
44
  progress: number,
40
- ): Promise<any> {
45
+ ): Promise<BonnysFortuneCommandResult> {
41
46
  return engine.processCommand(publicState, privateState, {
42
47
  id: nextCommandId('debug-meter'),
43
48
  type: 'DEBUG_UPDATE_BONUS_METER_PROGRESS',
@@ -335,12 +340,12 @@ describe('Bonus Meters - Bonnys Fortune', () => {
335
340
  expect(result.success).toBe(true);
336
341
 
337
342
  // Meters for bet amount 2.0 should be unchanged (thresholds same)
338
- const afterMeters2Thresholds = Object.values(
343
+ const afterMeters2Thresholds = (Object.values(
339
344
  result.privateState.bonusMetersByAmount[2.0] || {},
340
- ).map((m: any) => m.threshold);
341
- const initialMeters2Thresholds = Object.values(
345
+ ) as MeterState[]).map((m) => m.threshold);
346
+ const initialMeters2Thresholds = (Object.values(
342
347
  JSON.parse(initialMeters2) || {},
343
- ).map((m: any) => m.threshold);
348
+ ) as MeterState[]).map((m) => m.threshold);
344
349
 
345
350
  expect(afterMeters2Thresholds).toEqual(initialMeters2Thresholds);
346
351
  }
@@ -19,8 +19,19 @@ import {
19
19
  nextCommandId,
20
20
  resetCommandIdCounter,
21
21
  MockRngClient,
22
+ BonnysFortuneCommandResult,
22
23
  } from './helpers/test-engine-factory';
23
- import { SpinType } from '../logic/bonnys-fortune.types';
24
+ import {
25
+ SpinType,
26
+ SpinResultResponse,
27
+ CollectFeatureSpinResult,
28
+ BonusRoundResult,
29
+ } from '../logic/bonnys-fortune.types';
30
+
31
+ /** Helper to extract collect feature results from a bonus command result */
32
+ function getCollectResults(result: BonnysFortuneCommandResult): SpinResultResponse<CollectFeatureSpinResult>[] {
33
+ return (result.outcome as unknown as BonusRoundResult<CollectFeatureSpinResult>).results;
34
+ }
24
35
 
25
36
  describe('Collect Feature Handler - Bonnys Fortune', () => {
26
37
  beforeEach(() => {
@@ -50,11 +61,12 @@ describe('Collect Feature Handler - Bonnys Fortune', () => {
50
61
  );
51
62
 
52
63
  expect(bonusResult.success).toBe(true);
53
- expect(bonusResult.outcome?.results).toBeDefined();
54
- expect(bonusResult.outcome.results.length).toBeGreaterThan(0);
64
+ const results = getCollectResults(bonusResult);
65
+ expect(results).toBeDefined();
66
+ expect(results.length).toBeGreaterThan(0);
55
67
 
56
68
  // Each spin should have symbols array
57
- for (const spin of bonusResult.outcome.results) {
69
+ for (const spin of results) {
58
70
  expect(spin.result.symbols).toBeDefined();
59
71
  expect(Array.isArray(spin.result.symbols)).toBe(true);
60
72
  }
@@ -84,7 +96,8 @@ describe('Collect Feature Handler - Bonnys Fortune', () => {
84
96
  expect(bonusResult.success).toBe(true);
85
97
 
86
98
  // Check symbol naming convention
87
- for (const spin of bonusResult.outcome.results) {
99
+ const results = getCollectResults(bonusResult);
100
+ for (const spin of results) {
88
101
  for (const symbol of spin.result.symbols) {
89
102
  // Symbols should follow CV (value) or CM (multiplier) pattern
90
103
  expect(symbol).toMatch(/^(CV|CM)\d+$/);
@@ -115,7 +128,7 @@ describe('Collect Feature Handler - Bonnys Fortune', () => {
115
128
 
116
129
  expect(bonusResult.success).toBe(true);
117
130
 
118
- for (const spin of bonusResult.outcome.results) {
131
+ for (const spin of getCollectResults(bonusResult)) {
119
132
  expect(spin.result.slashed).toBeDefined();
120
133
  expect(Array.isArray(spin.result.slashed)).toBe(true);
121
134
  // slashed should match symbols length
@@ -151,8 +164,8 @@ describe('Collect Feature Handler - Bonnys Fortune', () => {
151
164
  expect(bonusResult.success).toBe(true);
152
165
 
153
166
  // Verify sequential spinIndex
154
- for (let i = 0; i < bonusResult.outcome.results.length; i++) {
155
- expect(bonusResult.outcome.results[i].result.spinIndex).toBe(i);
167
+ for (let i = 0; i < getCollectResults(bonusResult).length; i++) {
168
+ expect(getCollectResults(bonusResult)[i].result.spinIndex).toBe(i);
156
169
  }
157
170
  });
158
171
  });
@@ -181,7 +194,7 @@ describe('Collect Feature Handler - Bonnys Fortune', () => {
181
194
 
182
195
  expect(bonusResult.success).toBe(true);
183
196
 
184
- for (const spin of bonusResult.outcome.results) {
197
+ for (const spin of getCollectResults(bonusResult)) {
185
198
  expect(spin.result.currentValues).toBeDefined();
186
199
  expect(typeof spin.result.currentValues).toBe('number');
187
200
  expect(spin.result.currentValues).toBeGreaterThanOrEqual(0);
@@ -213,7 +226,7 @@ describe('Collect Feature Handler - Bonnys Fortune', () => {
213
226
 
214
227
  // totalValues should accumulate monotonically
215
228
  let prevTotal = 0;
216
- for (const spin of bonusResult.outcome.results) {
229
+ for (const spin of getCollectResults(bonusResult)) {
217
230
  expect(spin.result.totalValues).toBeGreaterThanOrEqual(prevTotal);
218
231
  prevTotal = spin.result.totalValues;
219
232
  }
@@ -244,7 +257,7 @@ describe('Collect Feature Handler - Bonnys Fortune', () => {
244
257
 
245
258
  // Verify accumulation
246
259
  let calculatedTotal = 0;
247
- for (const spin of bonusResult.outcome.results) {
260
+ for (const spin of getCollectResults(bonusResult)) {
248
261
  calculatedTotal += spin.result.currentValues;
249
262
  expect(spin.result.totalValues).toBeCloseTo(calculatedTotal, 2);
250
263
  }
@@ -275,7 +288,7 @@ describe('Collect Feature Handler - Bonnys Fortune', () => {
275
288
 
276
289
  expect(bonusResult.success).toBe(true);
277
290
 
278
- for (const spin of bonusResult.outcome.results) {
291
+ for (const spin of getCollectResults(bonusResult)) {
279
292
  expect(spin.result.currentMultipliers).toBeDefined();
280
293
  expect(typeof spin.result.currentMultipliers).toBe('number');
281
294
  expect(spin.result.currentMultipliers).toBeGreaterThanOrEqual(0);
@@ -307,7 +320,7 @@ describe('Collect Feature Handler - Bonnys Fortune', () => {
307
320
 
308
321
  // totalMultipliers should accumulate or stay constant
309
322
  let prevTotal = 0;
310
- for (const spin of bonusResult.outcome.results) {
323
+ for (const spin of getCollectResults(bonusResult)) {
311
324
  expect(spin.result.totalMultipliers).toBeGreaterThanOrEqual(prevTotal);
312
325
  prevTotal = spin.result.totalMultipliers;
313
326
  }
@@ -337,7 +350,7 @@ describe('Collect Feature Handler - Bonnys Fortune', () => {
337
350
  expect(bonusResult.success).toBe(true);
338
351
 
339
352
  // First spin should have at least 1 as base multiplier
340
- const firstSpin = bonusResult.outcome.results[0];
353
+ const firstSpin = getCollectResults(bonusResult)[0];
341
354
  expect(firstSpin.result.totalMultipliers).toBeGreaterThanOrEqual(1);
342
355
  });
343
356
  });
@@ -367,8 +380,8 @@ describe('Collect Feature Handler - Bonnys Fortune', () => {
367
380
  expect(bonusResult.success).toBe(true);
368
381
 
369
382
  // spinsLeft should decrement
370
- for (let i = 0; i < bonusResult.outcome.results.length; i++) {
371
- const spin = bonusResult.outcome.results[i];
383
+ for (let i = 0; i < getCollectResults(bonusResult).length; i++) {
384
+ const spin = getCollectResults(bonusResult)[i];
372
385
  expect(spin.result.spinsLeft).toBeDefined();
373
386
  expect(spin.result.spinsLeft).toBeGreaterThanOrEqual(0);
374
387
  }
@@ -398,7 +411,7 @@ describe('Collect Feature Handler - Bonnys Fortune', () => {
398
411
  expect(bonusResult.success).toBe(true);
399
412
 
400
413
  const lastSpin =
401
- bonusResult.outcome.results[bonusResult.outcome.results.length - 1];
414
+ getCollectResults(bonusResult)[getCollectResults(bonusResult).length - 1];
402
415
  expect(lastSpin.result.spinsLeft).toBe(0);
403
416
  expect(lastSpin.result.nextSpinType).toBe(SpinType.BASE_GAME_SPIN);
404
417
  });
@@ -427,8 +440,8 @@ describe('Collect Feature Handler - Bonnys Fortune', () => {
427
440
  expect(bonusResult.success).toBe(true);
428
441
 
429
442
  // All spins except last should have COLLECT_FEATURE_SPIN as next
430
- for (let i = 0; i < bonusResult.outcome.results.length - 1; i++) {
431
- const spin = bonusResult.outcome.results[i];
443
+ for (let i = 0; i < getCollectResults(bonusResult).length - 1; i++) {
444
+ const spin = getCollectResults(bonusResult)[i];
432
445
  if (spin.result.spinsLeft > 0) {
433
446
  expect(spin.result.nextSpinType).toBe(SpinType.COLLECT_FEATURE_SPIN);
434
447
  }
@@ -460,7 +473,7 @@ describe('Collect Feature Handler - Bonnys Fortune', () => {
460
473
 
461
474
  expect(bonusResult.success).toBe(true);
462
475
 
463
- for (const spin of bonusResult.outcome.results) {
476
+ for (const spin of getCollectResults(bonusResult)) {
464
477
  expect(spin.result.playerWinning).toBeDefined();
465
478
  expect(typeof spin.result.playerWinning).toBe('number');
466
479
  }
@@ -491,7 +504,7 @@ describe('Collect Feature Handler - Bonnys Fortune', () => {
491
504
 
492
505
  // totalWin should accumulate
493
506
  let prevTotal = 0;
494
- for (const spin of bonusResult.outcome.results) {
507
+ for (const spin of getCollectResults(bonusResult)) {
495
508
  expect(spin.result.totalWin).toBeGreaterThanOrEqual(prevTotal);
496
509
  prevTotal = spin.result.totalWin;
497
510
  }
@@ -526,7 +539,7 @@ describe('Collect Feature Handler - Bonnys Fortune', () => {
526
539
  // totalWin should be proportional to totalValues * totalMultipliers
527
540
  // (actual formula includes betStake derived from config)
528
541
  const lastSpin =
529
- bonusResult.outcome.results[bonusResult.outcome.results.length - 1];
542
+ getCollectResults(bonusResult)[getCollectResults(bonusResult).length - 1];
530
543
  const baseProduct =
531
544
  lastSpin.result.totalValues * lastSpin.result.totalMultipliers;
532
545
 
@@ -565,7 +578,7 @@ describe('Collect Feature Handler - Bonnys Fortune', () => {
565
578
  expect(bonusResult.success).toBe(true);
566
579
 
567
580
  const lastSpin =
568
- bonusResult.outcome.results[bonusResult.outcome.results.length - 1];
581
+ getCollectResults(bonusResult)[getCollectResults(bonusResult).length - 1];
569
582
  expect(lastSpin.result.totalWin).toBeGreaterThanOrEqual(0);
570
583
  });
571
584
 
@@ -599,7 +612,7 @@ describe('Collect Feature Handler - Bonnys Fortune', () => {
599
612
 
600
613
  if (bonusResult.success) {
601
614
  const lastSpin =
602
- bonusResult.outcome.results[bonusResult.outcome.results.length - 1];
615
+ getCollectResults(bonusResult)[getCollectResults(bonusResult).length - 1];
603
616
  totalWins.push(lastSpin.result.totalWin);
604
617
  }
605
618
  }
@@ -696,7 +709,7 @@ describe('Collect Feature Handler - Bonnys Fortune', () => {
696
709
 
697
710
  describe('RNG Determinism', () => {
698
711
  it('should produce consistent results with same LCG seed', async () => {
699
- const results: any[] = [];
712
+ const results: SpinResultResponse<CollectFeatureSpinResult>[][] = [];
700
713
 
701
714
  for (let i = 0; i < 2; i++) {
702
715
  resetCommandIdCounter();
@@ -720,7 +733,7 @@ describe('Collect Feature Handler - Bonnys Fortune', () => {
720
733
  bonus.bonusType,
721
734
  );
722
735
 
723
- results.push(bonusResult.outcome.results);
736
+ results.push(getCollectResults(bonusResult));
724
737
  }
725
738
 
726
739
  expect(results.length).toBe(2);
@@ -16,6 +16,10 @@
16
16
 
17
17
  import { BonnysFortuneV1GameEngine } from '../bonnys-fortune-v1.game-engine';
18
18
  import { GameActionCommand } from '../game-engine.interface';
19
+ import {
20
+ BonnysFortunePublicState,
21
+ BonnysFortunePrivateState,
22
+ } from '../logic/bonnys-fortune.types';
19
23
 
20
24
  describe("Bonny's Fortune - Comprehensive Test Suite", () => {
21
25
  let engine: BonnysFortuneV1GameEngine;
@@ -38,7 +42,7 @@ describe("Bonny's Fortune - Comprehensive Test Suite", () => {
38
42
  },
39
43
  };
40
44
 
41
- const result = await engine.processCommand(null, null, command);
45
+ const result = await engine.processCommand(null as unknown as BonnysFortunePublicState, null as unknown as BonnysFortunePrivateState, command);
42
46
 
43
47
  expect(result.success).toBe(true);
44
48
  expect(result.publicState).toBeDefined();
@@ -46,7 +50,7 @@ describe("Bonny's Fortune - Comprehensive Test Suite", () => {
46
50
  });
47
51
 
48
52
  it('should initialize with bonus meters', async () => {
49
- const result = await engine.processCommand(null, null, {
53
+ const result = await engine.processCommand(null as unknown as BonnysFortunePublicState, null as unknown as BonnysFortunePrivateState, {
50
54
  id: 'init-meters',
51
55
  type: 'INIT_SESSION_STATE',
52
56
  payload: { betAmount: 1.0 },
@@ -60,7 +64,7 @@ describe("Bonny's Fortune - Comprehensive Test Suite", () => {
60
64
  const betAmounts = [0.5, 1.0, 2.0, 5.0, 10.0];
61
65
 
62
66
  for (const betAmount of betAmounts) {
63
- const result = await engine.processCommand(null, null, {
67
+ const result = await engine.processCommand(null as unknown as BonnysFortunePublicState, null as unknown as BonnysFortunePrivateState, {
64
68
  id: `init-${betAmount}`,
65
69
  type: 'INIT_SESSION_STATE',
66
70
  payload: { betAmount },
@@ -84,11 +88,11 @@ describe("Bonny's Fortune - Comprehensive Test Suite", () => {
84
88
  // BASE GAME SPINS
85
89
  // ============================================================
86
90
  describe('Base Game Spins', () => {
87
- let publicState: any;
88
- let privateState: any;
91
+ let publicState: BonnysFortunePublicState;
92
+ let privateState: BonnysFortunePrivateState;
89
93
 
90
94
  beforeEach(async () => {
91
- const initResult = await engine.processCommand(null, null, {
95
+ const initResult = await engine.processCommand(null as unknown as BonnysFortunePublicState, null as unknown as BonnysFortunePrivateState, {
92
96
  id: 'init',
93
97
  type: 'INIT_SESSION_STATE',
94
98
  payload: { betAmount: 1.0 },
@@ -176,11 +180,11 @@ describe("Bonny's Fortune - Comprehensive Test Suite", () => {
176
180
  // BONUS GAME 1 - TREASURE HUNT
177
181
  // ============================================================
178
182
  describe('bonusGame1 (Treasure Hunt) Flow', () => {
179
- let publicState: any;
180
- let privateState: any;
183
+ let publicState: BonnysFortunePublicState;
184
+ let privateState: BonnysFortunePrivateState;
181
185
 
182
186
  beforeEach(async () => {
183
- const initResult = await engine.processCommand(null, null, {
187
+ const initResult = await engine.processCommand(null as unknown as BonnysFortunePublicState, null as unknown as BonnysFortunePrivateState, {
184
188
  id: 'init',
185
189
  type: 'INIT_SESSION_STATE',
186
190
  payload: { betAmount: 1.0 },
@@ -262,11 +266,11 @@ describe("Bonny's Fortune - Comprehensive Test Suite", () => {
262
266
  // BONUS GAME 2 - FREE SPINS
263
267
  // ============================================================
264
268
  describe('bonusGame2 (Free Spins) Flow', () => {
265
- let publicState: any;
266
- let privateState: any;
269
+ let publicState: BonnysFortunePublicState;
270
+ let privateState: BonnysFortunePrivateState;
267
271
 
268
272
  beforeEach(async () => {
269
- const initResult = await engine.processCommand(null, null, {
273
+ const initResult = await engine.processCommand(null as unknown as BonnysFortunePublicState, null as unknown as BonnysFortunePrivateState, {
270
274
  id: 'init',
271
275
  type: 'INIT_SESSION_STATE',
272
276
  payload: { betAmount: 1.0 },
@@ -327,11 +331,11 @@ describe("Bonny's Fortune - Comprehensive Test Suite", () => {
327
331
  // BONUS GAME 3 - STEERING TO THE FORTUNE
328
332
  // ============================================================
329
333
  describe('bonusGame3 (Steering to the Fortune) Flow', () => {
330
- let publicState: any;
331
- let privateState: any;
334
+ let publicState: BonnysFortunePublicState;
335
+ let privateState: BonnysFortunePrivateState;
332
336
 
333
337
  beforeEach(async () => {
334
- const initResult = await engine.processCommand(null, null, {
338
+ const initResult = await engine.processCommand(null as unknown as BonnysFortunePublicState, null as unknown as BonnysFortunePrivateState, {
335
339
  id: 'init',
336
340
  type: 'INIT_SESSION_STATE',
337
341
  payload: { betAmount: 1.0 },
@@ -392,11 +396,11 @@ describe("Bonny's Fortune - Comprehensive Test Suite", () => {
392
396
  // COLLECT FEATURE
393
397
  // ============================================================
394
398
  describe('collectFeature Flow', () => {
395
- let publicState: any;
396
- let privateState: any;
399
+ let publicState: BonnysFortunePublicState;
400
+ let privateState: BonnysFortunePrivateState;
397
401
 
398
402
  beforeEach(async () => {
399
- const initResult = await engine.processCommand(null, null, {
403
+ const initResult = await engine.processCommand(null as unknown as BonnysFortunePublicState, null as unknown as BonnysFortunePrivateState, {
400
404
  id: 'init',
401
405
  type: 'INIT_SESSION_STATE',
402
406
  payload: { betAmount: 1.0 },
@@ -423,7 +427,7 @@ describe("Bonny's Fortune - Comprehensive Test Suite", () => {
423
427
  const betAmounts = [1.0, 2.0, 5.0];
424
428
 
425
429
  for (const betAmount of betAmounts) {
426
- const init = await engine.processCommand(null, null, {
430
+ const init = await engine.processCommand(null as unknown as BonnysFortunePublicState, null as unknown as BonnysFortunePrivateState, {
427
431
  id: `init-${betAmount}`,
428
432
  type: 'INIT_SESSION_STATE',
429
433
  payload: { betAmount },
@@ -452,11 +456,11 @@ describe("Bonny's Fortune - Comprehensive Test Suite", () => {
452
456
  // BONUS METER MANIPULATION
453
457
  // ============================================================
454
458
  describe('Bonus Meter Manipulation', () => {
455
- let publicState: any;
456
- let privateState: any;
459
+ let publicState: BonnysFortunePublicState;
460
+ let privateState: BonnysFortunePrivateState;
457
461
 
458
462
  beforeEach(async () => {
459
- const initResult = await engine.processCommand(null, null, {
463
+ const initResult = await engine.processCommand(null as unknown as BonnysFortunePublicState, null as unknown as BonnysFortunePrivateState, {
460
464
  id: 'init',
461
465
  type: 'INIT_SESSION_STATE',
462
466
  payload: { betAmount: 1.0 },
@@ -509,11 +513,11 @@ describe("Bonny's Fortune - Comprehensive Test Suite", () => {
509
513
  // SPIN/BONUS COMBINATIONS
510
514
  // ============================================================
511
515
  describe('Spin/Bonus Combinations', () => {
512
- let publicState: any;
513
- let privateState: any;
516
+ let publicState: BonnysFortunePublicState;
517
+ let privateState: BonnysFortunePrivateState;
514
518
 
515
519
  beforeEach(async () => {
516
- const initResult = await engine.processCommand(null, null, {
520
+ const initResult = await engine.processCommand(null as unknown as BonnysFortunePublicState, null as unknown as BonnysFortunePrivateState, {
517
521
  id: 'init',
518
522
  type: 'INIT_SESSION_STATE',
519
523
  payload: { betAmount: 1.0 },
@@ -653,7 +657,7 @@ describe("Bonny's Fortune - Comprehensive Test Suite", () => {
653
657
  ];
654
658
 
655
659
  for (const { betAmount, bonusType } of configs) {
656
- const init = await engine.processCommand(null, null, {
660
+ const init = await engine.processCommand(null as unknown as BonnysFortunePublicState, null as unknown as BonnysFortunePrivateState, {
657
661
  id: `init-${betAmount}`,
658
662
  type: 'INIT_SESSION_STATE',
659
663
  payload: { betAmount },
@@ -697,11 +701,11 @@ describe("Bonny's Fortune - Comprehensive Test Suite", () => {
697
701
  // DEBUG TRIGGER ERROR CASES
698
702
  // ============================================================
699
703
  describe('Debug Trigger Error Cases', () => {
700
- let publicState: any;
701
- let privateState: any;
704
+ let publicState: BonnysFortunePublicState;
705
+ let privateState: BonnysFortunePrivateState;
702
706
 
703
707
  beforeEach(async () => {
704
- const initResult = await engine.processCommand(null, null, {
708
+ const initResult = await engine.processCommand(null as unknown as BonnysFortunePublicState, null as unknown as BonnysFortunePrivateState, {
705
709
  id: 'init',
706
710
  type: 'INIT_SESSION_STATE',
707
711
  payload: { betAmount: 1.0 },
@@ -730,7 +734,7 @@ describe("Bonny's Fortune - Comprehensive Test Suite", () => {
730
734
  // Engine throws TypeError when state is null
731
735
  // This is expected behavior - state must be initialized
732
736
  await expect(
733
- engine.processCommand(null, null, {
737
+ engine.processCommand(null as unknown as BonnysFortunePublicState, null as unknown as BonnysFortunePrivateState, {
734
738
  id: 'no-state',
735
739
  type: 'DEBUG_TRIGGER_BONUS',
736
740
  payload: {
@@ -747,11 +751,11 @@ describe("Bonny's Fortune - Comprehensive Test Suite", () => {
747
751
  // SESSION MANAGEMENT
748
752
  // ============================================================
749
753
  describe('Session Management', () => {
750
- let publicState: any;
751
- let privateState: any;
754
+ let publicState: BonnysFortunePublicState;
755
+ let privateState: BonnysFortunePrivateState;
752
756
 
753
757
  beforeEach(async () => {
754
- const initResult = await engine.processCommand(null, null, {
758
+ const initResult = await engine.processCommand(null as unknown as BonnysFortunePublicState, null as unknown as BonnysFortunePrivateState, {
755
759
  id: 'init',
756
760
  type: 'INIT_SESSION_STATE',
757
761
  payload: { betAmount: 1.0 },
@@ -775,11 +779,11 @@ describe("Bonny's Fortune - Comprehensive Test Suite", () => {
775
779
  // STATE PERSISTENCE
776
780
  // ============================================================
777
781
  describe('State Persistence', () => {
778
- let publicState: any;
779
- let privateState: any;
782
+ let publicState: BonnysFortunePublicState;
783
+ let privateState: BonnysFortunePrivateState;
780
784
 
781
785
  beforeEach(async () => {
782
- const initResult = await engine.processCommand(null, null, {
786
+ const initResult = await engine.processCommand(null as unknown as BonnysFortunePublicState, null as unknown as BonnysFortunePrivateState, {
783
787
  id: 'init',
784
788
  type: 'INIT_SESSION_STATE',
785
789
  payload: { betAmount: 1.0 },