@omnitronix/bonnys-fortune-game-engine 1.4.0 → 1.4.1

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 (194) hide show
  1. package/README.md +10 -0
  2. package/dist/__tests__/base-game.test.d.ts +1 -0
  3. package/dist/__tests__/base-game.test.js +255 -0
  4. package/dist/__tests__/base-game.test.js.map +1 -0
  5. package/dist/__tests__/bonnys-fortune-v1.game-engine.test.d.ts +1 -0
  6. package/dist/__tests__/bonnys-fortune-v1.game-engine.test.js +1 -1
  7. package/dist/__tests__/bonus-handlers.test.d.ts +1 -0
  8. package/dist/__tests__/bonus-handlers.test.js +303 -0
  9. package/dist/__tests__/bonus-handlers.test.js.map +1 -0
  10. package/dist/__tests__/bonus-meters.test.d.ts +1 -0
  11. package/dist/__tests__/bonus-meters.test.js +508 -0
  12. package/dist/__tests__/bonus-meters.test.js.map +1 -0
  13. package/dist/__tests__/collect-feature-handler.test.d.ts +1 -0
  14. package/dist/__tests__/collect-feature-handler.test.js +386 -0
  15. package/dist/__tests__/collect-feature-handler.test.js.map +1 -0
  16. package/dist/__tests__/comprehensive.test.d.ts +16 -0
  17. package/dist/__tests__/comprehensive.test.js +16 -10
  18. package/dist/__tests__/comprehensive.test.js.map +1 -1
  19. package/dist/__tests__/error-paths.test.d.ts +1 -0
  20. package/dist/__tests__/error-paths.test.js +373 -0
  21. package/dist/__tests__/error-paths.test.js.map +1 -0
  22. package/dist/__tests__/helpers/test-engine-factory.d.ts +103 -0
  23. package/dist/__tests__/helpers/test-engine-factory.js +268 -0
  24. package/dist/__tests__/helpers/test-engine-factory.js.map +1 -0
  25. package/dist/__tests__/rng-gli19-compliance.test.d.ts +1 -0
  26. package/dist/__tests__/rng-gli19-compliance.test.js +292 -0
  27. package/dist/__tests__/rng-gli19-compliance.test.js.map +1 -0
  28. package/dist/__tests__/rng-security.test.d.ts +1 -0
  29. package/dist/__tests__/rng-security.test.js +383 -0
  30. package/dist/__tests__/rng-security.test.js.map +1 -0
  31. package/dist/__tests__/rtp-simulation.test.d.ts +1 -0
  32. package/dist/__tests__/rtp-simulation.test.js +242 -0
  33. package/dist/__tests__/rtp-simulation.test.js.map +1 -0
  34. package/dist/__tests__/state-transitions.test.d.ts +1 -0
  35. package/dist/__tests__/state-transitions.test.js +307 -0
  36. package/dist/__tests__/state-transitions.test.js.map +1 -0
  37. package/dist/__tests__/steering-fortune-handler.test.d.ts +1 -0
  38. package/dist/__tests__/steering-fortune-handler.test.js +286 -0
  39. package/dist/__tests__/steering-fortune-handler.test.js.map +1 -0
  40. package/dist/__tests__/symbol-distribution.test.d.ts +1 -0
  41. package/dist/__tests__/symbol-distribution.test.js +195 -0
  42. package/dist/__tests__/symbol-distribution.test.js.map +1 -0
  43. package/dist/__tests__/treasure-hunt-handler.test.d.ts +1 -0
  44. package/dist/__tests__/treasure-hunt-handler.test.js +295 -0
  45. package/dist/__tests__/treasure-hunt-handler.test.js.map +1 -0
  46. package/dist/__tests__/win-calculator.test.d.ts +1 -0
  47. package/dist/__tests__/win-calculator.test.js +515 -0
  48. package/dist/__tests__/win-calculator.test.js.map +1 -0
  49. package/dist/bonnys-fortune-v1.game-engine.d.ts +41 -0
  50. package/dist/bonnys-fortune-v1.game-engine.js +22 -5
  51. package/dist/bonnys-fortune-v1.game-engine.js.map +1 -1
  52. package/dist/config/game-logic-config/file-system.game-logic-config-loader.d.ts +4 -0
  53. package/dist/config/game-logic-config/game-logic-config-loader.d.ts +3 -0
  54. package/dist/config/game-logic-config/game-logic-config.d.ts +2 -0
  55. package/dist/config/game-logic-config/game-logic-config.js +9 -4
  56. package/dist/config/game-logic-config/game-logic-config.js.map +1 -1
  57. package/dist/config/reel-strips-config/file-system.reel-strips-config-loader.d.ts +4 -0
  58. package/dist/config/reel-strips-config/reel-strip-option.dto.d.ts +4 -0
  59. package/dist/config/reel-strips-config/reel-strips-config-loader.d.ts +3 -0
  60. package/dist/config/reel-strips-config/reel-strips-config.dto.d.ts +5 -0
  61. package/dist/config/reel-strips-config/reel.dto.d.ts +5 -0
  62. package/dist/debug/debug-command-definitions.d.ts +36 -0
  63. package/dist/debug/debug-command-definitions.js +108 -0
  64. package/dist/debug/debug-command-definitions.js.map +1 -0
  65. package/dist/domain/game-round.types.d.ts +9 -0
  66. package/dist/domain/mappers/reel-strips-config.mapper.d.ts +4 -0
  67. package/dist/domain/reel-strip-option.d.ts +7 -0
  68. package/dist/domain/reel-strips-config.d.ts +9 -0
  69. package/dist/domain/reel.d.ts +8 -0
  70. package/dist/domain/types/debug-trigger-bonus-command.d.ts +5 -0
  71. package/dist/domain/types/debug-trigger-bonus-request.dto.d.ts +5 -0
  72. package/dist/domain/types/debug-update-bonus-meter-progress-command.d.ts +5 -0
  73. package/dist/domain/types/debug-update-bonus-meter-progress-request.dto.d.ts +5 -0
  74. package/dist/domain/types/game-spin-command.d.ts +8 -0
  75. package/dist/domain/types/game-spin-input.dto.d.ts +8 -0
  76. package/dist/domain/types/game-spin-output.dto.d.ts +119 -0
  77. package/dist/domain/types/game-symbols.response.dto.d.ts +10 -0
  78. package/dist/domain/types/reel-strip-option.dto.d.ts +4 -0
  79. package/dist/domain/types/reel-strips-config.dto.d.ts +5 -0
  80. package/dist/domain/types/reel.dto.d.ts +5 -0
  81. package/dist/domain/types/start-bonus-round-command.d.ts +8 -0
  82. package/dist/domain/types/start-bonus-round-input.dto.d.ts +10 -0
  83. package/dist/game-engine.interface.d.ts +8 -0
  84. package/dist/helpers/generate-hash.d.ts +1 -0
  85. package/dist/helpers/number-helper.d.ts +23 -0
  86. package/dist/helpers/optional-boolean-mapper.d.ts +1 -0
  87. package/dist/helpers/uuid-helper.d.ts +3 -0
  88. package/dist/helpers/validation-helper.d.ts +14 -0
  89. package/dist/index.d.ts +5 -0
  90. package/dist/index.js +10 -1
  91. package/dist/index.js.map +1 -1
  92. package/dist/logic/bonnys-fortune.game-logic.d.ts +41 -0
  93. package/dist/logic/bonnys-fortune.game-logic.js +53 -9
  94. package/dist/logic/bonnys-fortune.game-logic.js.map +1 -1
  95. package/dist/logic/bonnys-fortune.spin-generator.d.ts +19 -0
  96. package/dist/logic/bonnys-fortune.types.d.ts +173 -0
  97. package/dist/logic/bonnys-fortune.win-calculator.d.ts +10 -0
  98. package/dist/logic/game-logic-config.interface.d.ts +180 -0
  99. package/dist/logic/handlers/base-game.handler.d.ts +15 -0
  100. package/dist/logic/handlers/base-game.handler.js +5 -1
  101. package/dist/logic/handlers/base-game.handler.js.map +1 -1
  102. package/dist/logic/handlers/collect-feature-bonus.handler.d.ts +13 -0
  103. package/dist/logic/handlers/free-spins-bonus.handler.d.ts +11 -0
  104. package/dist/logic/handlers/steering-to-the-fortune-bonus.handler.d.ts +11 -0
  105. package/dist/logic/handlers/treasure-hunt-bonus.handler.d.ts +15 -0
  106. package/dist/rng/DummyRngClient.d.ts +18 -0
  107. package/dist/rng/DummyRngClient.js +1 -7
  108. package/dist/rng/DummyRngClient.js.map +1 -1
  109. package/dist/rng/rng-client.factory.d.ts +7 -0
  110. package/dist/rng/rng-client.factory.js.map +1 -1
  111. package/dist/rng/rng-client.interface.d.ts +16 -0
  112. package/dist/rng/rng-service.d.ts +26 -0
  113. package/dist/validation/abstract-game-logic-config.validator.d.ts +4 -0
  114. package/dist/validation/bonnys-fortune/bonnys-fortune-config.dto.d.ts +131 -0
  115. package/dist/validation/bonnys-fortune/bonnys-fortune-config.dto.js +9 -0
  116. package/dist/validation/bonnys-fortune/bonnys-fortune-config.dto.js.map +1 -1
  117. package/dist/validation/bonnys-fortune/bonnys-fortune-config.validator.d.ts +5 -0
  118. package/dist/validation/custom-decorators/IsNestedIntArray.d.ts +2 -0
  119. package/dist/validation/game-logic-config-validation.service.d.ts +8 -0
  120. package/dist/validation/reel-strips-config-validation.service.d.ts +5 -0
  121. package/package.json +6 -4
  122. package/src/__tests__/base-game.test.ts +320 -0
  123. package/src/__tests__/bonnys-fortune-v1.game-engine.test.ts +80 -0
  124. package/src/__tests__/bonus-handlers.test.ts +585 -0
  125. package/src/__tests__/bonus-meters.test.ts +789 -0
  126. package/src/__tests__/collect-feature-handler.test.ts +737 -0
  127. package/src/__tests__/comprehensive.test.ts +883 -0
  128. package/src/__tests__/error-paths.test.ts +457 -0
  129. package/src/__tests__/helpers/test-engine-factory.ts +380 -0
  130. package/src/__tests__/rng-gli19-compliance.test.ts +423 -0
  131. package/src/__tests__/rng-security.test.ts +479 -0
  132. package/src/__tests__/rtp-simulation.test.ts +309 -0
  133. package/src/__tests__/state-transitions.test.ts +422 -0
  134. package/src/__tests__/steering-fortune-handler.test.ts +535 -0
  135. package/src/__tests__/symbol-distribution.test.ts +229 -0
  136. package/src/__tests__/treasure-hunt-handler.test.ts +581 -0
  137. package/src/__tests__/win-calculator.test.ts +816 -0
  138. package/src/bonnys-fortune-v1.game-engine.ts +334 -0
  139. package/src/config/game-logic-config/file-system.game-logic-config-loader.ts +27 -0
  140. package/src/config/game-logic-config/game-logic-config-loader.ts +3 -0
  141. package/src/config/game-logic-config/game-logic-config.ts +385 -0
  142. package/src/config/reel-strips-config/file-system.reel-strips-config-loader.ts +43 -0
  143. package/src/config/reel-strips-config/reel-strip-option.dto.ts +13 -0
  144. package/src/config/reel-strips-config/reel-strips-config-loader.ts +9 -0
  145. package/src/config/reel-strips-config/reel-strips-config.dto.ts +16 -0
  146. package/src/config/reel-strips-config/reel.dto.ts +16 -0
  147. package/src/config/reel-strips-config/reels-BASE.csv +52 -0
  148. package/src/config/reel-strips-config/reels-BONUS.csv +52 -0
  149. package/src/debug/debug-command-definitions.ts +108 -0
  150. package/src/domain/game-round.types.ts +10 -0
  151. package/src/domain/mappers/reel-strips-config.mapper.ts +16 -0
  152. package/src/domain/reel-strip-option.ts +15 -0
  153. package/src/domain/reel-strips-config.ts +21 -0
  154. package/src/domain/reel.ts +17 -0
  155. package/src/domain/types/debug-trigger-bonus-command.ts +5 -0
  156. package/src/domain/types/debug-trigger-bonus-request.dto.ts +5 -0
  157. package/src/domain/types/debug-update-bonus-meter-progress-command.ts +5 -0
  158. package/src/domain/types/debug-update-bonus-meter-progress-request.dto.ts +5 -0
  159. package/src/domain/types/game-spin-command.ts +8 -0
  160. package/src/domain/types/game-spin-input.dto.ts +8 -0
  161. package/src/domain/types/game-spin-output.dto.ts +142 -0
  162. package/src/domain/types/game-symbols.response.dto.ts +11 -0
  163. package/src/domain/types/reel-strip-option.dto.ts +13 -0
  164. package/src/domain/types/reel-strips-config.dto.ts +15 -0
  165. package/src/domain/types/reel.dto.ts +15 -0
  166. package/src/domain/types/start-bonus-round-command.ts +8 -0
  167. package/src/domain/types/start-bonus-round-input.dto.ts +10 -0
  168. package/src/game-engine.interface.ts +19 -0
  169. package/src/helpers/generate-hash.ts +5 -0
  170. package/src/helpers/number-helper.ts +41 -0
  171. package/src/helpers/optional-boolean-mapper.ts +5 -0
  172. package/src/helpers/uuid-helper.ts +7 -0
  173. package/src/helpers/validation-helper.ts +27 -0
  174. package/src/index.ts +31 -0
  175. package/src/logic/bonnys-fortune.game-logic.ts +535 -0
  176. package/src/logic/bonnys-fortune.spin-generator.ts +277 -0
  177. package/src/logic/bonnys-fortune.types.ts +226 -0
  178. package/src/logic/bonnys-fortune.win-calculator.ts +210 -0
  179. package/src/logic/game-logic-config.interface.ts +176 -0
  180. package/src/logic/handlers/base-game.handler.ts +221 -0
  181. package/src/logic/handlers/collect-feature-bonus.handler.ts +301 -0
  182. package/src/logic/handlers/free-spins-bonus.handler.ts +119 -0
  183. package/src/logic/handlers/steering-to-the-fortune-bonus.handler.ts +118 -0
  184. package/src/logic/handlers/treasure-hunt-bonus.handler.ts +232 -0
  185. package/src/rng/DummyRngClient.ts +109 -0
  186. package/src/rng/rng-client.factory.ts +27 -0
  187. package/src/rng/rng-client.interface.ts +38 -0
  188. package/src/rng/rng-service.ts +130 -0
  189. package/src/validation/abstract-game-logic-config.validator.ts +20 -0
  190. package/src/validation/bonnys-fortune/bonnys-fortune-config.dto.ts +379 -0
  191. package/src/validation/bonnys-fortune/bonnys-fortune-config.validator.ts +8 -0
  192. package/src/validation/custom-decorators/IsNestedIntArray.ts +29 -0
  193. package/src/validation/game-logic-config-validation.service.ts +28 -0
  194. package/src/validation/reel-strips-config-validation.service.ts +29 -0
package/README.md CHANGED
@@ -602,6 +602,16 @@ async function testBonusGame() {
602
602
  }
603
603
  ```
604
604
 
605
+ ## Testing
606
+
607
+ This engine uses [@omnitronix/game-test-utils](https://github.com/omnitronix/game-test-utils) for comprehensive testing including:
608
+ - GLI-19 compliance validation
609
+ - RNG security testing
610
+ - Statistical RTP simulation
611
+ - Symbol distribution analysis
612
+
613
+ Run tests: `npm test`
614
+
605
615
  ## Development
606
616
 
607
617
  ### Running Tests
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,255 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ /**
4
+ * Base Game Logic Tests — Bonnys Fortune
5
+ *
6
+ * Tests the core base game mechanics:
7
+ * - Screen generation (5x3 grid, 243 ways)
8
+ * - Win calculation (ways evaluation)
9
+ * - Bonus meter tracking and progression
10
+ * - Bet amount handling
11
+ * - Per-bet-amount meter state
12
+ */
13
+ const test_engine_factory_1 = require("./helpers/test-engine-factory");
14
+ describe('Base Game Logic — Bonnys Fortune', () => {
15
+ beforeEach(() => {
16
+ (0, test_engine_factory_1.resetCommandIdCounter)();
17
+ });
18
+ describe('Screen Generation', () => {
19
+ it('should generate a 5x3 screen on each spin', async () => {
20
+ const engine = (0, test_engine_factory_1.createEngine)();
21
+ const session = await (0, test_engine_factory_1.initSession)(engine);
22
+ const result = await (0, test_engine_factory_1.executeSpin)(engine, session.publicState, session.privateState);
23
+ expect(result.success).toBe(true);
24
+ const screen = result.outcome?.result?.screen ?? result.outcome?.screen;
25
+ expect(screen).toBeDefined();
26
+ expect(screen.length).toBe(5);
27
+ for (const reel of screen) {
28
+ expect(reel.length).toBe(3);
29
+ }
30
+ });
31
+ it('should produce valid symbol IDs on screen', async () => {
32
+ const engine = (0, test_engine_factory_1.createEngine)();
33
+ const session = await (0, test_engine_factory_1.initSession)(engine);
34
+ const result = await (0, test_engine_factory_1.executeSpin)(engine, session.publicState, session.privateState);
35
+ expect(result.success).toBe(true);
36
+ const screen = result.outcome?.result?.screen ?? result.outcome?.screen;
37
+ for (const reel of screen) {
38
+ for (const symbol of reel) {
39
+ expect(typeof symbol).toBe('number');
40
+ expect(symbol).toBeGreaterThanOrEqual(0);
41
+ }
42
+ }
43
+ });
44
+ it('should produce different screens across multiple spins', async () => {
45
+ // Use LCG RNG to avoid DummyRng producing identical screens and
46
+ // constant bonus meter triggers that prevent base game spins
47
+ const { engine } = (0, test_engine_factory_1.createEngineWithLcgRng)(54321);
48
+ const session = await (0, test_engine_factory_1.initSession)(engine);
49
+ let pub = session.publicState;
50
+ let priv = session.privateState;
51
+ const screens = [];
52
+ for (let i = 0; i < 50; i++) {
53
+ if (priv.nextSpinType !== 'BASE_GAME_SPIN') {
54
+ // Bonus triggered — complete it via extractFinalBonusState
55
+ if (priv.pendingBonuses?.length > 0) {
56
+ const bonus = priv.pendingBonuses[0];
57
+ const bonusResult = await (0, test_engine_factory_1.startBonusRound)(engine, pub, priv, bonus.bonusType);
58
+ const finalState = (0, test_engine_factory_1.extractFinalBonusState)(bonusResult);
59
+ pub = finalState.publicState;
60
+ priv = finalState.privateState;
61
+ // Handle chained bonuses (steering fortune)
62
+ while (priv.pendingBonuses?.length > 0) {
63
+ const nextBonus = priv.pendingBonuses[0];
64
+ const chainResult = await (0, test_engine_factory_1.startBonusRound)(engine, pub, priv, nextBonus.bonusType);
65
+ const chainFinal = (0, test_engine_factory_1.extractFinalBonusState)(chainResult);
66
+ pub = chainFinal.publicState;
67
+ priv = chainFinal.privateState;
68
+ }
69
+ }
70
+ if (priv.nextSpinType !== 'BASE_GAME_SPIN')
71
+ break;
72
+ }
73
+ const result = await (0, test_engine_factory_1.executeSpin)(engine, pub, priv);
74
+ expect(result.success).toBe(true);
75
+ const screen = result.outcome?.result?.screen ?? result.outcome?.screen ?? result.privateState.screen;
76
+ screens.push(JSON.stringify(screen));
77
+ pub = result.publicState;
78
+ priv = result.privateState;
79
+ }
80
+ const unique = new Set(screens);
81
+ expect(unique.size).toBeGreaterThan(1);
82
+ });
83
+ });
84
+ describe('Win Calculation', () => {
85
+ it('should report playerWinning as non-negative number', async () => {
86
+ const engine = (0, test_engine_factory_1.createEngine)();
87
+ const session = await (0, test_engine_factory_1.initSession)(engine);
88
+ const result = await (0, test_engine_factory_1.executeSpin)(engine, session.publicState, session.privateState);
89
+ expect(result.success).toBe(true);
90
+ const playerWinning = result.outcome?.result?.playerWinning ?? result.outcome?.playerWinning;
91
+ if (playerWinning !== undefined) {
92
+ expect(playerWinning).toBeGreaterThanOrEqual(0);
93
+ expect(typeof playerWinning).toBe('number');
94
+ }
95
+ });
96
+ it('should produce finite win amounts', async () => {
97
+ const engine = (0, test_engine_factory_1.createEngine)();
98
+ const session = await (0, test_engine_factory_1.initSession)(engine);
99
+ let pub = session.publicState;
100
+ let priv = session.privateState;
101
+ for (let i = 0; i < 20; i++) {
102
+ if (priv.nextSpinType !== 'BASE_GAME_SPIN')
103
+ break;
104
+ const result = await (0, test_engine_factory_1.executeSpin)(engine, pub, priv);
105
+ expect(result.success).toBe(true);
106
+ const playerWinning = result.outcome?.result?.playerWinning ?? result.outcome?.playerWinning;
107
+ if (playerWinning !== undefined) {
108
+ expect(Number.isFinite(playerWinning)).toBe(true);
109
+ }
110
+ pub = result.publicState;
111
+ priv = result.privateState;
112
+ }
113
+ });
114
+ });
115
+ describe('Bonus Meter Mechanics', () => {
116
+ it('should have bonus meters in public state after init', async () => {
117
+ const engine = (0, test_engine_factory_1.createEngine)();
118
+ const session = await (0, test_engine_factory_1.initSession)(engine);
119
+ expect(session.publicState.bonusMeters).toBeDefined();
120
+ const keys = Object.keys(session.publicState.bonusMeters || {});
121
+ expect(keys.length).toBeGreaterThan(0);
122
+ });
123
+ it('should have valid threshold and progress for each meter', async () => {
124
+ const engine = (0, test_engine_factory_1.createEngine)();
125
+ const session = await (0, test_engine_factory_1.initSession)(engine);
126
+ const meters = session.publicState.bonusMeters;
127
+ for (const [, meter] of Object.entries(meters || {})) {
128
+ const m = meter;
129
+ expect(m.threshold).toBeGreaterThan(0);
130
+ expect(m.progress).toBeGreaterThanOrEqual(0);
131
+ expect(typeof m.threshold).toBe('number');
132
+ expect(typeof m.progress).toBe('number');
133
+ }
134
+ });
135
+ it('should maintain meter state across spins', async () => {
136
+ const engine = (0, test_engine_factory_1.createEngine)();
137
+ const session = await (0, test_engine_factory_1.initSession)(engine);
138
+ let pub = session.publicState;
139
+ let priv = session.privateState;
140
+ for (let i = 0; i < 10; i++) {
141
+ if (priv.nextSpinType !== 'BASE_GAME_SPIN')
142
+ break;
143
+ const result = await (0, test_engine_factory_1.executeSpin)(engine, pub, priv);
144
+ expect(result.success).toBe(true);
145
+ expect(result.publicState.bonusMeters).toBeDefined();
146
+ // Verify meter structure integrity
147
+ for (const [, meter] of Object.entries(result.publicState.bonusMeters || {})) {
148
+ const m = meter;
149
+ expect(m.threshold).toBeGreaterThan(0);
150
+ expect(m.progress).toBeGreaterThanOrEqual(0);
151
+ expect(m.progress).toBeLessThanOrEqual(m.threshold);
152
+ }
153
+ pub = result.publicState;
154
+ priv = result.privateState;
155
+ }
156
+ });
157
+ it('should not allow meter progress to exceed threshold', async () => {
158
+ const engine = (0, test_engine_factory_1.createEngine)();
159
+ const session = await (0, test_engine_factory_1.initSession)(engine);
160
+ let pub = session.publicState;
161
+ let priv = session.privateState;
162
+ for (let i = 0; i < 30; i++) {
163
+ if (priv.nextSpinType !== 'BASE_GAME_SPIN')
164
+ break;
165
+ const result = await (0, test_engine_factory_1.executeSpin)(engine, pub, priv);
166
+ expect(result.success).toBe(true);
167
+ for (const [, meter] of Object.entries(result.publicState.bonusMeters || {})) {
168
+ const m = meter;
169
+ expect(m.progress).toBeLessThanOrEqual(m.threshold);
170
+ }
171
+ pub = result.publicState;
172
+ priv = result.privateState;
173
+ }
174
+ });
175
+ it('should have per-bet-amount meter state in private state', async () => {
176
+ const engine = (0, test_engine_factory_1.createEngine)();
177
+ const session = await (0, test_engine_factory_1.initSession)(engine);
178
+ expect(session.privateState.bonusMetersByAmount).toBeDefined();
179
+ const amounts = Object.keys(session.privateState.bonusMetersByAmount || {});
180
+ expect(amounts.length).toBeGreaterThan(0);
181
+ });
182
+ });
183
+ describe('RNG Outcome Tracking', () => {
184
+ it('should produce valid RNG outcome on each spin', async () => {
185
+ const engine = (0, test_engine_factory_1.createEngine)();
186
+ const session = await (0, test_engine_factory_1.initSession)(engine);
187
+ const result = await (0, test_engine_factory_1.executeSpin)(engine, session.publicState, session.privateState);
188
+ expect(result.success).toBe(true);
189
+ expect(result.rngOutcome).toBeDefined();
190
+ const validation = (0, test_engine_factory_1.validateRngOutcome)(result.rngOutcome);
191
+ expect(validation.valid).toBe(true);
192
+ });
193
+ });
194
+ describe('Bet Amount Handling', () => {
195
+ it('should accept different bet amounts', async () => {
196
+ const engine = (0, test_engine_factory_1.createEngine)();
197
+ const session = await (0, test_engine_factory_1.initSession)(engine, 0.5);
198
+ const result = await (0, test_engine_factory_1.executeSpin)(engine, session.publicState, session.privateState, 0.5);
199
+ expect(result.success).toBe(true);
200
+ });
201
+ it('should update currentBetAmount in public state', async () => {
202
+ const engine = (0, test_engine_factory_1.createEngine)();
203
+ const session = await (0, test_engine_factory_1.initSession)(engine, 2.0);
204
+ expect(session.publicState.currentBetAmount).toBe(2.0);
205
+ });
206
+ it('should switch meter state when bet amount changes', async () => {
207
+ const engine = (0, test_engine_factory_1.createEngine)();
208
+ const session = await (0, test_engine_factory_1.initSession)(engine, 1.0);
209
+ const result1 = await (0, test_engine_factory_1.executeSpin)(engine, session.publicState, session.privateState, 1.0);
210
+ expect(result1.success).toBe(true);
211
+ if (result1.privateState.nextSpinType === 'BASE_GAME_SPIN') {
212
+ // Change bet amount
213
+ const result2 = await (0, test_engine_factory_1.executeSpin)(engine, result1.publicState, result1.privateState, 2.0);
214
+ expect(result2.success).toBe(true);
215
+ // Meters should still be valid
216
+ expect(result2.publicState.bonusMeters).toBeDefined();
217
+ }
218
+ });
219
+ });
220
+ describe('Mock RNG Determinism', () => {
221
+ it('should produce deterministic results with mock RNG', async () => {
222
+ const { engine: engine1 } = (0, test_engine_factory_1.createEngineWithMockRng)([42, 42, 42, 42, 42]);
223
+ const { engine: engine2 } = (0, test_engine_factory_1.createEngineWithMockRng)([42, 42, 42, 42, 42]);
224
+ const session1 = await (0, test_engine_factory_1.initSession)(engine1);
225
+ const session2 = await (0, test_engine_factory_1.initSession)(engine2);
226
+ const result1 = await (0, test_engine_factory_1.executeSpin)(engine1, session1.publicState, session1.privateState);
227
+ const result2 = await (0, test_engine_factory_1.executeSpin)(engine2, session2.publicState, session2.privateState);
228
+ expect(result1.success).toBe(true);
229
+ expect(result2.success).toBe(true);
230
+ const screen1 = JSON.stringify(result1.outcome?.screen || result1.privateState.screen);
231
+ const screen2 = JSON.stringify(result2.outcome?.screen || result2.privateState.screen);
232
+ expect(screen1).toBe(screen2);
233
+ });
234
+ });
235
+ describe('Multiple Spin Stability', () => {
236
+ it('should handle 30 consecutive base game spins', async () => {
237
+ const engine = (0, test_engine_factory_1.createEngine)();
238
+ const session = await (0, test_engine_factory_1.initSession)(engine);
239
+ let pub = session.publicState;
240
+ let priv = session.privateState;
241
+ let spinCount = 0;
242
+ for (let i = 0; i < 30; i++) {
243
+ if (priv.nextSpinType !== 'BASE_GAME_SPIN')
244
+ break;
245
+ const result = await (0, test_engine_factory_1.executeSpin)(engine, pub, priv);
246
+ expect(result.success).toBe(true);
247
+ spinCount++;
248
+ pub = result.publicState;
249
+ priv = result.privateState;
250
+ }
251
+ expect(spinCount).toBeGreaterThan(0);
252
+ });
253
+ });
254
+ });
255
+ //# sourceMappingURL=base-game.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base-game.test.js","sourceRoot":"","sources":["../../src/__tests__/base-game.test.ts"],"names":[],"mappings":"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"}
@@ -10,7 +10,7 @@ describe('BonnysFortuneV1GameEngine', () => {
10
10
  it('should initialize the game engine and return info', async () => {
11
11
  const info = gameEngine.getGameEngineInfo();
12
12
  expect(info.gameCode).toBe('bonnys-fortune');
13
- expect(info.version).toBe('1.0.0');
13
+ expect(info.version).toBe('1.5.0');
14
14
  expect(info.rtp).toBe(96);
15
15
  expect(info.gameType).toBe('slot');
16
16
  });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,303 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ /**
4
+ * Bonus Handler Tests — Bonnys Fortune
5
+ *
6
+ * Tests all 4 bonus types via debug trigger:
7
+ * - Treasure Hunt (bonusGame1): pick mechanic, jackpots
8
+ * - Free Spins (bonusGame2): ways evaluation, multipliers
9
+ * - Steering Fortune (bonusGame3): wheel spin, prize resolution
10
+ * - Collect Feature (collectFeature): symbol collection, checkpoints
11
+ * - Debug trigger → START_BONUS_ROUND flow
12
+ * - Meter redraw after bonus completion
13
+ */
14
+ const test_engine_factory_1 = require("./helpers/test-engine-factory");
15
+ describe('Bonus Handlers — Bonnys Fortune', () => {
16
+ beforeEach(() => {
17
+ (0, test_engine_factory_1.resetCommandIdCounter)();
18
+ });
19
+ describe('Treasure Hunt (bonusGame1)', () => {
20
+ it('should trigger treasure hunt via debug successfully', async () => {
21
+ const engine = (0, test_engine_factory_1.createEngine)();
22
+ const session = await (0, test_engine_factory_1.initSession)(engine);
23
+ const debugResult = await (0, test_engine_factory_1.debugTriggerBonus)(engine, session.publicState, session.privateState, 'bonusGame1');
24
+ expect(debugResult.success).toBe(true);
25
+ // Debug trigger adds exactly 1 pending bonus
26
+ expect(debugResult.privateState.pendingBonuses.length).toBe(1);
27
+ });
28
+ it('should complete treasure hunt and return to BASE_GAME_SPIN', async () => {
29
+ const engine = (0, test_engine_factory_1.createEngine)();
30
+ const session = await (0, test_engine_factory_1.initSession)(engine);
31
+ const debugResult = await (0, test_engine_factory_1.debugTriggerBonus)(engine, session.publicState, session.privateState, 'bonusGame1');
32
+ if (!debugResult.success)
33
+ return;
34
+ const bonus = debugResult.privateState.pendingBonuses[0];
35
+ const bonusResult = await (0, test_engine_factory_1.startBonusRound)(engine, debugResult.publicState, debugResult.privateState, bonus.bonusType);
36
+ expect(bonusResult.success).toBe(true);
37
+ // BF generates all bonus results upfront — final state is in last result
38
+ const finalState = (0, test_engine_factory_1.extractFinalBonusState)(bonusResult);
39
+ expect(finalState.privateState.nextSpinType).toBe('BASE_GAME_SPIN');
40
+ });
41
+ it('should produce non-negative winnings from treasure hunt', async () => {
42
+ const engine = (0, test_engine_factory_1.createEngine)();
43
+ const session = await (0, test_engine_factory_1.initSession)(engine);
44
+ const debugResult = await (0, test_engine_factory_1.debugTriggerBonus)(engine, session.publicState, session.privateState, 'bonusGame1');
45
+ if (!debugResult.success)
46
+ return;
47
+ const bonus = debugResult.privateState.pendingBonuses[0];
48
+ const bonusResult = await (0, test_engine_factory_1.startBonusRound)(engine, debugResult.publicState, debugResult.privateState, bonus.bonusType);
49
+ expect(bonusResult.success).toBe(true);
50
+ if (bonusResult.outcome) {
51
+ const outcomes = Array.isArray(bonusResult.outcome)
52
+ ? bonusResult.outcome
53
+ : [bonusResult.outcome];
54
+ for (const o of outcomes) {
55
+ if (o.playerWinning !== undefined) {
56
+ expect(o.playerWinning).toBeGreaterThanOrEqual(0);
57
+ }
58
+ }
59
+ }
60
+ });
61
+ it('should include RNG outcome in treasure hunt', async () => {
62
+ const engine = (0, test_engine_factory_1.createEngine)();
63
+ const session = await (0, test_engine_factory_1.initSession)(engine);
64
+ const debugResult = await (0, test_engine_factory_1.debugTriggerBonus)(engine, session.publicState, session.privateState, 'bonusGame1');
65
+ if (!debugResult.success)
66
+ return;
67
+ const bonus = debugResult.privateState.pendingBonuses[0];
68
+ const bonusResult = await (0, test_engine_factory_1.startBonusRound)(engine, debugResult.publicState, debugResult.privateState, bonus.bonusType);
69
+ expect(bonusResult.success).toBe(true);
70
+ expect(bonusResult.rngOutcome).toBeDefined();
71
+ expect(typeof bonusResult.rngOutcome).toBe('object');
72
+ expect(Object.keys(bonusResult.rngOutcome || {}).length).toBeGreaterThan(0);
73
+ });
74
+ });
75
+ describe('Free Spins (bonusGame2)', () => {
76
+ it('should trigger free spins via debug successfully', async () => {
77
+ const engine = (0, test_engine_factory_1.createEngine)();
78
+ const session = await (0, test_engine_factory_1.initSession)(engine);
79
+ const debugResult = await (0, test_engine_factory_1.debugTriggerBonus)(engine, session.publicState, session.privateState, 'bonusGame2');
80
+ expect(debugResult.success).toBe(true);
81
+ // Debug trigger adds exactly 1 pending bonus
82
+ expect(debugResult.privateState.pendingBonuses.length).toBe(1);
83
+ });
84
+ it('should complete free spins and return to BASE_GAME_SPIN', async () => {
85
+ const engine = (0, test_engine_factory_1.createEngine)();
86
+ const session = await (0, test_engine_factory_1.initSession)(engine);
87
+ const debugResult = await (0, test_engine_factory_1.debugTriggerBonus)(engine, session.publicState, session.privateState, 'bonusGame2');
88
+ if (!debugResult.success)
89
+ return;
90
+ const bonus = debugResult.privateState.pendingBonuses[0];
91
+ const bonusResult = await (0, test_engine_factory_1.startBonusRound)(engine, debugResult.publicState, debugResult.privateState, bonus.bonusType);
92
+ expect(bonusResult.success).toBe(true);
93
+ // BF generates all bonus results upfront — final state is in last result
94
+ const finalState = (0, test_engine_factory_1.extractFinalBonusState)(bonusResult);
95
+ expect(finalState.privateState.nextSpinType).toBe('BASE_GAME_SPIN');
96
+ });
97
+ it('should produce non-negative winnings from free spins', async () => {
98
+ const engine = (0, test_engine_factory_1.createEngine)();
99
+ const session = await (0, test_engine_factory_1.initSession)(engine);
100
+ const debugResult = await (0, test_engine_factory_1.debugTriggerBonus)(engine, session.publicState, session.privateState, 'bonusGame2');
101
+ if (!debugResult.success)
102
+ return;
103
+ const bonus = debugResult.privateState.pendingBonuses[0];
104
+ const bonusResult = await (0, test_engine_factory_1.startBonusRound)(engine, debugResult.publicState, debugResult.privateState, bonus.bonusType);
105
+ expect(bonusResult.success).toBe(true);
106
+ if (bonusResult.outcome) {
107
+ const outcomes = Array.isArray(bonusResult.outcome)
108
+ ? bonusResult.outcome
109
+ : [bonusResult.outcome];
110
+ for (const o of outcomes) {
111
+ if (o.playerWinning !== undefined) {
112
+ expect(o.playerWinning).toBeGreaterThanOrEqual(0);
113
+ }
114
+ }
115
+ }
116
+ });
117
+ });
118
+ describe('Steering Fortune (bonusGame3)', () => {
119
+ it('should trigger steering fortune via debug successfully', async () => {
120
+ const engine = (0, test_engine_factory_1.createEngine)();
121
+ const session = await (0, test_engine_factory_1.initSession)(engine);
122
+ const debugResult = await (0, test_engine_factory_1.debugTriggerBonus)(engine, session.publicState, session.privateState, 'bonusGame3');
123
+ expect(debugResult.success).toBe(true);
124
+ // Debug trigger adds exactly 1 pending bonus
125
+ expect(debugResult.privateState.pendingBonuses.length).toBe(1);
126
+ });
127
+ it('should complete steering fortune', async () => {
128
+ const engine = (0, test_engine_factory_1.createEngine)();
129
+ const session = await (0, test_engine_factory_1.initSession)(engine);
130
+ const debugResult = await (0, test_engine_factory_1.debugTriggerBonus)(engine, session.publicState, session.privateState, 'bonusGame3');
131
+ if (!debugResult.success)
132
+ return;
133
+ const bonus = debugResult.privateState.pendingBonuses[0];
134
+ const bonusResult = await (0, test_engine_factory_1.startBonusRound)(engine, debugResult.publicState, debugResult.privateState, bonus.bonusType);
135
+ expect(bonusResult.success).toBe(true);
136
+ // Steering Fortune may chain to another bonus type
137
+ const finalState = (0, test_engine_factory_1.extractFinalBonusState)(bonusResult);
138
+ expect(finalState.privateState.nextSpinType).toBeDefined();
139
+ });
140
+ it('should handle steering fortune chaining to another bonus', async () => {
141
+ const engine = (0, test_engine_factory_1.createEngine)();
142
+ const session = await (0, test_engine_factory_1.initSession)(engine);
143
+ const debugResult = await (0, test_engine_factory_1.debugTriggerBonus)(engine, session.publicState, session.privateState, 'bonusGame3');
144
+ if (!debugResult.success)
145
+ return;
146
+ const bonus = debugResult.privateState.pendingBonuses[0];
147
+ const bonusResult = await (0, test_engine_factory_1.startBonusRound)(engine, debugResult.publicState, debugResult.privateState, bonus.bonusType);
148
+ expect(bonusResult.success).toBe(true);
149
+ // If it chained to another bonus, complete that too
150
+ const finalState = (0, test_engine_factory_1.extractFinalBonusState)(bonusResult);
151
+ if (finalState.privateState.pendingBonuses?.length > 0) {
152
+ const nextBonus = finalState.privateState.pendingBonuses[0];
153
+ const nextResult = await (0, test_engine_factory_1.startBonusRound)(engine, finalState.publicState, finalState.privateState, nextBonus.bonusType);
154
+ expect(nextResult.success).toBe(true);
155
+ }
156
+ });
157
+ });
158
+ describe('Collect Feature (collectFeature)', () => {
159
+ it('should trigger collect feature via debug successfully', async () => {
160
+ const engine = (0, test_engine_factory_1.createEngine)();
161
+ const session = await (0, test_engine_factory_1.initSession)(engine);
162
+ const debugResult = await (0, test_engine_factory_1.debugTriggerBonus)(engine, session.publicState, session.privateState, 'collectFeature');
163
+ expect(debugResult.success).toBe(true);
164
+ // Debug trigger adds exactly 1 pending bonus
165
+ expect(debugResult.privateState.pendingBonuses.length).toBe(1);
166
+ });
167
+ it('should complete collect feature and return to BASE_GAME_SPIN', async () => {
168
+ const engine = (0, test_engine_factory_1.createEngine)();
169
+ const session = await (0, test_engine_factory_1.initSession)(engine);
170
+ const debugResult = await (0, test_engine_factory_1.debugTriggerBonus)(engine, session.publicState, session.privateState, 'collectFeature');
171
+ if (!debugResult.success)
172
+ return;
173
+ const bonus = debugResult.privateState.pendingBonuses[0];
174
+ const bonusResult = await (0, test_engine_factory_1.startBonusRound)(engine, debugResult.publicState, debugResult.privateState, bonus.bonusType);
175
+ expect(bonusResult.success).toBe(true);
176
+ // BF generates all bonus results upfront — final state is in last result
177
+ const finalState = (0, test_engine_factory_1.extractFinalBonusState)(bonusResult);
178
+ expect(finalState.privateState.nextSpinType).toBe('BASE_GAME_SPIN');
179
+ });
180
+ it('should produce non-negative winnings from collect feature', async () => {
181
+ const engine = (0, test_engine_factory_1.createEngine)();
182
+ const session = await (0, test_engine_factory_1.initSession)(engine);
183
+ const debugResult = await (0, test_engine_factory_1.debugTriggerBonus)(engine, session.publicState, session.privateState, 'collectFeature');
184
+ if (!debugResult.success)
185
+ return;
186
+ const bonus = debugResult.privateState.pendingBonuses[0];
187
+ const bonusResult = await (0, test_engine_factory_1.startBonusRound)(engine, debugResult.publicState, debugResult.privateState, bonus.bonusType);
188
+ expect(bonusResult.success).toBe(true);
189
+ if (bonusResult.outcome) {
190
+ const outcomes = Array.isArray(bonusResult.outcome)
191
+ ? bonusResult.outcome
192
+ : [bonusResult.outcome];
193
+ for (const o of outcomes) {
194
+ if (o.playerWinning !== undefined) {
195
+ expect(o.playerWinning).toBeGreaterThanOrEqual(0);
196
+ }
197
+ }
198
+ }
199
+ });
200
+ });
201
+ describe('Meter Redraw After Bonus', () => {
202
+ it('should redraw meter after non-collectFeature bonus', async () => {
203
+ const engine = (0, test_engine_factory_1.createEngine)();
204
+ const session = await (0, test_engine_factory_1.initSession)(engine);
205
+ const debugResult = await (0, test_engine_factory_1.debugTriggerBonus)(engine, session.publicState, session.privateState, 'bonusGame1');
206
+ if (!debugResult.success)
207
+ return;
208
+ const bonus = debugResult.privateState.pendingBonuses[0];
209
+ const bonusResult = await (0, test_engine_factory_1.startBonusRound)(engine, debugResult.publicState, debugResult.privateState, bonus.bonusType);
210
+ expect(bonusResult.success).toBe(true);
211
+ // After bonus, meters should be valid (may have been redrawn)
212
+ const finalMeters = bonusResult.publicState.bonusMeters;
213
+ expect(finalMeters).toBeDefined();
214
+ expect(typeof finalMeters).toBe('object');
215
+ // Should have 3 meters (bonusGame1, bonusGame2, bonusGame3)
216
+ expect(Object.keys(finalMeters || {}).length).toBe(3);
217
+ for (const [, meter] of Object.entries(finalMeters || {})) {
218
+ const m = meter;
219
+ expect(m.threshold).toBeGreaterThan(0);
220
+ expect(m.progress).toBeGreaterThanOrEqual(0);
221
+ }
222
+ });
223
+ });
224
+ describe('Post-Bonus Play', () => {
225
+ it('should allow normal spin after treasure hunt', async () => {
226
+ const engine = (0, test_engine_factory_1.createEngine)();
227
+ const session = await (0, test_engine_factory_1.initSession)(engine);
228
+ const debugResult = await (0, test_engine_factory_1.debugTriggerBonus)(engine, session.publicState, session.privateState, 'bonusGame1');
229
+ if (!debugResult.success)
230
+ return;
231
+ const bonus = debugResult.privateState.pendingBonuses[0];
232
+ const bonusResult = await (0, test_engine_factory_1.startBonusRound)(engine, debugResult.publicState, debugResult.privateState, bonus.bonusType);
233
+ expect(bonusResult.success).toBe(true);
234
+ const finalState1 = (0, test_engine_factory_1.extractFinalBonusState)(bonusResult);
235
+ if (finalState1.privateState.nextSpinType === 'BASE_GAME_SPIN') {
236
+ const spinResult = await (0, test_engine_factory_1.executeSpin)(engine, finalState1.publicState, finalState1.privateState);
237
+ expect(spinResult.success).toBe(true);
238
+ }
239
+ });
240
+ it('should allow normal spin after free spins', async () => {
241
+ const engine = (0, test_engine_factory_1.createEngine)();
242
+ const session = await (0, test_engine_factory_1.initSession)(engine);
243
+ const debugResult = await (0, test_engine_factory_1.debugTriggerBonus)(engine, session.publicState, session.privateState, 'bonusGame2');
244
+ if (!debugResult.success)
245
+ return;
246
+ const bonus = debugResult.privateState.pendingBonuses[0];
247
+ const bonusResult = await (0, test_engine_factory_1.startBonusRound)(engine, debugResult.publicState, debugResult.privateState, bonus.bonusType);
248
+ expect(bonusResult.success).toBe(true);
249
+ const finalState2 = (0, test_engine_factory_1.extractFinalBonusState)(bonusResult);
250
+ if (finalState2.privateState.nextSpinType === 'BASE_GAME_SPIN') {
251
+ const spinResult = await (0, test_engine_factory_1.executeSpin)(engine, finalState2.publicState, finalState2.privateState);
252
+ expect(spinResult.success).toBe(true);
253
+ }
254
+ });
255
+ it('should allow normal spin after collect feature', async () => {
256
+ const engine = (0, test_engine_factory_1.createEngine)();
257
+ const session = await (0, test_engine_factory_1.initSession)(engine);
258
+ const debugResult = await (0, test_engine_factory_1.debugTriggerBonus)(engine, session.publicState, session.privateState, 'collectFeature');
259
+ if (!debugResult.success)
260
+ return;
261
+ const bonus = debugResult.privateState.pendingBonuses[0];
262
+ const bonusResult = await (0, test_engine_factory_1.startBonusRound)(engine, debugResult.publicState, debugResult.privateState, bonus.bonusType);
263
+ expect(bonusResult.success).toBe(true);
264
+ const finalState3 = (0, test_engine_factory_1.extractFinalBonusState)(bonusResult);
265
+ if (finalState3.privateState.nextSpinType === 'BASE_GAME_SPIN') {
266
+ const spinResult = await (0, test_engine_factory_1.executeSpin)(engine, finalState3.publicState, finalState3.privateState);
267
+ expect(spinResult.success).toBe(true);
268
+ }
269
+ });
270
+ });
271
+ describe('Debug Trigger Mechanics', () => {
272
+ it('should support all 4 bonus types via debug', async () => {
273
+ const types = ['bonusGame1', 'bonusGame2', 'bonusGame3', 'collectFeature'];
274
+ for (const type of types) {
275
+ const engine = (0, test_engine_factory_1.createEngine)();
276
+ const session = await (0, test_engine_factory_1.initSession)(engine);
277
+ const result = await (0, test_engine_factory_1.debugTriggerBonus)(engine, session.publicState, session.privateState, type);
278
+ expect(result.success).toBe(true);
279
+ }
280
+ });
281
+ it('should add bonus to pending list with valid bonusId', async () => {
282
+ const engine = (0, test_engine_factory_1.createEngine)();
283
+ const session = await (0, test_engine_factory_1.initSession)(engine);
284
+ const result = await (0, test_engine_factory_1.debugTriggerBonus)(engine, session.publicState, session.privateState, 'bonusGame1');
285
+ expect(result.success).toBe(true);
286
+ const bonus = result.privateState.pendingBonuses[0];
287
+ expect(bonus).toBeDefined();
288
+ expect(bonus.bonusId).toBeDefined();
289
+ expect(typeof bonus.bonusId).toBe('string');
290
+ // UUID format: at least 32 characters (without dashes) or 36 with dashes
291
+ expect(bonus.bonusId.length).toBeGreaterThanOrEqual(32);
292
+ });
293
+ it('should preserve bet amount in triggered bonus', async () => {
294
+ const engine = (0, test_engine_factory_1.createEngine)();
295
+ const session = await (0, test_engine_factory_1.initSession)(engine, 5.0);
296
+ const result = await (0, test_engine_factory_1.debugTriggerBonus)(engine, session.publicState, session.privateState, 'bonusGame1', 5.0);
297
+ expect(result.success).toBe(true);
298
+ const bonus = result.privateState.pendingBonuses[0];
299
+ expect(bonus.betAmount).toBe(5.0);
300
+ });
301
+ });
302
+ });
303
+ //# sourceMappingURL=bonus-handlers.test.js.map