@omnitronix/happy-panda-game-engine 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,13 +1,67 @@
1
1
  # Happy Panda Game Engine
2
2
 
3
- ![Version](https://img.shields.io/badge/version-0.0.1-blue.svg)
3
+ ![Version](https://img.shields.io/badge/version-0.0.2-blue.svg)
4
4
  ![License](https://img.shields.io/badge/license-UNLICENSED-red.svg)
5
- ![RTP](https://img.shields.io/badge/RTP-95.91%25-green.svg)
6
- ![C++ Parity](https://img.shields.io/badge/C++_Parity-VERIFIED-brightgreen.svg)
5
+ ![Package](https://img.shields.io/badge/package-%40omnitronix%2Fhappy--panda--game--engine-green.svg)
7
6
 
8
- TypeScript implementation of the Happy Red Panda (Cherry Master) slot game math model. Achieves C++ parity with the original `CherryMaster_A_2.cpp` implementation.
7
+ Pure business logic library for the Happy Panda (Cherry Master) slot game. A framework-agnostic, TypeScript-based game engine that handles all core game mechanics, bonus systems, and jackpot logic. Achieves C++ parity with the original `CherryMaster_A_2.cpp` implementation.
9
8
 
10
- ## C++ Parity Status - VERIFIED
9
+ ## What is Happy Panda Game Engine?
10
+
11
+ The Happy Panda Game Engine is an isolated, framework-agnostic library containing pure business logic for the Happy Panda slot game. It implements a command-based architecture that separates game logic from presentation and infrastructure concerns.
12
+
13
+ ### Core Characteristics
14
+
15
+ - **Pure Business Logic**: Contains only game rules, calculations, and state transitions
16
+ - **Framework Agnostic**: No dependencies on specific UI frameworks or web servers
17
+ - **Command-Based Architecture**: All interactions through well-defined commands
18
+ - **State Separation**: Distinguishes between public state (visible to players) and private state (server-side only)
19
+ - **RNG Integration**: Supports pluggable RNG providers for testing and production
20
+ - **Audit Trail**: Complete RNG outcome tracking for compliance and debugging
21
+ - **C++ Parity**: Verified against original C++ implementation with 100M spin validation
22
+
23
+ ### Game Specifications
24
+
25
+ - **RTP**: 96.05% (verified at 95.91% over 100M spins, within tolerance)
26
+ - **Game Type**: Classic 3x3 Slot
27
+ - **Version**: 1.0.0
28
+ - **Layout**: 3 reels x 3 rows
29
+ - **Win Evaluation**: 8/16 bidirectional paylines + wall wins + scatter wins
30
+ - **Modes**: 8-line (SINGLE) and 16-line (BOTH)
31
+
32
+ ## RTP Validation Results
33
+
34
+ ### 100 Million Spin Verification
35
+
36
+ ```
37
+ ================================================================
38
+ HAPPY PANDA RTP VERIFICATION - 100 MILLION SPINS
39
+ ================================================================
40
+
41
+ Configuration:
42
+ - Game Direction: SINGLE (8 lines)
43
+ - Bet per spin: 8
44
+ - Seeds: 10 x 10M = 100M total paid spins
45
+ - C++ Target RTP: 96.05%
46
+
47
+ RTP Breakdown by Spin Type:
48
+ ----------------------------------------------------------------
49
+ PAID_SPIN : 62.54% | C++: 63.51% | Diff: -0.97%
50
+ BONUS_JACKPOT : 16.24% | C++: 13.56% | Diff: +2.68%
51
+ BONUS_CHERRY : 3.96% | C++: 3.67% | Diff: +0.29%
52
+ BONUS_BELL : 5.06% | C++: 5.83% | Diff: -0.77%
53
+ BONUS_BAR1 : 5.47% | C++: 6.31% | Diff: -0.84%
54
+ RESPIN_CHERRY : 2.64% | C++: 3.18% | Diff: -0.54%
55
+ ----------------------------------------------------------------
56
+ TOTAL : 95.91% | C++: 96.05%
57
+
58
+ Statistics:
59
+ - Standard Deviation: 0.32%
60
+ - Range: 95.54% - 96.40%
61
+
62
+ STATUS: PASS - RTP within 0.15% tolerance
63
+ ================================================================
64
+ ```
11
65
 
12
66
  | Metric | TypeScript | C++ Target | Difference | Status |
13
67
  |--------|------------|------------|------------|--------|
@@ -21,13 +75,243 @@ TypeScript implementation of the Happy Red Panda (Cherry Master) slot game math
21
75
 
22
76
  **No configuration values from XLSX were modified** - only calculation logic was adjusted to match C++ behavior.
23
77
 
24
- ## Game Specifications
78
+ ## Features
25
79
 
26
- - **RTP**: 96.05% target (95.91% achieved, -0.14% variance)
27
- - **Game Type**: Classic 3x3 Slot
28
- - **Modes**: 8-line (SINGLE) and 16-line (BOTH)
29
- - **Bet Stakes**: 1, 2, 5, 10, 20, 50
30
- - **Symbols**: 12 base symbols + special symbols
80
+ - **Command-Based Architecture**: 3 different command types for all game operations
81
+ - **State Management**: Public/private state separation with type safety
82
+ - **RNG Integration**: Pluggable RNG providers with audit trail support
83
+ - **Bidirectional Paylines**: 8 or 16 paylines with left-to-right and right-to-left evaluation
84
+ - **Wall Wins**: Full 3x3 matrix wins with multiple types (pure, mixed, fruits, colors)
85
+ - **Scatter Wins**: Seven symbols pay anywhere on screen
86
+ - **Bonus System**: 5 distinct bonus types with counter-based triggers
87
+ - **Jackpot System**: Progressive and pool jackpots
88
+ - **TypeScript Support**: Complete type definitions for all interfaces
89
+ - **Comprehensive Testing**: Full test suite with Jest and 100M spin RTP validation
90
+
91
+ ## Architecture Overview
92
+
93
+ The game engine follows a modular architecture with clear separation of concerns:
94
+
95
+ ### Core Components
96
+
97
+ - **Game Engine** (`HappyPandaEngine`): Main entry point and command processor
98
+ - **V1 Wrapper** (`HappyPandaV1GameEngine`): Service integration wrapper with GameEngine interface
99
+ - **Spin Generator**: Grid generation with weighted random selection
100
+ - **Win Evaluator**: Line, wall, scatter, and special win detection
101
+ - **Counter Manager**: Bonus counter management and trigger logic
102
+ - **Jackpot Manager**: Progressive and pool jackpot handling
103
+
104
+ ### Directory Structure
105
+
106
+ ```
107
+ src/
108
+ ├── happy-panda-v1.game-engine.ts # V1 service wrapper
109
+ ├── engine/
110
+ │ └── happy-panda-engine.ts # Main engine class
111
+ ├── config/
112
+ │ └── happy-panda.config.ts # All game configuration (XLSX values)
113
+ ├── rng/
114
+ │ ├── spin-generator.ts # Grid generation with C++ parity
115
+ │ └── weighted-random.ts # Weighted random selection
116
+ ├── logic/
117
+ │ ├── handlers/
118
+ │ │ └── spin-handler.ts # Spin orchestration
119
+ │ └── services/
120
+ │ ├── win-evaluator.ts # Win detection (line/wall/scatter)
121
+ │ ├── counter-manager.ts # Bonus counter management
122
+ │ └── jackpot-manager.ts # Jackpot handling
123
+ ├── domain/
124
+ │ └── types.ts # Type definitions
125
+ └── __tests__/
126
+ ├── rtp-simulation.test.ts # RTP validation
127
+ ├── rtp-diagnostic.test.ts # RTP breakdown by spin type
128
+ ├── cpp-parity.test.ts # C++ parity tests
129
+ └── win-evaluator.test.ts # Win evaluation tests
130
+
131
+ docs/
132
+ ├── RTP-MATCHING.md # RTP implementation details
133
+ └── TEST-PROTOCOL-RTP-100M.md # 100M spin test protocol
134
+ ```
135
+
136
+ ## Installation
137
+
138
+ ```bash
139
+ npm install @omnitronix/happy-panda-game-engine
140
+ ```
141
+
142
+ > **Note**: This is a private package for Omnitronix internal use only (UNLICENSED).
143
+
144
+ ## Quick Start
145
+
146
+ ```typescript
147
+ import { HappyPandaV1GameEngine } from '@omnitronix/happy-panda-game-engine';
148
+
149
+ // Initialize game engine
150
+ const gameEngine = new HappyPandaV1GameEngine();
151
+
152
+ // Get engine info
153
+ const info = gameEngine.getGameEngineInfo();
154
+ // { gameCode: 'happy-panda', version: '1.0.0', rtp: 96.05, gameType: 'slot', gameName: 'Happy Panda', provider: 'Omnitronix' }
155
+
156
+ // Initialize session
157
+ const initCommand = {
158
+ id: 'cmd-init-123',
159
+ type: 'INIT_SESSION_STATE',
160
+ payload: {
161
+ gameDirection: 0, // 0 = SINGLE (8 lines), 1 = BOTH (16 lines)
162
+ betStake: 1
163
+ }
164
+ };
165
+
166
+ const initResult = await gameEngine.processCommand(null, null, initCommand);
167
+ // Returns: { success: true, publicState, privateState, outcome, rngOutcome }
168
+
169
+ // Process spin
170
+ const spinCommand = {
171
+ id: 'cmd-spin-456',
172
+ type: 'SPIN',
173
+ payload: {}
174
+ };
175
+
176
+ const spinResult = await gameEngine.processCommand(
177
+ initResult.publicState,
178
+ initResult.privateState,
179
+ spinCommand
180
+ );
181
+ ```
182
+
183
+ ## API Reference
184
+
185
+ ### Main Class: `HappyPandaV1GameEngine`
186
+
187
+ Implements the standard `GameEngine` interface for integration with game-engine-service.
188
+
189
+ #### Constructor
190
+
191
+ ```typescript
192
+ new HappyPandaV1GameEngine()
193
+ ```
194
+
195
+ - Initializes game engine with tracked RNG provider
196
+ - Ready to process commands immediately
197
+
198
+ #### Methods
199
+
200
+ **`getGameEngineInfo(): GameEngineInfo`**
201
+ Returns game metadata including code, version, RTP, and provider.
202
+
203
+ ```typescript
204
+ {
205
+ gameCode: 'happy-panda',
206
+ version: '1.0.0',
207
+ rtp: 96.05,
208
+ gameType: 'slot',
209
+ gameName: 'Happy Panda',
210
+ provider: 'Omnitronix'
211
+ }
212
+ ```
213
+
214
+ **`processCommand(publicState, privateState, command): Promise<CommandProcessingResult>`**
215
+ Main command processor that handles all game operations.
216
+
217
+ ### Commands
218
+
219
+ The game engine supports 3 different command types:
220
+
221
+ #### 1. INIT_SESSION_STATE
222
+
223
+ **Purpose**: Initialize game session state
224
+
225
+ **Payload**:
226
+ ```typescript
227
+ {
228
+ gameDirection?: number; // 0 = SINGLE (8 lines), 1 = BOTH (16 lines). Default: 0
229
+ betStake?: number; // Bet multiplier. Default: 1
230
+ }
231
+ ```
232
+
233
+ **Returns**: Initial public and private state with default values
234
+
235
+ #### 2. SPIN
236
+
237
+ **Purpose**: Execute a spin
238
+
239
+ **Payload**: None required (uses current state)
240
+
241
+ **Returns**: Spin result with grid, wins, and state updates
242
+
243
+ **Result Structure**:
244
+ ```typescript
245
+ {
246
+ success: true,
247
+ publicState: PublicState,
248
+ privateState: PrivateState,
249
+ outcome: {
250
+ sessionId: string,
251
+ grid: Symbol[][], // 3x3 grid
252
+ wins: SpinWinResult, // All wins
253
+ state: PublicState,
254
+ jackpotWon: number, // Jackpot payout (if any)
255
+ poolJackpotWon: number, // Pool jackpot payout (if any)
256
+ bonusTriggered: SpinType | null,
257
+ isBonusComplete: boolean
258
+ },
259
+ rngOutcome: RngOutcome // Audit trail
260
+ }
261
+ ```
262
+
263
+ #### 3. GET_SYMBOLS
264
+
265
+ **Purpose**: Retrieve symbol definitions
266
+
267
+ **Payload**: None
268
+
269
+ **Returns**: Array of symbol definitions with names and values
270
+
271
+ ### State Types
272
+
273
+ #### PublicState (visible to player)
274
+
275
+ ```typescript
276
+ {
277
+ gameDirection: GameDirection; // 0 = SINGLE, 1 = BOTH
278
+ betStake: number; // Bet multiplier
279
+ betGame: number; // Total bet (8 or 16 x betStake)
280
+ currentSpinType: SpinType; // Current spin type
281
+ spinsRemaining: number; // Remaining bonus spins
282
+ grid: Symbol[][]; // Current 3x3 grid
283
+ bonusJackpotValue: number; // Progressive jackpot
284
+ poolJackpotValue: number; // Pool jackpot
285
+ hasPendingBonus: boolean; // Indicates pending bonus
286
+ }
287
+ ```
288
+
289
+ #### PrivateState (server-side only)
290
+
291
+ ```typescript
292
+ {
293
+ counters: BonusCounters; // Bonus trigger counters
294
+ pendingBonuses: PendingBonuses; // Queued bonuses
295
+ nextSpinType: SpinType; // Next spin type
296
+ accumulatedBonusWins: number; // Bonus sequence wins
297
+ centerCherrySymbol: Symbol | null; // For respin feature
298
+ }
299
+ ```
300
+
301
+ #### CommandProcessingResult
302
+
303
+ ```typescript
304
+ {
305
+ success: boolean;
306
+ publicState: PublicState;
307
+ privateState: PrivateState;
308
+ outcome?: SpinResponse;
309
+ message?: string;
310
+ rngOutcome?: RngOutcome; // RNG audit trail
311
+ }
312
+ ```
313
+
314
+ ## Game Mechanics
31
315
 
32
316
  ### Symbol Set
33
317
 
@@ -46,148 +330,336 @@ TypeScript implementation of the Happy Red Panda (Cherry Master) slot game math
46
330
  | 10 | CH_S | Cherry Special |
47
331
  | 11 | BA_S | Super Bar |
48
332
 
49
- ## Win Types
333
+ ### Win Types
334
+
335
+ #### 1. Line Wins
50
336
 
51
- ### 1. Line Wins
52
- - 8 paylines (8-line mode) or 16 paylines (16-line mode)
337
+ - 8 paylines (SINGLE mode) or 16 paylines (BOTH mode)
53
338
  - Evaluated left-to-right and right-to-left in 16-line mode
54
339
  - Cherry pays on 1, 2, or 3 matches
340
+ - Line shapes follow classic 3x3 patterns
55
341
 
56
- ### 2. Wall Wins (Matrix/Screen Wins)
57
- - Full 3x3 of same symbol (or mixed for special types)
58
- - Types: Pure symbol, Cherry mix (CH+CH_S), Any Bar mix, All Fruits, All Colors
59
- - **Suppresses** line wins when active
342
+ #### 2. Wall Wins (Matrix/Screen Wins)
343
+
344
+ - Full 3x3 of same symbol
345
+ - Special mixed types:
346
+ - **Cherry Mix**: CH + CH_S combinations
347
+ - **Any Bar Mix**: BA1 + BA2 + BA3 + BA_S
348
+ - **All Fruits**: ME + BE + PR + OR
349
+ - **All Colors**: Mixed symbol colors
350
+ - **Important**: Wall wins suppress line wins when active
351
+
352
+ #### 3. Scatter Wins (Seven)
60
353
 
61
- ### 3. Scatter Wins (Seven)
62
354
  - 2-9 Seven symbols anywhere on screen
63
355
  - Super Seven only = doubled payout
64
- - **Suppresses** line wins when active
356
+ - **Important**: Scatter wins suppress line wins when active
357
+
358
+ #### 4. Special Wins (Bonus modes only)
65
359
 
66
- ### 4. Special Wins (Bonus modes only)
67
- - Cherry pieces (Jackpot Bonus)
68
- - Bell scatter (Bell Bonus)
69
- - Super Bar scatter (Bar1 Bonus)
360
+ - **Cherry Pieces**: Count of cherry pieces (Jackpot Bonus)
361
+ - **Bell Scatter**: Bell count anywhere (Bell Bonus)
362
+ - **Super Bar Scatter**: Super Bar count anywhere (Bar1 Bonus)
70
363
 
71
- ## Bonus System
364
+ ### Bonus System
72
365
 
73
- ### Bonus Types
366
+ Five distinct bonus types with counter-based triggers:
74
367
 
75
- | Bonus | Trigger | Description |
76
- |-------|---------|-------------|
77
- | Jackpot | 3x Cherry on first 8 lines | Cherry pieces with jackpot chance |
78
- | Cherry | Cherry pair counter reaches 0 | Cherry respin feature |
79
- | Bell | Bell triple counter reaches 0 | Bell scatter pays |
80
- | Bar1 | Bar1 triple counter reaches 0 | Super Bar scatter pays |
81
- | Respin | Lone center cherry on losing spin | 1-2 corner respins |
368
+ | Bonus | Trigger | Spins | Description |
369
+ |-------|---------|-------|-------------|
370
+ | **Jackpot** | 3x Cherry on first 8 lines | 3 | Cherry pieces with progressive jackpot chance |
371
+ | **Cherry** | Cherry pair counter = 0 | Variable | Cherry respin feature |
372
+ | **Bell** | Bell triple counter = 0 | 5 | Bell scatter pays + pool jackpot |
373
+ | **Bar1** | Bar1 triple counter = 0 | 7 | Super Bar scatter pays |
374
+ | **Respin** | Lone center cherry on losing spin | 1-2 | Corner respin feature |
82
375
 
83
376
  ### Counter System
84
- - Counters decrement on specific symbol combinations
85
- - When counter reaches 0, bonus triggers
86
- - Each bonus has different spin allocation
87
377
 
88
- ## Project Structure
378
+ Each bonus has an associated counter that decrements on specific symbol combinations:
89
379
 
90
- ```
91
- src/
92
- ├── engine/
93
- │ └── happy-panda-engine.ts # Main engine class
94
- ├── config/
95
- │ └── happy-panda.config.ts # All game configuration (XLSX values)
96
- ├── rng/
97
- │ ├── spin-generator.ts # Grid generation with C++ parity
98
- │ └── weighted-random.ts # Weighted random selection
99
- ├── logic/
100
- │ ├── handlers/
101
- │ │ └── spin-handler.ts # Spin orchestration
102
- │ └── services/
103
- │ ├── win-evaluator.ts # Win detection
104
- │ └── counter-manager.ts # Bonus counter management
105
- ├── domain/
106
- │ └── types.ts # Type definitions
107
- └── __tests__/
108
- ├── rtp-simulation.test.ts # RTP validation
109
- ├── rtp-diagnostic.test.ts # RTP breakdown by spin type
110
- └── cpp-parity.test.ts # C++ parity tests
380
+ - **Jackpot Counter**: Decrements on specific patterns, triggers at 0
381
+ - **Cherry Counter**: Decrements on cherry pairs, resets to 6 or 9
382
+ - **Bell Counter**: Decrements on bell triples, resets to 3 or 5
383
+ - **Bar1 Counter**: Decrements on bar1 triples, resets to 1
111
384
 
112
- docs/
113
- ├── RTP-MATCHING.md # RTP implementation details
114
- └── TEST-PROTOCOL-RTP-100M.md # 100M spin test protocol
385
+ ### Jackpot System
386
+
387
+ Two jackpot types:
388
+
389
+ - **Progressive Jackpot** (`bonusJackpotValue`): Accumulates on losing paid spins, paid during Jackpot Bonus
390
+ - **Pool Jackpot** (`poolJackpotValue`): Accumulates separately, paid when Bell Bonus triggers
391
+
392
+ ## Integration Examples
393
+
394
+ ### Example 1: Basic RGS Integration
395
+
396
+ ```typescript
397
+ import { HappyPandaV1GameEngine } from '@omnitronix/happy-panda-game-engine';
398
+
399
+ class GameSessionService {
400
+ private gameEngine: HappyPandaV1GameEngine;
401
+
402
+ constructor() {
403
+ this.gameEngine = new HappyPandaV1GameEngine();
404
+ }
405
+
406
+ async createSession(userId: string, gameDirection: number, betStake: number) {
407
+ const initCommand = {
408
+ id: `init-${userId}-${Date.now()}`,
409
+ type: 'INIT_SESSION_STATE',
410
+ payload: { gameDirection, betStake }
411
+ };
412
+
413
+ const result = await this.gameEngine.processCommand(null, null, initCommand);
414
+
415
+ // Store result.publicState in database (visible to player)
416
+ // Store result.privateState securely (server-side only)
417
+ // Store result.rngOutcome for audit trail
418
+
419
+ return {
420
+ sessionId: userId,
421
+ publicState: result.publicState,
422
+ // Never send privateState to client!
423
+ };
424
+ }
425
+
426
+ async processSpin(sessionId: string, publicState: any, privateState: any) {
427
+ const spinCommand = {
428
+ id: `spin-${sessionId}-${Date.now()}`,
429
+ type: 'SPIN',
430
+ payload: {}
431
+ };
432
+
433
+ const result = await this.gameEngine.processCommand(
434
+ publicState,
435
+ privateState,
436
+ spinCommand
437
+ );
438
+
439
+ // Update states in database
440
+ // Log RNG outcome for compliance
441
+ // Return outcome to client
442
+
443
+ return {
444
+ outcome: result.outcome,
445
+ publicState: result.publicState,
446
+ rngOutcome: result.rngOutcome, // For audit
447
+ // privateState stays on server
448
+ };
449
+ }
450
+ }
115
451
  ```
116
452
 
117
- ## Usage
453
+ ### Example 2: Handling Bonus Triggers
118
454
 
119
455
  ```typescript
120
- import { HappyPandaEngine } from '@omnitronix/happy-panda-game-engine';
121
- import { CommandType, GameDirection } from '@omnitronix/happy-panda-game-engine';
456
+ async handleSpinResult(spinResult: any) {
457
+ const { outcome, publicState, privateState } = spinResult;
458
+
459
+ // Check win types
460
+ console.log(`Total Payout: ${outcome.wins.totalPayout}`);
461
+ console.log(`Line Wins: ${outcome.wins.lineWins.length}`);
462
+ console.log(`Wall Win: ${outcome.wins.wallWin ? 'Yes' : 'No'}`);
463
+ console.log(`Scatter Wins: ${outcome.wins.scatterWins.length}`);
464
+
465
+ // Check for jackpot
466
+ if (outcome.jackpotWon > 0) {
467
+ console.log(`JACKPOT WON: ${outcome.jackpotWon}`);
468
+ }
469
+
470
+ if (outcome.poolJackpotWon > 0) {
471
+ console.log(`POOL JACKPOT WON: ${outcome.poolJackpotWon}`);
472
+ }
473
+
474
+ // Check if bonus was triggered
475
+ if (outcome.bonusTriggered) {
476
+ return {
477
+ type: 'BONUS_TRIGGERED',
478
+ bonusType: outcome.bonusTriggered,
479
+ spinsRemaining: publicState.spinsRemaining,
480
+ message: `${outcome.bonusTriggered} bonus triggered!`
481
+ };
482
+ }
483
+
484
+ // Check if bonus sequence completed
485
+ if (outcome.isBonusComplete) {
486
+ return {
487
+ type: 'BONUS_COMPLETE',
488
+ message: 'Bonus sequence completed'
489
+ };
490
+ }
491
+
492
+ return {
493
+ type: 'REGULAR_SPIN',
494
+ totalPayout: outcome.wins.totalPayout
495
+ };
496
+ }
497
+ ```
122
498
 
123
- // Create engine with RNG provider
124
- const engine = new HappyPandaEngine(rngProvider);
499
+ ### Example 3: Game Direction Modes
125
500
 
126
- // Initialize session
127
- const { state } = await engine.handleCommand({
128
- command: CommandType.INIT_SESSION_STATE,
129
- sessionId: 'session-123',
130
- gameDirection: GameDirection.SINGLE, // 8-line mode
131
- betStake: 1,
501
+ ```typescript
502
+ // 8-line mode (SINGLE)
503
+ const single8Lines = await gameEngine.processCommand(null, null, {
504
+ id: 'init-single',
505
+ type: 'INIT_SESSION_STATE',
506
+ payload: { gameDirection: 0, betStake: 1 }
132
507
  });
508
+ // Total bet = 8 x betStake = 8
133
509
 
134
- // Execute spin
135
- const { state: newState, response } = await engine.handleCommand(
136
- { command: CommandType.SPIN, sessionId: 'session-123' },
137
- state
138
- );
139
-
140
- console.log(response.wins.totalPayout);
141
- console.log(response.grid);
510
+ // 16-line mode (BOTH)
511
+ const both16Lines = await gameEngine.processCommand(null, null, {
512
+ id: 'init-both',
513
+ type: 'INIT_SESSION_STATE',
514
+ payload: { gameDirection: 1, betStake: 1 }
515
+ });
516
+ // Total bet = 16 x betStake = 16
142
517
  ```
143
518
 
144
- ## Testing
519
+ ## Development
520
+
521
+ ### Running Tests
145
522
 
146
523
  ```bash
147
524
  # Run all tests
148
525
  npm test
149
526
 
527
+ # Run tests in watch mode
528
+ npm run test:watch
529
+
530
+ # Run with coverage
531
+ npm run test:cov
532
+
150
533
  # Run RTP diagnostic (100K spins)
151
534
  npm test -- --testNamePattern="RTP Diagnostic"
152
535
 
153
- # Run specific test file
154
- npm test -- --testPathPattern="rtp-simulation"
536
+ # Run C++ parity tests
537
+ npm test -- --testPathPattern="cpp-parity"
155
538
  ```
156
539
 
157
- ## RTP Validation Results
540
+ ### Building
158
541
 
159
- ### 100 Million Spin Simulation
542
+ ```bash
543
+ # Clean build
544
+ npm run clean
545
+ npm run build
160
546
 
547
+ # Development mode
548
+ npm run build
161
549
  ```
162
- ================================================================
163
- HAPPY PANDA RTP VERIFICATION - 100 MILLION SPINS
164
- ================================================================
165
550
 
166
- Configuration:
167
- - Game Direction: SINGLE (8 lines)
168
- - Bet per spin: 8
169
- - Seeds: 10 x 10M = 100M total paid spins
170
- - C++ Target RTP: 96.05%
551
+ ### Linting
171
552
 
172
- RTP Breakdown by Spin Type:
173
- ----------------------------------------------------------------
174
- PAID_SPIN : 62.54% | C++: 63.51% | Diff: -0.97%
175
- BONUS_JACKPOT : 16.24% | C++: 13.56% | Diff: +2.68%
176
- BONUS_CHERRY : 3.96% | C++: 3.67% | Diff: +0.29%
177
- BONUS_BELL : 5.06% | C++: 5.83% | Diff: -0.77%
178
- BONUS_BAR1 : 5.47% | C++: 6.31% | Diff: -0.84%
179
- RESPIN_CHERRY : 2.64% | C++: 3.18% | Diff: -0.54%
180
- ----------------------------------------------------------------
181
- TOTAL : 95.91% | C++: 96.05%
553
+ ```bash
554
+ # Check for issues
555
+ npm run lint
182
556
 
183
- Statistics:
184
- - Standard Deviation: 0.32%
185
- - Range: 95.54% - 96.40%
557
+ # Auto-fix issues
558
+ npm run lint -- --fix
559
+ ```
186
560
 
187
- STATUS: PASS - RTP within 0.15% tolerance
188
- ================================================================
561
+ ## Configuration
562
+
563
+ Game configuration located in `src/config/happy-panda.config.ts`:
564
+
565
+ - Symbol definitions and IDs
566
+ - Paytable values for all win types
567
+ - Line shapes for 8 and 16 line modes
568
+ - Reel strip weights by spin type
569
+ - Bonus counter initial values
570
+ - Jackpot accumulation rates
571
+
572
+ All values match the original Excel specification (`CherryMaster_A_2_26.05.2025.xlsx`).
573
+
574
+ ## Type Exports
575
+
576
+ The package exports all necessary types for TypeScript integration:
577
+
578
+ ```typescript
579
+ import {
580
+ // V1 Game Engine
581
+ HappyPandaV1GameEngine,
582
+ GameEngine,
583
+ GameEngineInfo,
584
+ GameActionCommand,
585
+ CommandProcessingResult,
586
+ RngOutcome,
587
+ RngOutcomeRecord,
588
+
589
+ // Core Engine
590
+ HappyPandaEngine,
591
+
592
+ // Configuration
593
+ Symbol,
594
+ SpinType,
595
+ ScreenWinType,
596
+ GRID,
597
+ LINE_SHAPES,
598
+ LINES_PER_DIRECTION,
599
+
600
+ // Domain Types
601
+ Grid,
602
+ Position,
603
+ LineWin,
604
+ WallWin,
605
+ ScatterWin,
606
+ SpecialWin,
607
+ SpinWinResult,
608
+ BonusCounters,
609
+ PendingBonuses,
610
+ JackpotState,
611
+ GameDirection,
612
+ GameState,
613
+ PublicState,
614
+ PrivateState,
615
+ SpinRequest,
616
+ SpinResponse,
617
+ SessionState,
618
+ RngProvider,
619
+ CommandType,
620
+ GameCommand,
621
+
622
+ // Logic Services
623
+ evaluateSpin,
624
+ evaluateLineWins,
625
+ evaluateWallWin,
626
+ evaluateScatterWins,
627
+ generateGrid,
628
+ } from '@omnitronix/happy-panda-game-engine';
189
629
  ```
190
630
 
631
+ ## Key Differences from Traditional Slots
632
+
633
+ ### Classic 3x3 Layout
634
+
635
+ - **Traditional Modern**: 5+ reels, multiple rows
636
+ - **Happy Panda**: Classic 3x3 grid with bidirectional paylines
637
+ - **Advantage**: Nostalgic gameplay, simpler visual presentation
638
+
639
+ ### Bidirectional Paylines
640
+
641
+ - **8-Line Mode**: Left-to-right only
642
+ - **16-Line Mode**: Both directions (LTR + RTL)
643
+ - **Strategic Choice**: Player chooses mode based on bet preference
644
+
645
+ ### Wall Wins (Matrix Wins)
646
+
647
+ - **Screen-filling wins**: Full 3x3 of same/related symbols
648
+ - **Multiple types**: Pure, cherry mix, bar mix, fruits, colors
649
+ - **Important**: Wall wins suppress line wins (no double-counting)
650
+
651
+ ### Counter-Based Bonuses
652
+
653
+ - **Not scatter-triggered**: Bonuses trigger via counter depletion
654
+ - **Progressive build-up**: Counters decrement on specific symbol combos
655
+ - **Multiple bonus types**: 5 distinct bonus modes with different mechanics
656
+
657
+ ### Dual Jackpot System
658
+
659
+ - **Progressive Jackpot**: Accumulates on losses, paid in Jackpot Bonus
660
+ - **Pool Jackpot**: Separate accumulator, paid with Bell Bonus
661
+ - **Dual opportunity**: Two paths to jackpot wins
662
+
191
663
  ## Documentation
192
664
 
193
665
  - **Test Protocol**: `docs/TEST-PROTOCOL-RTP-100M.md` - Complete 100M spin test results
@@ -195,18 +667,10 @@ Statistics:
195
667
  - **C++ Source**: `math/Happy Red Panda/CherryMaster_A_2.cpp`
196
668
  - **Excel Spec**: `math/Happy Red Panda/CherryMaster_A_2_26.05.2025.xlsx`
197
669
 
198
- ## Installation
199
-
200
- ```bash
201
- npm install @omnitronix/happy-panda-game-engine
202
- ```
203
-
204
- ## Build
205
-
206
- ```bash
207
- npm run build
208
- ```
209
-
210
670
  ## License
211
671
 
212
672
  UNLICENSED - Internal use only for Omnitronix
673
+
674
+ ## Support
675
+
676
+ For questions or issues, contact the Omnitronix development team.
@@ -4,36 +4,35 @@
4
4
  *
5
5
  * Wrapper class that adapts HappyPandaEngine to the standard GameEngine interface
6
6
  * expected by the game-engine-service infrastructure.
7
+ *
8
+ * Uses the proper RNG infrastructure matching Heroes of Aether and Bonny's Fortune:
9
+ * - RngClientFactory for environment-based client selection
10
+ * - RngService for audit trail tracking
11
+ * - Full RNG outcome reporting for compliance
7
12
  */
8
13
  Object.defineProperty(exports, "__esModule", { value: true });
9
14
  exports.HappyPandaV1GameEngine = void 0;
10
15
  const happy_panda_engine_1 = require("./engine/happy-panda-engine");
11
16
  const types_1 = require("./domain/types");
17
+ const rng_client_factory_1 = require("./rng/rng-client.factory");
18
+ const rng_service_1 = require("./rng/rng-service");
12
19
  /**
13
- * RNG adapter that tracks outcomes for the game engine service
20
+ * RNG Provider adapter that bridges RngService to the engine's RngProvider interface
14
21
  */
15
- class TrackedRngProvider {
16
- constructor() {
17
- this.outcomes = new Map();
22
+ class RngServiceAdapter {
23
+ constructor(rngService) {
24
+ this.rngService = rngService;
18
25
  this.currentCommandId = '';
19
- this.seedCounter = 0;
26
+ this.actionCounter = 0;
20
27
  }
21
28
  setCommandId(commandId) {
22
29
  this.currentCommandId = commandId;
23
- if (!this.outcomes.has(commandId)) {
24
- this.outcomes.set(commandId, []);
25
- }
30
+ this.actionCounter = 0;
26
31
  }
27
32
  async random(max) {
28
- const result = Math.floor(Math.random() * max);
29
- const record = {
30
- result,
31
- seed: this.seedCounter++,
32
- min: 0,
33
- max: max - 1,
34
- };
35
- this.outcomes.get(this.currentCommandId)?.push(record);
36
- return result;
33
+ const actionId = `rng_${this.actionCounter++}`;
34
+ const { value } = await this.rngService.getSingleNumber(0, max - 1, this.currentCommandId, actionId);
35
+ return value;
37
36
  }
38
37
  async randomBatch(count, max) {
39
38
  const results = [];
@@ -42,33 +41,29 @@ class TrackedRngProvider {
42
41
  }
43
42
  return results;
44
43
  }
45
- getOutcomes(commandId) {
46
- const records = this.outcomes.get(commandId) || [];
47
- const outcome = {};
48
- records.forEach((record, index) => {
49
- outcome[`rng_${index}`] = record;
50
- });
51
- return outcome;
52
- }
53
- clearOutcomes(commandId) {
54
- this.outcomes.delete(commandId);
55
- }
56
44
  }
57
45
  /**
58
46
  * Happy Panda V1 Game Engine
59
47
  *
60
48
  * Implements the standard GameEngine interface for use with game-engine-service.
49
+ * Uses proper RNG infrastructure with full audit trail support.
61
50
  */
62
51
  class HappyPandaV1GameEngine {
63
- constructor() {
52
+ constructor(config = {}) {
64
53
  this.gameCode = 'happy-panda';
65
54
  this.version = '1.0.0';
66
55
  this.rtp = 96.05;
67
56
  this.gameType = 'slot';
68
57
  this.gameName = 'Happy Panda';
69
58
  this.provider = 'Omnitronix';
70
- this.rngProvider = new TrackedRngProvider();
71
- this.engine = new happy_panda_engine_1.HappyPandaEngine(this.rngProvider);
59
+ // Create RNG client based on environment (local for dev, external for prod)
60
+ const rngClient = rng_client_factory_1.RngClientFactory.create({ metrics: config.metrics });
61
+ // Wrap in service for audit trail tracking
62
+ this.rngService = new rng_service_1.RngService(rngClient);
63
+ // Create adapter to bridge to engine's RngProvider interface
64
+ this.rngAdapter = new RngServiceAdapter(this.rngService);
65
+ // Initialize engine with RNG adapter
66
+ this.engine = new happy_panda_engine_1.HappyPandaEngine(this.rngAdapter);
72
67
  }
73
68
  getGameEngineInfo() {
74
69
  return {
@@ -81,7 +76,9 @@ class HappyPandaV1GameEngine {
81
76
  };
82
77
  }
83
78
  async processCommand(publicState, privateState, command) {
84
- this.rngProvider.setCommandId(command.id);
79
+ // Register command for RNG audit trail
80
+ this.rngService.registerCommand(command.id);
81
+ this.rngAdapter.setCommandId(command.id);
85
82
  try {
86
83
  switch (command.type) {
87
84
  case 'INIT_SESSION_STATE': {
@@ -94,8 +91,8 @@ class HappyPandaV1GameEngine {
94
91
  gameDirection,
95
92
  betStake,
96
93
  });
97
- const rngOutcome = this.rngProvider.getOutcomes(command.id);
98
- this.rngProvider.clearOutcomes(command.id);
94
+ const rngOutcome = this.rngService.getRngOutcomeForCommand(command.id);
95
+ this.rngService.removeCommand(command.id);
99
96
  return {
100
97
  success: true,
101
98
  publicState: result.state.publicState,
@@ -107,6 +104,7 @@ class HappyPandaV1GameEngine {
107
104
  }
108
105
  case 'SPIN': {
109
106
  if (!publicState || !privateState) {
107
+ this.rngService.removeCommand(command.id);
110
108
  return {
111
109
  success: false,
112
110
  publicState: publicState,
@@ -126,8 +124,8 @@ class HappyPandaV1GameEngine {
126
124
  command: types_1.CommandType.SPIN,
127
125
  sessionId: command.id,
128
126
  }, sessionState);
129
- const rngOutcome = this.rngProvider.getOutcomes(command.id);
130
- this.rngProvider.clearOutcomes(command.id);
127
+ const rngOutcome = this.rngService.getRngOutcomeForCommand(command.id);
128
+ this.rngService.removeCommand(command.id);
131
129
  return {
132
130
  success: true,
133
131
  publicState: result.state.publicState,
@@ -142,7 +140,7 @@ class HappyPandaV1GameEngine {
142
140
  command: types_1.CommandType.GET_SYMBOLS,
143
141
  sessionId: command.id,
144
142
  });
145
- this.rngProvider.clearOutcomes(command.id);
143
+ this.rngService.removeCommand(command.id);
146
144
  return {
147
145
  success: true,
148
146
  publicState: publicState,
@@ -152,7 +150,7 @@ class HappyPandaV1GameEngine {
152
150
  };
153
151
  }
154
152
  default: {
155
- this.rngProvider.clearOutcomes(command.id);
153
+ this.rngService.removeCommand(command.id);
156
154
  return {
157
155
  success: false,
158
156
  publicState: publicState,
@@ -163,7 +161,7 @@ class HappyPandaV1GameEngine {
163
161
  }
164
162
  }
165
163
  catch (error) {
166
- this.rngProvider.clearOutcomes(command.id);
164
+ this.rngService.removeCommand(command.id);
167
165
  return {
168
166
  success: false,
169
167
  publicState: publicState,
@@ -1 +1 @@
1
- {"version":3,"file":"happy-panda-v1.game-engine.js","sourceRoot":"","sources":["../src/happy-panda-v1.game-engine.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"happy-panda-v1.game-engine.js","sourceRoot":"","sources":["../src/happy-panda-v1.game-engine.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@
7
7
  * @packageDocumentation
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.generateGrid = exports.evaluateScatterWins = exports.evaluateWallWin = exports.evaluateLineWins = exports.evaluateSpin = exports.CommandType = exports.GameDirection = exports.LINES_PER_DIRECTION = exports.LINE_SHAPES = exports.GRID = exports.ScreenWinType = exports.SpinType = exports.Symbol = exports.HappyPandaEngine = exports.HappyPandaV1GameEngine = void 0;
10
+ exports.RngService = exports.RngClientFactory = exports.DummyRngClient = exports.generateGrid = exports.evaluateScatterWins = exports.evaluateWallWin = exports.evaluateLineWins = exports.evaluateSpin = exports.CommandType = exports.GameDirection = exports.LINES_PER_DIRECTION = exports.LINE_SHAPES = exports.GRID = exports.ScreenWinType = exports.SpinType = exports.Symbol = exports.HappyPandaEngine = exports.HappyPandaV1GameEngine = void 0;
11
11
  // V1 Game Engine (for game-engine-service integration)
12
12
  var happy_panda_v1_game_engine_1 = require("./happy-panda-v1.game-engine");
13
13
  Object.defineProperty(exports, "HappyPandaV1GameEngine", { enumerable: true, get: function () { return happy_panda_v1_game_engine_1.HappyPandaV1GameEngine; } });
@@ -34,4 +34,10 @@ Object.defineProperty(exports, "evaluateWallWin", { enumerable: true, get: funct
34
34
  Object.defineProperty(exports, "evaluateScatterWins", { enumerable: true, get: function () { return services_1.evaluateScatterWins; } });
35
35
  var rng_1 = require("./rng");
36
36
  Object.defineProperty(exports, "generateGrid", { enumerable: true, get: function () { return rng_1.generateGrid; } });
37
+ var dummy_rng_client_1 = require("./rng/dummy-rng-client");
38
+ Object.defineProperty(exports, "DummyRngClient", { enumerable: true, get: function () { return dummy_rng_client_1.DummyRngClient; } });
39
+ var rng_client_factory_1 = require("./rng/rng-client.factory");
40
+ Object.defineProperty(exports, "RngClientFactory", { enumerable: true, get: function () { return rng_client_factory_1.RngClientFactory; } });
41
+ var rng_service_1 = require("./rng/rng-service");
42
+ Object.defineProperty(exports, "RngService", { enumerable: true, get: function () { return rng_service_1.RngService; } });
37
43
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;;AAEH,uDAAuD;AACvD,2EAAsE;AAA7D,oIAAA,sBAAsB,OAAA;AAU/B,cAAc;AACd,mCAA4C;AAAnC,0GAAA,gBAAgB,OAAA;AAEzB,gBAAgB;AAChB,mCAOkB;AANhB,gGAAA,MAAM,OAAA;AACN,kGAAA,QAAQ,OAAA;AACR,uGAAA,aAAa,OAAA;AACb,8FAAA,IAAI,OAAA;AACJ,qGAAA,WAAW,OAAA;AACX,6GAAA,mBAAmB,OAAA;AAGrB,eAAe;AACf,mCAwBkB;AAbhB,uGAAA,aAAa,OAAA;AAQb,qGAAA,WAAW,OAAA;AAOb,yCAAyC;AACzC,6CAAwG;AAA/F,wGAAA,YAAY,OAAA;AAAE,4GAAA,gBAAgB,OAAA;AAAE,2GAAA,eAAe,OAAA;AAAE,+GAAA,mBAAmB,OAAA;AAC7E,6BAAqC;AAA5B,mGAAA,YAAY,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;;AAEH,uDAAuD;AACvD,2EAAsE;AAA7D,oIAAA,sBAAsB,OAAA;AAU/B,cAAc;AACd,mCAA4C;AAAnC,0GAAA,gBAAgB,OAAA;AAEzB,gBAAgB;AAChB,mCAOkB;AANhB,gGAAA,MAAM,OAAA;AACN,kGAAA,QAAQ,OAAA;AACR,uGAAA,aAAa,OAAA;AACb,8FAAA,IAAI,OAAA;AACJ,qGAAA,WAAW,OAAA;AACX,6GAAA,mBAAmB,OAAA;AAGrB,eAAe;AACf,mCAwBkB;AAbhB,uGAAA,aAAa,OAAA;AAQb,qGAAA,WAAW,OAAA;AAOb,yCAAyC;AACzC,6CAAwG;AAA/F,wGAAA,YAAY,OAAA;AAAE,4GAAA,gBAAgB,OAAA;AAAE,2GAAA,eAAe,OAAA;AAAE,+GAAA,mBAAmB,OAAA;AAC7E,6BAAqC;AAA5B,mGAAA,YAAY,OAAA;AAQrB,2DAAwD;AAA/C,kHAAA,cAAc,OAAA;AACvB,+DAA6E;AAApE,sHAAA,gBAAgB,OAAA;AACzB,iDAA+C;AAAtC,yGAAA,UAAU,OAAA"}
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ /**
3
+ * Dummy RNG Client
4
+ *
5
+ * Local RNG client for development and testing.
6
+ * Uses Math.random() for all operations.
7
+ * Returns seed value of 0 (non-cryptographic, for testing only).
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.DummyRngClient = void 0;
11
+ class DummyRngClient {
12
+ /**
13
+ * Generate a single random integer in range [min, max]
14
+ */
15
+ async getSingleNumber(min, max, _seed) {
16
+ const randomValue = Math.random();
17
+ const result = Math.floor(randomValue * (max - min + 1)) + min;
18
+ return { value: result, seed: 0 };
19
+ }
20
+ /**
21
+ * Generate multiple random integers in range [min, max]
22
+ */
23
+ async getBatchNumbers(min, max, count, _seed) {
24
+ const results = [];
25
+ for (let i = 0; i < count; i++) {
26
+ const randomValue = Math.random();
27
+ results.push(Math.floor(randomValue * (max - min + 1)) + min);
28
+ }
29
+ return results;
30
+ }
31
+ /**
32
+ * Generate multiple random integers with individual seeds
33
+ */
34
+ async getBatchNumbersWithSeeds(min, max, count, _seed) {
35
+ const results = [];
36
+ for (let i = 0; i < count; i++) {
37
+ const randomValue = Math.random();
38
+ results.push({
39
+ value: Math.floor(randomValue * (max - min + 1)) + min,
40
+ seed: 0,
41
+ });
42
+ }
43
+ return results;
44
+ }
45
+ /**
46
+ * Generate a single random float in range [0, 1)
47
+ */
48
+ async getSingleFloat(_seed) {
49
+ return { value: Math.random(), seed: 0 };
50
+ }
51
+ /**
52
+ * Generate multiple random floats in range [0, 1)
53
+ */
54
+ async getBatchFloats(count, _seed) {
55
+ const values = [];
56
+ const seeds = [];
57
+ for (let i = 0; i < count; i++) {
58
+ values.push(Math.random());
59
+ seeds.push(0);
60
+ }
61
+ return { values, seeds };
62
+ }
63
+ /**
64
+ * Alternative method for single random number
65
+ */
66
+ async getRandomNumber(min, max, _seed) {
67
+ return this.getSingleNumber(min, max, _seed);
68
+ }
69
+ /**
70
+ * Alternative method for single random float
71
+ */
72
+ async getRandomFloat(_seed) {
73
+ return this.getSingleFloat(_seed);
74
+ }
75
+ }
76
+ exports.DummyRngClient = DummyRngClient;
77
+ //# sourceMappingURL=dummy-rng-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dummy-rng-client.js","sourceRoot":"","sources":["../../src/rng/dummy-rng-client.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;;AAQH,MAAa,cAAc;IACzB;;OAEG;IACH,KAAK,CAAC,eAAe,CACnB,GAAW,EACX,GAAW,EACX,KAAc;QAEd,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QAClC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,GAAG,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC;QAC/D,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;IACpC,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,eAAe,CACnB,GAAW,EACX,GAAW,EACX,KAAa,EACb,KAAc;QAEd,MAAM,OAAO,GAAa,EAAE,CAAC;QAC7B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;YAC/B,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;YAClC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,GAAG,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC;QAChE,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,wBAAwB,CAC5B,GAAW,EACX,GAAW,EACX,KAAa,EACb,KAAc;QAEd,MAAM,OAAO,GAAwB,EAAE,CAAC;QACxC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;YAC/B,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;YAClC,OAAO,CAAC,IAAI,CAAC;gBACX,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW,GAAG,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG;gBACtD,IAAI,EAAE,CAAC;aACR,CAAC,CAAC;QACL,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,cAAc,CAAC,KAAc;QACjC,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;IAC3C,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,cAAc,CAClB,KAAa,EACb,KAAc;QAEd,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;YAC/B,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;YAC3B,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAChB,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAC3B,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,eAAe,CACnB,GAAW,EACX,GAAW,EACX,KAAc;QAEd,OAAO,IAAI,CAAC,eAAe,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;IAC/C,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,cAAc,CAAC,KAAc;QACjC,OAAO,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IACpC,CAAC;CACF;AA3FD,wCA2FC"}
package/dist/rng/index.js CHANGED
@@ -19,4 +19,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
19
19
  */
20
20
  __exportStar(require("./weighted-random"), exports);
21
21
  __exportStar(require("./spin-generator"), exports);
22
+ // RNG Service and Client Infrastructure
23
+ __exportStar(require("./rng-client.interface"), exports);
24
+ __exportStar(require("./dummy-rng-client"), exports);
25
+ __exportStar(require("./rng-client.factory"), exports);
26
+ __exportStar(require("./rng-service"), exports);
22
27
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/rng/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA;;GAEG;AACH,oDAAkC;AAClC,mDAAiC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/rng/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA;;GAEG;AACH,oDAAkC;AAClC,mDAAiC;AAEjC,wCAAwC;AACxC,yDAAuC;AACvC,qDAAmC;AACnC,uDAAqC;AACrC,gDAA8B"}
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ /**
3
+ * RNG Client Factory
4
+ *
5
+ * Factory pattern for creating RNG clients based on environment configuration.
6
+ * Matches the pattern used in Heroes of Aether and Bonny's Fortune.
7
+ *
8
+ * Environment Variables:
9
+ * - RNG_CLIENT_MODE: 'local' (DummyRngClient) or 'external' (production RngClient)
10
+ * - RNG_CLIENT_URL: HTTP endpoint for external RNG service
11
+ * - RNG_CLIENT_GRPC_URL: gRPC endpoint for external RNG service
12
+ * - RNG_CLIENT_METHOD: 'grpc' or 'rest'
13
+ * - RNG_CLIENT_POOL_SIZE: Ring buffer pool size (default: 2000)
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.RngClientFactory = void 0;
17
+ const dummy_rng_client_1 = require("./dummy-rng-client");
18
+ class RngClientFactory {
19
+ /**
20
+ * Create an RNG client based on environment configuration.
21
+ *
22
+ * @param config Optional configuration overrides
23
+ * @returns IRngClient instance (DummyRngClient for local, external client for production)
24
+ */
25
+ static create(config = {}) {
26
+ const mode = process.env.RNG_CLIENT_MODE || 'external';
27
+ if (mode === 'local') {
28
+ console.log('[RngClientFactory] Creating DummyRngClient (local mode)');
29
+ return new dummy_rng_client_1.DummyRngClient();
30
+ }
31
+ // For external mode, we would use @omnitronix/rng-client-core
32
+ // For now, fall back to DummyRngClient if external client not available
33
+ console.log('[RngClientFactory] External RNG mode requested');
34
+ const rngClientConfig = {
35
+ serverUrl: process.env.RNG_CLIENT_URL,
36
+ grpcUrl: process.env.RNG_CLIENT_GRPC_URL,
37
+ clientMethod: process.env.RNG_CLIENT_METHOD,
38
+ poolingSize: parseInt(process.env.RNG_CLIENT_POOL_SIZE || '2000'),
39
+ metrics: config.metrics,
40
+ };
41
+ // Try to load external RNG client
42
+ try {
43
+ // Dynamic import to avoid hard dependency
44
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
45
+ const { RngClient } = require('@omnitronix/rng-client-core');
46
+ console.log('[RngClientFactory] Creating external RngClient');
47
+ return new RngClient(rngClientConfig);
48
+ }
49
+ catch (error) {
50
+ console.warn('[RngClientFactory] @omnitronix/rng-client-core not available, falling back to DummyRngClient');
51
+ console.warn('[RngClientFactory] Install @omnitronix/rng-client-core for production use');
52
+ return new dummy_rng_client_1.DummyRngClient();
53
+ }
54
+ }
55
+ }
56
+ exports.RngClientFactory = RngClientFactory;
57
+ //# sourceMappingURL=rng-client.factory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rng-client.factory.js","sourceRoot":"","sources":["../../src/rng/rng-client.factory.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;GAYG;;;AAGH,yDAAoD;AAUpD,MAAa,gBAAgB;IAC3B;;;;;OAKG;IACH,MAAM,CAAC,MAAM,CAAC,SAAgC,EAAE;QAC9C,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,UAAU,CAAC;QAEvD,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;YACrB,OAAO,CAAC,GAAG,CAAC,yDAAyD,CAAC,CAAC;YACvE,OAAO,IAAI,iCAAc,EAAE,CAAC;QAC9B,CAAC;QAED,8DAA8D;QAC9D,wEAAwE;QACxE,OAAO,CAAC,GAAG,CAAC,gDAAgD,CAAC,CAAC;QAE9D,MAAM,eAAe,GAAoB;YACvC,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,cAAc;YACrC,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,mBAAmB;YACxC,YAAY,EAAE,OAAO,CAAC,GAAG,CAAC,iBAAoC;YAC9D,WAAW,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,MAAM,CAAC;YACjE,OAAO,EAAE,MAAM,CAAC,OAAO;SACxB,CAAC;QAEF,kCAAkC;QAClC,IAAI,CAAC;YACH,0CAA0C;YAC1C,8DAA8D;YAC9D,MAAM,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC,6BAA6B,CAAC,CAAC;YAC7D,OAAO,CAAC,GAAG,CAAC,gDAAgD,CAAC,CAAC;YAC9D,OAAO,IAAI,SAAS,CAAC,eAAe,CAAC,CAAC;QACxC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,IAAI,CACV,8FAA8F,CAC/F,CAAC;YACF,OAAO,CAAC,IAAI,CACV,2EAA2E,CAC5E,CAAC;YACF,OAAO,IAAI,iCAAc,EAAE,CAAC;QAC9B,CAAC;IACH,CAAC;CACF;AA5CD,4CA4CC"}
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ /**
3
+ * RNG Client Interface
4
+ *
5
+ * Contract for all RNG clients (local and external).
6
+ * Matches the pattern used in Heroes of Aether and Bonny's Fortune.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ //# sourceMappingURL=rng-client.interface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rng-client.interface.js","sourceRoot":"","sources":["../../src/rng/rng-client.interface.ts"],"names":[],"mappings":";AAAA;;;;;GAKG"}
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+ /**
3
+ * RNG Service
4
+ *
5
+ * Core RNG service with audit trail tracking for compliance.
6
+ * Wraps an IRngClient and tracks all RNG outcomes per command.
7
+ * Matches the pattern used in Heroes of Aether and Bonny's Fortune.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.RngService = void 0;
11
+ /**
12
+ * RNG Service
13
+ *
14
+ * Provides RNG operations with full audit trail tracking.
15
+ * Each command ID can have multiple action IDs, allowing fine-grained tracking
16
+ * of all RNG calls within a single game command.
17
+ */
18
+ class RngService {
19
+ constructor(rngClient) {
20
+ this.rngClient = rngClient;
21
+ this.rngOutcomes = {};
22
+ }
23
+ /**
24
+ * Register a command for RNG tracking.
25
+ * Call this at the start of command processing.
26
+ */
27
+ registerCommand(commandId) {
28
+ if (!this.rngOutcomes[commandId]) {
29
+ this.rngOutcomes[commandId] = {};
30
+ }
31
+ }
32
+ /**
33
+ * Remove a command's RNG outcomes.
34
+ * Call this after command processing to clean up.
35
+ */
36
+ removeCommand(commandId) {
37
+ delete this.rngOutcomes[commandId];
38
+ }
39
+ /**
40
+ * Check if a command has RNG outcomes
41
+ */
42
+ hasCommandRngOutcome(commandId) {
43
+ return !!this.rngOutcomes[commandId];
44
+ }
45
+ /**
46
+ * Get the count of RNG outcomes for a command
47
+ */
48
+ getRngOutcomeCountForCommand(commandId) {
49
+ const outcomes = this.rngOutcomes[commandId];
50
+ return outcomes ? Object.keys(outcomes).length : 0;
51
+ }
52
+ /**
53
+ * Store an RNG outcome for audit trail
54
+ */
55
+ storeSeedForCommand(commandId, actionId, result, seed, min, max) {
56
+ this.registerCommand(commandId);
57
+ this.rngOutcomes[commandId][actionId] = { result, seed, min, max };
58
+ }
59
+ /**
60
+ * Get cached seed for a command/action (for replay scenarios)
61
+ */
62
+ getSeedForCommand(commandId, actionId) {
63
+ return this.rngOutcomes[commandId]?.[actionId]?.seed;
64
+ }
65
+ /**
66
+ * Get all RNG outcomes for a command
67
+ */
68
+ getRngOutcomeForCommand(commandId) {
69
+ return this.rngOutcomes[commandId] || {};
70
+ }
71
+ /**
72
+ * Get all RNG outcomes across all commands
73
+ */
74
+ getAllRngOutcome() {
75
+ return { ...this.rngOutcomes };
76
+ }
77
+ /**
78
+ * Generate a single random integer in range [min, max]
79
+ * Tracks the outcome for audit trail
80
+ */
81
+ async getSingleNumber(min, max, commandId, actionId) {
82
+ // Check if we already have a cached result (for replay)
83
+ const existingSeed = this.getSeedForCommand(commandId, actionId);
84
+ if (existingSeed !== undefined) {
85
+ const existingResult = this.rngOutcomes[commandId][actionId];
86
+ return { value: existingResult.result, seed: existingResult.seed };
87
+ }
88
+ // Generate new random number
89
+ const response = await this.rngClient.getSingleNumber(min, max);
90
+ // Store for audit trail
91
+ this.storeSeedForCommand(commandId, actionId, response.value, response.seed, min, max);
92
+ return response;
93
+ }
94
+ /**
95
+ * Generate multiple random integers in range [min, max]
96
+ * Each number is tracked individually with incrementing action IDs
97
+ */
98
+ async getBatchNumbers(min, max, count, commandId, actionId) {
99
+ const results = [];
100
+ for (let i = 0; i < count; i++) {
101
+ const result = await this.getSingleNumber(min, max, commandId, `${actionId}_${i}`);
102
+ results.push(result);
103
+ }
104
+ return results;
105
+ }
106
+ /**
107
+ * Generate a weighted random selection
108
+ * Returns the index selected based on weights
109
+ */
110
+ async getWeightedRandomIndex(weights, commandId, actionId) {
111
+ const total = weights.reduce((acc, w) => acc + w, 0);
112
+ const { value: rand } = await this.getSingleNumber(0, total - 1, commandId, actionId);
113
+ let cumulative = 0;
114
+ for (let i = 0; i < weights.length; i++) {
115
+ cumulative += weights[i];
116
+ if (rand < cumulative) {
117
+ return i;
118
+ }
119
+ }
120
+ return weights.length - 1;
121
+ }
122
+ }
123
+ exports.RngService = RngService;
124
+ //# sourceMappingURL=rng-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rng-service.js","sourceRoot":"","sources":["../../src/rng/rng-service.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;;AAmBH;;;;;;GAMG;AACH,MAAa,UAAU;IAGrB,YAA6B,SAAqB;QAArB,cAAS,GAAT,SAAS,CAAY;QAF1C,gBAAW,GAAwC,EAAE,CAAC;IAET,CAAC;IAEtD;;;OAGG;IACH,eAAe,CAAC,SAAiB;QAC/B,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,EAAE,CAAC;YACjC,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC;QACnC,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,aAAa,CAAC,SAAiB;QAC7B,OAAO,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;IACrC,CAAC;IAED;;OAEG;IACH,oBAAoB,CAAC,SAAiB;QACpC,OAAO,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;IACvC,CAAC;IAED;;OAEG;IACH,4BAA4B,CAAC,SAAiB;QAC5C,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;QAC7C,OAAO,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IACrD,CAAC;IAED;;OAEG;IACK,mBAAmB,CACzB,SAAiB,EACjB,QAAgB,EAChB,MAAc,EACd,IAAY,EACZ,GAAW,EACX,GAAW;QAEX,IAAI,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC;QAChC,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IACrE,CAAC;IAED;;OAEG;IACH,iBAAiB,CAAC,SAAiB,EAAE,QAAgB;QACnD,OAAO,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC;IACvD,CAAC;IAED;;OAEG;IACH,uBAAuB,CAAC,SAAiB;QACvC,OAAO,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;IAC3C,CAAC;IAED;;OAEG;IACH,gBAAgB;QACd,OAAO,EAAE,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IACjC,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,eAAe,CACnB,GAAW,EACX,GAAW,EACX,SAAiB,EACjB,QAAgB;QAEhB,wDAAwD;QACxD,MAAM,YAAY,GAAG,IAAI,CAAC,iBAAiB,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QACjE,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;YAC/B,MAAM,cAAc,GAAG,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,CAAC;YAC7D,OAAO,EAAE,KAAK,EAAE,cAAc,CAAC,MAAM,EAAE,IAAI,EAAE,cAAc,CAAC,IAAI,EAAE,CAAC;QACrE,CAAC;QAED,6BAA6B;QAC7B,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAEhE,wBAAwB;QACxB,IAAI,CAAC,mBAAmB,CACtB,SAAS,EACT,QAAQ,EACR,QAAQ,CAAC,KAAK,EACd,QAAQ,CAAC,IAAI,EACb,GAAG,EACH,GAAG,CACJ,CAAC;QAEF,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,eAAe,CACnB,GAAW,EACX,GAAW,EACX,KAAa,EACb,SAAiB,EACjB,QAAgB;QAEhB,MAAM,OAAO,GAAsC,EAAE,CAAC;QAEtD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;YAC/B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,eAAe,CACvC,GAAG,EACH,GAAG,EACH,SAAS,EACT,GAAG,QAAQ,IAAI,CAAC,EAAE,CACnB,CAAC;YACF,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACvB,CAAC;QAED,OAAO,OAAO,CAAC;IACjB,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,sBAAsB,CAC1B,OAAiB,EACjB,SAAiB,EACjB,QAAgB;QAEhB,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QACrD,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,eAAe,CAChD,CAAC,EACD,KAAK,GAAG,CAAC,EACT,SAAS,EACT,QAAQ,CACT,CAAC;QAEF,IAAI,UAAU,GAAG,CAAC,CAAC;QACnB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACxC,UAAU,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC;YACzB,IAAI,IAAI,GAAG,UAAU,EAAE,CAAC;gBACtB,OAAO,CAAC,CAAC;YACX,CAAC;QACH,CAAC;QAED,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;IAC5B,CAAC;CACF;AAhKD,gCAgKC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omnitronix/happy-panda-game-engine",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "Happy Panda Game Engine",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",