@gamerstake/game-core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/.eslintrc.json +22 -0
  2. package/.testing-guide-summary.md +261 -0
  3. package/DEVELOPER_GUIDE.md +996 -0
  4. package/MANUAL_TESTING.md +369 -0
  5. package/QUICK_START.md +368 -0
  6. package/README.md +379 -0
  7. package/TESTING_OVERVIEW.md +378 -0
  8. package/dist/index.d.ts +1266 -0
  9. package/dist/index.js +1632 -0
  10. package/dist/index.js.map +1 -0
  11. package/examples/simple-game/README.md +176 -0
  12. package/examples/simple-game/client.ts +201 -0
  13. package/examples/simple-game/package.json +14 -0
  14. package/examples/simple-game/server.ts +233 -0
  15. package/jest.config.ts +39 -0
  16. package/package.json +54 -0
  17. package/src/core/GameLoop.ts +214 -0
  18. package/src/core/GameRules.ts +103 -0
  19. package/src/core/GameServer.ts +200 -0
  20. package/src/core/Room.ts +368 -0
  21. package/src/entities/Entity.ts +118 -0
  22. package/src/entities/Registry.ts +161 -0
  23. package/src/index.ts +51 -0
  24. package/src/input/Command.ts +41 -0
  25. package/src/input/InputQueue.ts +130 -0
  26. package/src/network/Network.ts +112 -0
  27. package/src/network/Snapshot.ts +59 -0
  28. package/src/physics/AABB.ts +104 -0
  29. package/src/physics/Movement.ts +124 -0
  30. package/src/spatial/Grid.ts +202 -0
  31. package/src/types/index.ts +117 -0
  32. package/src/types/protocol.ts +161 -0
  33. package/src/utils/Logger.ts +112 -0
  34. package/src/utils/RingBuffer.ts +116 -0
  35. package/tests/AABB.test.ts +38 -0
  36. package/tests/Entity.test.ts +35 -0
  37. package/tests/GameLoop.test.ts +58 -0
  38. package/tests/GameServer.test.ts +64 -0
  39. package/tests/Grid.test.ts +28 -0
  40. package/tests/InputQueue.test.ts +42 -0
  41. package/tests/Movement.test.ts +37 -0
  42. package/tests/Network.test.ts +39 -0
  43. package/tests/Registry.test.ts +36 -0
  44. package/tests/RingBuffer.test.ts +38 -0
  45. package/tests/Room.test.ts +80 -0
  46. package/tests/Snapshot.test.ts +19 -0
  47. package/tsconfig.json +28 -0
  48. package/tsup.config.ts +14 -0
@@ -0,0 +1,1266 @@
1
+ import { Server, Socket } from 'socket.io';
2
+ import pino from 'pino';
3
+
4
+ /**
5
+ * Core type definitions for game-core package.
6
+ */
7
+ /**
8
+ * Room configuration options.
9
+ */
10
+ interface RoomConfig {
11
+ /** Ticks per second (default: 20) */
12
+ tickRate?: number;
13
+ /** Grid cell size in world units (default: 512) */
14
+ cellSize?: number;
15
+ /** Maximum buffered inputs per player (default: 100) */
16
+ maxInputQueueSize?: number;
17
+ /** Maximum entities per room (default: 1000) */
18
+ maxEntities?: number;
19
+ /** Visibility range in grid cells (default: 1 = 3x3 area) */
20
+ visibilityRange?: number;
21
+ }
22
+ /**
23
+ * Server metrics for monitoring.
24
+ */
25
+ interface ServerMetrics {
26
+ /** Total number of active rooms */
27
+ roomCount: number;
28
+ /** Total players across all rooms */
29
+ totalPlayers: number;
30
+ /** Per-room metrics */
31
+ rooms: RoomMetrics[];
32
+ }
33
+ /**
34
+ * Per-room metrics.
35
+ */
36
+ interface RoomMetrics {
37
+ /** Room ID */
38
+ id: string;
39
+ /** Number of players in room */
40
+ playerCount: number;
41
+ /** Total entities in room */
42
+ entityCount: number;
43
+ /** Current tick number */
44
+ tickCount: number;
45
+ /** Is the room running */
46
+ isRunning: boolean;
47
+ /** Average tick time (ms) */
48
+ avgTickTime?: number;
49
+ /** Actual ticks per second */
50
+ ticksPerSecond?: number;
51
+ }
52
+ /**
53
+ * Game loop performance metrics.
54
+ */
55
+ interface LoopMetrics {
56
+ /** Average tick processing time (ms) */
57
+ avgTickTime: number;
58
+ /** Maximum tick time (ms) */
59
+ maxTickTime: number;
60
+ /** Minimum tick time (ms) */
61
+ minTickTime: number;
62
+ /** Actual ticks per second */
63
+ ticksPerSecond: number;
64
+ }
65
+ /**
66
+ * Network event base type.
67
+ */
68
+ interface NetworkEvent {
69
+ /** Opcode identifying event type */
70
+ op: string;
71
+ /** Additional event data */
72
+ [key: string]: unknown;
73
+ }
74
+ /**
75
+ * Snapshot of entity state.
76
+ */
77
+ interface EntitySnapshot {
78
+ id: string;
79
+ x: number;
80
+ y: number;
81
+ vx: number;
82
+ vy: number;
83
+ [key: string]: unknown;
84
+ }
85
+ /**
86
+ * Full room state snapshot.
87
+ */
88
+ interface StateSnapshot {
89
+ /** Current tick number */
90
+ tick: number;
91
+ /** All entities in room */
92
+ entities: EntitySnapshot[];
93
+ /** Timestamp */
94
+ timestamp: number;
95
+ }
96
+
97
+ /**
98
+ * Game Loop
99
+ *
100
+ * Fixed tick rate server loop with drift compensation.
101
+ * Processes inputs, updates state, broadcasts changes.
102
+ */
103
+
104
+ /**
105
+ * Tick handler interface.
106
+ * Implement this to receive tick callbacks.
107
+ */
108
+ interface TickHandler {
109
+ onTick(tickNumber: number, deltaMs: number): void;
110
+ }
111
+ /**
112
+ * High-precision game loop using setTimeout with drift compensation.
113
+ */
114
+ declare class GameLoop {
115
+ private readonly tickRate;
116
+ private readonly tickMs;
117
+ private tickNumber;
118
+ private lastTickTime;
119
+ private running;
120
+ private timeout;
121
+ private readonly handlers;
122
+ private tickTimes;
123
+ private readonly metricsWindowSize;
124
+ /**
125
+ * Create a new game loop.
126
+ *
127
+ * @param tickRate - Ticks per second (default: 20)
128
+ */
129
+ constructor(tickRate?: number);
130
+ /**
131
+ * Register a tick handler.
132
+ */
133
+ addHandler(handler: TickHandler): void;
134
+ /**
135
+ * Remove a tick handler.
136
+ */
137
+ removeHandler(handler: TickHandler): void;
138
+ /**
139
+ * Start the game loop.
140
+ */
141
+ start(): void;
142
+ /**
143
+ * Stop the game loop.
144
+ */
145
+ stop(): void;
146
+ /**
147
+ * Check if loop is running.
148
+ */
149
+ isRunning(): boolean;
150
+ /**
151
+ * Get current tick number.
152
+ */
153
+ getCurrentTick(): number;
154
+ /**
155
+ * Get tick rate (TPS).
156
+ */
157
+ getTickRate(): number;
158
+ /**
159
+ * Get tick duration (ms).
160
+ */
161
+ getTickMs(): number;
162
+ /**
163
+ * Get performance metrics.
164
+ */
165
+ getMetrics(): LoopMetrics;
166
+ /**
167
+ * Schedule the next tick with drift compensation.
168
+ */
169
+ private scheduleNextTick;
170
+ /**
171
+ * Execute one tick.
172
+ */
173
+ private tick;
174
+ /**
175
+ * Record tick time for metrics.
176
+ */
177
+ private recordTickTime;
178
+ }
179
+
180
+ /**
181
+ * Entity System
182
+ *
183
+ * Base entity class for all game objects.
184
+ */
185
+ /**
186
+ * Base entity class.
187
+ * All game objects (players, NPCs, items) extend this.
188
+ *
189
+ * Includes position, velocity, and dirty flag for
190
+ * efficient network synchronization.
191
+ */
192
+ declare class Entity {
193
+ /** Unique entity identifier */
194
+ readonly id: string;
195
+ /** World X position */
196
+ x: number;
197
+ /** World Y position */
198
+ y: number;
199
+ /** Velocity X (units/second) */
200
+ vx: number;
201
+ /** Velocity Y (units/second) */
202
+ vy: number;
203
+ /** Dirty flag - entity needs to be broadcast */
204
+ dirty: boolean;
205
+ /** Last update timestamp */
206
+ lastUpdate: number;
207
+ /**
208
+ * Create a new entity.
209
+ *
210
+ * @param id - Unique identifier
211
+ * @param x - Initial X position
212
+ * @param y - Initial Y position
213
+ */
214
+ constructor(id: string, x: number, y: number);
215
+ /**
216
+ * Update position based on velocity and delta time.
217
+ *
218
+ * @param deltaMs - Time since last update (milliseconds)
219
+ */
220
+ updatePosition(deltaMs: number): void;
221
+ /**
222
+ * Set velocity.
223
+ */
224
+ setVelocity(vx: number, vy: number): void;
225
+ /**
226
+ * Teleport to position (no velocity integration).
227
+ */
228
+ setPosition(x: number, y: number): void;
229
+ /**
230
+ * Mark entity as clean (after broadcast).
231
+ */
232
+ markClean(): void;
233
+ /**
234
+ * Mark entity as dirty (needs broadcast).
235
+ */
236
+ markDirty(): void;
237
+ /**
238
+ * Get entity as plain object for serialization.
239
+ */
240
+ toJSON(): Record<string, unknown>;
241
+ }
242
+
243
+ /**
244
+ * Command types for player input.
245
+ */
246
+ /**
247
+ * Base command interface.
248
+ */
249
+ interface Command {
250
+ /** Client sequence number for reconciliation */
251
+ readonly seq: number;
252
+ /** Command type */
253
+ readonly type: string;
254
+ /** Timestamp when command was created */
255
+ readonly timestamp: number;
256
+ }
257
+ /**
258
+ * Movement command.
259
+ */
260
+ interface MoveCommand extends Command {
261
+ type: 'move';
262
+ dir: {
263
+ x: number;
264
+ y: number;
265
+ };
266
+ }
267
+ /**
268
+ * Stop command.
269
+ */
270
+ interface StopCommand extends Command {
271
+ type: 'stop';
272
+ }
273
+ /**
274
+ * Generic action command.
275
+ */
276
+ interface ActionCommand extends Command {
277
+ type: 'action';
278
+ action: string;
279
+ data?: Record<string, unknown>;
280
+ }
281
+
282
+ /**
283
+ * Game Rules Interface
284
+ *
285
+ * Pluggable game logic interface.
286
+ * Implement this to create custom multiplayer games.
287
+ */
288
+
289
+ /**
290
+ * Game-specific logic interface.
291
+ *
292
+ * This is the core abstraction that makes game-core reusable.
293
+ * All game-specific logic is implemented through this interface,
294
+ * keeping the engine generic.
295
+ *
296
+ * @example
297
+ * ```typescript
298
+ * class MyGameRules implements GameRules {
299
+ * onRoomCreated(room: Room): void {
300
+ * // Spawn initial entities
301
+ * }
302
+ *
303
+ * onPlayerJoin(room: Room, player: Entity): void {
304
+ * // Setup player
305
+ * }
306
+ *
307
+ * onTick(room: Room, delta: number): void {
308
+ * // Update game state
309
+ * }
310
+ *
311
+ * onCommand(room: Room, playerId: string, command: Command): void {
312
+ * // Handle player input
313
+ * }
314
+ *
315
+ * onPlayerLeave(room: Room, playerId: string): void {
316
+ * // Cleanup player
317
+ * }
318
+ *
319
+ * shouldEndRoom(room: Room): boolean {
320
+ * // For match-based games, return true when match ends
321
+ * // For persistent worlds, return false
322
+ * return false;
323
+ * }
324
+ * }
325
+ * ```
326
+ */
327
+ interface GameRules<TEntity extends Entity = Entity> {
328
+ /**
329
+ * Called once when room is created.
330
+ * Use for initialization (spawn items, set boundaries, etc).
331
+ *
332
+ * @param room - The newly created room
333
+ */
334
+ onRoomCreated(room: Room<TEntity>): void;
335
+ /**
336
+ * Called when a player joins the room.
337
+ *
338
+ * @param room - The room
339
+ * @param player - The entity representing the player
340
+ */
341
+ onPlayerJoin(room: Room<TEntity>, player: TEntity): void;
342
+ /**
343
+ * Called when a player leaves the room.
344
+ *
345
+ * @param room - The room
346
+ * @param playerId - The ID of the leaving player
347
+ */
348
+ onPlayerLeave(room: Room<TEntity>, playerId: string): void;
349
+ /**
350
+ * Called every tick (20 TPS by default).
351
+ * Process inputs, update game state, check win conditions.
352
+ *
353
+ * @param room - The room
354
+ * @param delta - Time since last tick (milliseconds)
355
+ */
356
+ onTick(room: Room<TEntity>, delta: number): void;
357
+ /**
358
+ * Called when a player sends a command.
359
+ *
360
+ * @param room - The room
361
+ * @param playerId - The player who sent the command
362
+ * @param command - The input command from the player
363
+ */
364
+ onCommand(room: Room<TEntity>, playerId: string, command: Command): void;
365
+ /**
366
+ * Check if the room should be destroyed.
367
+ *
368
+ * For match-based games, return true when match ends.
369
+ * For persistent worlds, return false.
370
+ *
371
+ * @param room - The room
372
+ * @returns True if room should end
373
+ */
374
+ shouldEndRoom(room: Room<TEntity>): boolean;
375
+ }
376
+
377
+ /**
378
+ * Entity Registry
379
+ *
380
+ * Manages entity lifecycle and provides efficient queries.
381
+ */
382
+
383
+ /**
384
+ * Entity registry with lifecycle management.
385
+ *
386
+ * Provides zero-allocation queries using pre-allocated buffers
387
+ * to minimize GC pressure in hot paths.
388
+ */
389
+ declare class Registry<TEntity extends Entity = Entity> {
390
+ private readonly entities;
391
+ private readonly dirtyEntities;
392
+ private readonly dirtyBuffer;
393
+ private readonly allBuffer;
394
+ /**
395
+ * Add an entity to the registry.
396
+ */
397
+ add(entity: TEntity): void;
398
+ /**
399
+ * Remove an entity from the registry.
400
+ */
401
+ remove(entityId: string): boolean;
402
+ /**
403
+ * Get an entity by ID.
404
+ */
405
+ get(entityId: string): TEntity | undefined;
406
+ /**
407
+ * Check if entity exists.
408
+ */
409
+ has(entityId: string): boolean;
410
+ /**
411
+ * Mark an entity as dirty (needs broadcast).
412
+ */
413
+ markDirty(entityId: string): void;
414
+ /**
415
+ * Get all dirty entities and clear dirty flags.
416
+ * OPTIMIZED: Reuses buffer array to avoid allocations.
417
+ *
418
+ * @returns Array of dirty entities (reused buffer - don't store reference!)
419
+ */
420
+ getDirtyEntities(): TEntity[];
421
+ /**
422
+ * Get all entities.
423
+ * OPTIMIZED: Reuses buffer array to avoid allocations.
424
+ *
425
+ * @returns Array of all entities (reused buffer - don't store reference!)
426
+ */
427
+ getAll(): TEntity[];
428
+ /**
429
+ * Get entity count.
430
+ */
431
+ size(): number;
432
+ /**
433
+ * Get dirty entity count.
434
+ */
435
+ dirtyCount(): number;
436
+ /**
437
+ * Clear all entities.
438
+ */
439
+ clear(): void;
440
+ /**
441
+ * Execute callback for each entity.
442
+ */
443
+ forEach(callback: (entity: TEntity) => void): void;
444
+ /**
445
+ * Filter entities by predicate.
446
+ *
447
+ * @param predicate - Filter function
448
+ * @returns New array of matching entities
449
+ */
450
+ filter(predicate: (entity: TEntity) => boolean): TEntity[];
451
+ }
452
+
453
+ /**
454
+ * Grid System
455
+ *
456
+ * Spatial partitioning using a fixed-size grid.
457
+ * Enables O(1) lookup for nearby entities.
458
+ */
459
+ /**
460
+ * Grid-based spatial partitioning system.
461
+ *
462
+ * Uses a hash map of cells for O(1) entity tracking.
463
+ * Automatically cleans up empty cells to prevent memory leaks.
464
+ */
465
+ declare class Grid {
466
+ private readonly cellSize;
467
+ private readonly cells;
468
+ private readonly entityCells;
469
+ /**
470
+ * Create a new spatial grid.
471
+ *
472
+ * @param cellSize - Size of each cell in world units (default: 512)
473
+ */
474
+ constructor(cellSize?: number);
475
+ /**
476
+ * Convert world coordinates to cell key.
477
+ */
478
+ getCellKey(worldX: number, worldY: number): string;
479
+ /**
480
+ * Get or create a cell at world coordinates.
481
+ */
482
+ private getCell;
483
+ /**
484
+ * Add an entity to the grid.
485
+ */
486
+ addEntity(entityId: string, x: number, y: number): void;
487
+ /**
488
+ * Remove an entity from the grid.
489
+ */
490
+ removeEntity(entityId: string): void;
491
+ /**
492
+ * Move an entity to a new position.
493
+ * Returns true if the entity changed cells.
494
+ */
495
+ moveEntity(entityId: string, oldX: number, oldY: number, newX: number, newY: number): boolean;
496
+ /**
497
+ * Get all entities in nearby cells.
498
+ *
499
+ * @param worldX - World X coordinate
500
+ * @param worldY - World Y coordinate
501
+ * @param range - Range in grid cells (default: 1 = 3x3 area)
502
+ */
503
+ getNearbyEntities(worldX: number, worldY: number, range?: number): string[];
504
+ /**
505
+ * Get entities in a specific cell.
506
+ */
507
+ getEntitiesInCell(worldX: number, worldY: number): string[];
508
+ /**
509
+ * Get the cell an entity is in.
510
+ */
511
+ getEntityCell(entityId: string): string | undefined;
512
+ /**
513
+ * Get total number of active cells.
514
+ */
515
+ getCellCount(): number;
516
+ /**
517
+ * Get total entities in grid.
518
+ */
519
+ getEntityCount(): number;
520
+ /**
521
+ * Clear all cells and entities.
522
+ */
523
+ clear(): void;
524
+ }
525
+
526
+ /**
527
+ * Input Queue
528
+ *
529
+ * Buffers player inputs for processing in the game loop.
530
+ * Uses RingBuffer for O(1) push/pop operations.
531
+ */
532
+
533
+ /**
534
+ * Input queue manager using RingBuffer for O(1) operations.
535
+ *
536
+ * Manages input queues for multiple players, with automatic
537
+ * overflow handling (drops oldest inputs when full).
538
+ */
539
+ declare class InputQueue {
540
+ private readonly queues;
541
+ private readonly maxQueueSize;
542
+ /**
543
+ * Create a new input queue manager.
544
+ *
545
+ * @param maxQueueSize - Maximum inputs per player (default: 100)
546
+ */
547
+ constructor(maxQueueSize?: number);
548
+ /**
549
+ * Get or create a player's queue.
550
+ */
551
+ private getQueue;
552
+ /**
553
+ * Add an input to a player's queue. O(1)
554
+ */
555
+ push(playerId: string, input: Command): void;
556
+ /**
557
+ * Get and remove the next input for a player. O(1)
558
+ */
559
+ pop(playerId: string): Command | undefined;
560
+ /**
561
+ * Peek at the next input without removing. O(1)
562
+ */
563
+ peek(playerId: string): Command | undefined;
564
+ /**
565
+ * Get all inputs for a player and clear.
566
+ */
567
+ drain(playerId: string): Command[];
568
+ /**
569
+ * Get queue size for a player.
570
+ */
571
+ size(playerId: string): number;
572
+ /**
573
+ * Clear a player's queue.
574
+ */
575
+ clear(playerId: string): void;
576
+ /**
577
+ * Remove a player's queue entirely.
578
+ */
579
+ remove(playerId: string): void;
580
+ /**
581
+ * Clear all queues.
582
+ */
583
+ clearAll(): void;
584
+ /**
585
+ * Get total queued inputs across all players.
586
+ */
587
+ getTotalSize(): number;
588
+ }
589
+
590
+ /**
591
+ * Network Layer
592
+ *
593
+ * Broadcast and send utilities for game events.
594
+ */
595
+
596
+ /**
597
+ * Network layer for broadcasting game events.
598
+ *
599
+ * Wraps Socket.io with game-specific utilities.
600
+ */
601
+ declare class Network {
602
+ private io;
603
+ private readonly sockets;
604
+ /**
605
+ * Set the Socket.io server instance.
606
+ */
607
+ setServer(io: Server): void;
608
+ /**
609
+ * Register a socket connection.
610
+ */
611
+ registerSocket(playerId: string, socket: Socket): void;
612
+ /**
613
+ * Unregister a socket connection.
614
+ */
615
+ unregisterSocket(playerId: string): void;
616
+ /**
617
+ * Get a socket by player ID.
618
+ */
619
+ getSocket(playerId: string): Socket | undefined;
620
+ /**
621
+ * Broadcast event to all connected players.
622
+ */
623
+ broadcast(event: NetworkEvent): void;
624
+ /**
625
+ * Broadcast event to all players in a room.
626
+ */
627
+ broadcastToRoom(roomId: string, event: NetworkEvent): void;
628
+ /**
629
+ * Send event to a specific player.
630
+ */
631
+ sendTo(playerId: string, event: NetworkEvent): void;
632
+ /**
633
+ * Send event to multiple players.
634
+ */
635
+ sendToMany(playerIds: string[], event: NetworkEvent): void;
636
+ /**
637
+ * Get connected player count.
638
+ */
639
+ getPlayerCount(): number;
640
+ /**
641
+ * Clear all sockets.
642
+ */
643
+ clear(): void;
644
+ }
645
+
646
+ /**
647
+ * Room
648
+ *
649
+ * A single game room/match instance.
650
+ * Manages entities, tick loop, and networking.
651
+ */
652
+
653
+ /**
654
+ * A single game room/match.
655
+ *
656
+ * Orchestrates all game systems: tick loop, entities, spatial partitioning,
657
+ * input processing, and network synchronization.
658
+ *
659
+ * @example
660
+ * ```typescript
661
+ * const room = new Room('room-1', new MyGameRules(), {
662
+ * tickRate: 20,
663
+ * cellSize: 512,
664
+ * });
665
+ *
666
+ * room.start();
667
+ *
668
+ * // Add players
669
+ * const player = new Entity('player-1', 100, 100);
670
+ * room.addPlayer(player);
671
+ * ```
672
+ */
673
+ declare class Room<TEntity extends Entity = Entity> implements TickHandler {
674
+ /** Room identifier */
675
+ readonly id: string;
676
+ /** Game-specific logic */
677
+ readonly rules: GameRules<TEntity>;
678
+ private readonly registry;
679
+ private readonly grid;
680
+ private readonly loop;
681
+ private readonly inputQueue;
682
+ private readonly network;
683
+ private readonly players;
684
+ private tickCount;
685
+ private startTime;
686
+ private readonly config;
687
+ /**
688
+ * Create a new room.
689
+ *
690
+ * @param id - Unique room identifier
691
+ * @param rules - Game-specific logic
692
+ * @param config - Room configuration
693
+ */
694
+ constructor(id: string, rules: GameRules<TEntity>, config?: RoomConfig);
695
+ /**
696
+ * Start the room (begins tick loop).
697
+ */
698
+ start(): void;
699
+ /**
700
+ * Stop the room (ends tick loop).
701
+ */
702
+ stop(): void;
703
+ /**
704
+ * Add a player to the room.
705
+ */
706
+ addPlayer(player: TEntity): void;
707
+ /**
708
+ * Remove a player from the room.
709
+ */
710
+ removePlayer(playerId: string): void;
711
+ /**
712
+ * Spawn a non-player entity.
713
+ */
714
+ spawnEntity(entity: TEntity): void;
715
+ /**
716
+ * Destroy an entity.
717
+ */
718
+ destroyEntity(entityId: string): void;
719
+ /**
720
+ * Queue a player input.
721
+ */
722
+ queueInput(playerId: string, command: Command): void;
723
+ /**
724
+ * Broadcast event to all players.
725
+ */
726
+ broadcast(event: NetworkEvent): void;
727
+ /**
728
+ * Send event to specific player.
729
+ */
730
+ sendTo(playerId: string, event: NetworkEvent): void;
731
+ /**
732
+ * Get full state snapshot.
733
+ */
734
+ getSnapshot(): StateSnapshot;
735
+ /**
736
+ * Get delta snapshot (only dirty entities).
737
+ */
738
+ getDeltaSnapshot(): StateSnapshot;
739
+ /**
740
+ * Main tick function (called by GameLoop).
741
+ */
742
+ onTick(tickNumber: number, deltaMs: number): void;
743
+ /**
744
+ * Get all players.
745
+ */
746
+ getPlayers(): Map<string, TEntity>;
747
+ /**
748
+ * Get entity registry.
749
+ */
750
+ getRegistry(): Registry<TEntity>;
751
+ /**
752
+ * Get spatial grid.
753
+ */
754
+ getGrid(): Grid;
755
+ /**
756
+ * Get network layer.
757
+ */
758
+ getNetwork(): Network;
759
+ /**
760
+ * Get input queue.
761
+ */
762
+ getInputQueue(): InputQueue;
763
+ /**
764
+ * Get current tick number.
765
+ */
766
+ getTickCount(): number;
767
+ /**
768
+ * Get uptime in milliseconds.
769
+ */
770
+ getUptime(): number;
771
+ /**
772
+ * Check if room is running.
773
+ */
774
+ isRunning(): boolean;
775
+ /**
776
+ * Get room configuration.
777
+ */
778
+ getConfig(): Required<RoomConfig>;
779
+ /**
780
+ * Get room metrics.
781
+ */
782
+ getMetrics(): {
783
+ avgTickTime: number;
784
+ maxTickTime: number;
785
+ minTickTime: number;
786
+ ticksPerSecond: number;
787
+ roomId: string;
788
+ tickCount: number;
789
+ uptime: number;
790
+ playerCount: number;
791
+ entityCount: number;
792
+ cellCount: number;
793
+ queuedInputs: number;
794
+ };
795
+ }
796
+
797
+ /**
798
+ * Game Server
799
+ *
800
+ * Multi-room game server.
801
+ * Manages room lifecycle and routing.
802
+ */
803
+
804
+ /**
805
+ * Multi-room game server.
806
+ *
807
+ * Orchestrates multiple game rooms and provides routing
808
+ * for Socket.io connections.
809
+ *
810
+ * @example
811
+ * ```typescript
812
+ * const server = new GameServer();
813
+ * server.setServer(io);
814
+ *
815
+ * const room = server.createRoom('room-1', new MyGameRules());
816
+ *
817
+ * // Later...
818
+ * server.destroyRoom('room-1');
819
+ * ```
820
+ */
821
+ declare class GameServer {
822
+ private readonly rooms;
823
+ private io;
824
+ /**
825
+ * Set the Socket.io server instance.
826
+ *
827
+ * This should be called before creating rooms to enable
828
+ * network functionality.
829
+ */
830
+ setServer(io: Server): void;
831
+ /**
832
+ * Create a new room.
833
+ *
834
+ * @param id - Unique room identifier
835
+ * @param rules - Game-specific logic
836
+ * @param config - Room configuration
837
+ * @returns The created room
838
+ * @throws If room with same ID already exists
839
+ */
840
+ createRoom<TEntity extends Entity = Entity>(id: string, rules: GameRules<TEntity>, config?: RoomConfig): Room<TEntity>;
841
+ /**
842
+ * Destroy a room.
843
+ *
844
+ * Stops the room's tick loop and removes it from the server.
845
+ *
846
+ * @param id - Room identifier
847
+ * @returns True if room was destroyed, false if not found
848
+ */
849
+ destroyRoom(id: string): boolean;
850
+ /**
851
+ * Get a room by ID.
852
+ */
853
+ getRoom(id: string): Room | undefined;
854
+ /**
855
+ * Get a room with specific entity type.
856
+ *
857
+ * @param id - Room identifier
858
+ * @returns Room cast to specific entity type, or undefined
859
+ */
860
+ getRoomAs<TEntity extends Entity>(id: string): Room<TEntity> | undefined;
861
+ /**
862
+ * Check if room exists.
863
+ */
864
+ hasRoom(id: string): boolean;
865
+ /**
866
+ * Get all rooms.
867
+ */
868
+ getRooms(): Map<string, Room>;
869
+ /**
870
+ * Get room count.
871
+ */
872
+ getRoomCount(): number;
873
+ /**
874
+ * Get total player count across all rooms.
875
+ */
876
+ getTotalPlayerCount(): number;
877
+ /**
878
+ * Get server health metrics.
879
+ */
880
+ getMetrics(): ServerMetrics;
881
+ /**
882
+ * Stop all rooms.
883
+ */
884
+ stopAll(): void;
885
+ /**
886
+ * Destroy all rooms.
887
+ */
888
+ destroyAll(): void;
889
+ }
890
+
891
+ /**
892
+ * AABB Collision Detection
893
+ *
894
+ * Axis-Aligned Bounding Box collision detection.
895
+ */
896
+
897
+ /**
898
+ * Axis-Aligned Bounding Box.
899
+ */
900
+ interface AABB {
901
+ x: number;
902
+ y: number;
903
+ width: number;
904
+ height: number;
905
+ }
906
+ /**
907
+ * AABB collision detection utilities.
908
+ */
909
+ declare class AABBCollision {
910
+ /**
911
+ * Check if two AABBs overlap.
912
+ */
913
+ static overlaps(a: AABB, b: AABB): boolean;
914
+ /**
915
+ * Check if a point is inside an AABB.
916
+ */
917
+ static containsPoint(aabb: AABB, x: number, y: number): boolean;
918
+ /**
919
+ * Create AABB from entity.
920
+ */
921
+ static fromEntity(entity: Entity, width: number, height: number): AABB;
922
+ /**
923
+ * Get the distance between two AABBs.
924
+ * Returns 0 if overlapping.
925
+ */
926
+ static distance(a: AABB, b: AABB): number;
927
+ /**
928
+ * Compute the overlap amount between two AABBs.
929
+ * Returns { x: 0, y: 0 } if not overlapping.
930
+ */
931
+ static overlap(a: AABB, b: AABB): {
932
+ x: number;
933
+ y: number;
934
+ };
935
+ }
936
+
937
+ /**
938
+ * Movement Physics
939
+ *
940
+ * Velocity integration and boundary constraints.
941
+ */
942
+
943
+ /**
944
+ * Boundary constraints for movement.
945
+ */
946
+ interface Boundary {
947
+ minX: number;
948
+ maxX: number;
949
+ minY: number;
950
+ maxY: number;
951
+ }
952
+ /**
953
+ * Movement physics utilities.
954
+ */
955
+ declare class Movement {
956
+ /**
957
+ * Update entity position based on velocity.
958
+ *
959
+ * @param entity - Entity to update
960
+ * @param deltaMs - Time delta in milliseconds
961
+ */
962
+ static integrate(entity: Entity, deltaMs: number): void;
963
+ /**
964
+ * Apply velocity to entity.
965
+ */
966
+ static applyVelocity(entity: Entity, vx: number, vy: number): void;
967
+ /**
968
+ * Stop entity movement.
969
+ */
970
+ static stop(entity: Entity): void;
971
+ /**
972
+ * Constrain entity to boundary.
973
+ *
974
+ * @param entity - Entity to constrain
975
+ * @param boundary - Boundary constraints
976
+ * @returns True if entity was clamped
977
+ */
978
+ static constrainToBoundary(entity: Entity, boundary: Boundary): boolean;
979
+ /**
980
+ * Calculate distance between two entities.
981
+ */
982
+ static distance(a: Entity, b: Entity): number;
983
+ /**
984
+ * Normalize a direction vector.
985
+ */
986
+ static normalize(x: number, y: number): {
987
+ x: number;
988
+ y: number;
989
+ };
990
+ /**
991
+ * Calculate velocity from direction and speed.
992
+ */
993
+ static velocityFromDirection(dirX: number, dirY: number, speed: number): {
994
+ vx: number;
995
+ vy: number;
996
+ };
997
+ }
998
+
999
+ /**
1000
+ * Snapshot System
1001
+ *
1002
+ * Full state snapshots for network synchronization.
1003
+ */
1004
+
1005
+ /**
1006
+ * Snapshot generator.
1007
+ *
1008
+ * Creates full state snapshots for sending to clients.
1009
+ * Used for initial sync and periodic full updates.
1010
+ */
1011
+ declare class Snapshot {
1012
+ /**
1013
+ * Create a full state snapshot from entities.
1014
+ *
1015
+ * @param tick - Current tick number
1016
+ * @param entities - Array of entities to snapshot
1017
+ * @returns Full state snapshot
1018
+ */
1019
+ static create(tick: number, entities: Entity[]): StateSnapshot;
1020
+ /**
1021
+ * Convert an entity to a snapshot.
1022
+ */
1023
+ static entityToSnapshot(entity: Entity): EntitySnapshot;
1024
+ /**
1025
+ * Create a delta snapshot (only dirty entities).
1026
+ *
1027
+ * @param tick - Current tick number
1028
+ * @param dirtyEntities - Array of dirty entities
1029
+ * @returns Delta snapshot
1030
+ */
1031
+ static createDelta(tick: number, dirtyEntities: Entity[]): StateSnapshot;
1032
+ }
1033
+
1034
+ /**
1035
+ * Ring Buffer
1036
+ *
1037
+ * O(1) insert and remove, fixed capacity, no allocations in hot path.
1038
+ * Used for input queues, position history, etc.
1039
+ */
1040
+ declare class RingBuffer<T> {
1041
+ private readonly capacity;
1042
+ private readonly buffer;
1043
+ private head;
1044
+ private tail;
1045
+ private count;
1046
+ constructor(capacity: number);
1047
+ /**
1048
+ * Add item to buffer. O(1)
1049
+ * Returns false if buffer is full.
1050
+ */
1051
+ push(item: T): boolean;
1052
+ /**
1053
+ * Add item, overwriting oldest if full. O(1)
1054
+ */
1055
+ pushOverwrite(item: T): T | undefined;
1056
+ /**
1057
+ * Remove and return oldest item. O(1)
1058
+ */
1059
+ shift(): T | undefined;
1060
+ /**
1061
+ * Peek at oldest item without removing. O(1)
1062
+ */
1063
+ peek(): T | undefined;
1064
+ /**
1065
+ * Get current size.
1066
+ */
1067
+ size(): number;
1068
+ /**
1069
+ * Check if empty.
1070
+ */
1071
+ isEmpty(): boolean;
1072
+ /**
1073
+ * Check if full.
1074
+ */
1075
+ isFull(): boolean;
1076
+ /**
1077
+ * Clear all items. O(1)
1078
+ */
1079
+ clear(): void;
1080
+ /**
1081
+ * Get capacity.
1082
+ */
1083
+ getCapacity(): number;
1084
+ }
1085
+
1086
+ /**
1087
+ * Logger utility
1088
+ *
1089
+ * Wrapper around pino for structured logging.
1090
+ * Consumers can override this with their own logger.
1091
+ */
1092
+
1093
+ /**
1094
+ * Logger instance.
1095
+ * Can be replaced by consumers.
1096
+ */
1097
+ declare let logger: pino.Logger<never, boolean>;
1098
+ /**
1099
+ * Set a custom logger instance.
1100
+ *
1101
+ * @param customLogger - Custom pino logger
1102
+ */
1103
+ declare function setLogger(customLogger: pino.Logger): void;
1104
+ /**
1105
+ * Create a child logger with additional context.
1106
+ *
1107
+ * @param bindings - Additional context fields
1108
+ */
1109
+ declare function createChildLogger(bindings: Record<string, unknown>): pino.Logger;
1110
+ /**
1111
+ * Logger class for dependency injection.
1112
+ */
1113
+ declare class Logger {
1114
+ private logger;
1115
+ constructor(bindings?: Record<string, unknown>);
1116
+ debug(obj: object, msg?: string): void;
1117
+ debug(msg: string): void;
1118
+ info(obj: object, msg?: string): void;
1119
+ info(msg: string): void;
1120
+ warn(obj: object, msg?: string): void;
1121
+ warn(msg: string): void;
1122
+ error(obj: object, msg?: string): void;
1123
+ error(msg: string): void;
1124
+ child(bindings: Record<string, unknown>): Logger;
1125
+ }
1126
+
1127
+ /**
1128
+ * Network protocol definitions.
1129
+ *
1130
+ * Opcodes and message formats for client-server communication.
1131
+ */
1132
+ /**
1133
+ * Client → Server opcodes.
1134
+ */
1135
+ declare enum ClientOpcode {
1136
+ /** Player movement input */
1137
+ C_MOVE = "C_MOVE",
1138
+ /** Player stop input */
1139
+ C_STOP = "C_STOP",
1140
+ /** Generic action */
1141
+ C_ACTION = "C_ACTION"
1142
+ }
1143
+ /**
1144
+ * Server → Client opcodes.
1145
+ */
1146
+ declare enum ServerOpcode {
1147
+ /** Initial state on join */
1148
+ S_INIT = "S_INIT",
1149
+ /** Delta state update */
1150
+ S_UPDATE = "S_UPDATE",
1151
+ /** Entity spawned */
1152
+ S_SPAWN = "S_SPAWN",
1153
+ /** Entity destroyed */
1154
+ S_DESPAWN = "S_DESPAWN",
1155
+ /** Custom game event */
1156
+ S_EVENT = "S_EVENT",
1157
+ /** Error message */
1158
+ S_ERROR = "S_ERROR"
1159
+ }
1160
+ /**
1161
+ * Client movement input.
1162
+ */
1163
+ interface C_Move {
1164
+ op: ClientOpcode.C_MOVE;
1165
+ seq: number;
1166
+ dir: {
1167
+ x: number;
1168
+ y: number;
1169
+ };
1170
+ timestamp: number;
1171
+ }
1172
+ /**
1173
+ * Client stop input.
1174
+ */
1175
+ interface C_Stop {
1176
+ op: ClientOpcode.C_STOP;
1177
+ seq: number;
1178
+ timestamp: number;
1179
+ }
1180
+ /**
1181
+ * Client action input.
1182
+ */
1183
+ interface C_Action {
1184
+ op: ClientOpcode.C_ACTION;
1185
+ seq: number;
1186
+ action: string;
1187
+ data?: Record<string, unknown>;
1188
+ timestamp: number;
1189
+ }
1190
+ /**
1191
+ * Server initial state.
1192
+ */
1193
+ interface S_Init {
1194
+ op: ServerOpcode.S_INIT;
1195
+ playerId: string;
1196
+ tick: number;
1197
+ entities: Array<{
1198
+ id: string;
1199
+ x: number;
1200
+ y: number;
1201
+ vx: number;
1202
+ vy: number;
1203
+ }>;
1204
+ }
1205
+ /**
1206
+ * Server delta update.
1207
+ */
1208
+ interface S_Update {
1209
+ op: ServerOpcode.S_UPDATE;
1210
+ tick: number;
1211
+ entities: Array<{
1212
+ id: string;
1213
+ x: number;
1214
+ y: number;
1215
+ vx: number;
1216
+ vy: number;
1217
+ seq?: number;
1218
+ }>;
1219
+ }
1220
+ /**
1221
+ * Server entity spawn.
1222
+ */
1223
+ interface S_Spawn {
1224
+ op: ServerOpcode.S_SPAWN;
1225
+ entity: {
1226
+ id: string;
1227
+ x: number;
1228
+ y: number;
1229
+ vx: number;
1230
+ vy: number;
1231
+ };
1232
+ }
1233
+ /**
1234
+ * Server entity despawn.
1235
+ */
1236
+ interface S_Despawn {
1237
+ op: ServerOpcode.S_DESPAWN;
1238
+ entityId: string;
1239
+ reason?: string;
1240
+ }
1241
+ /**
1242
+ * Server custom event.
1243
+ */
1244
+ interface S_Event {
1245
+ op: ServerOpcode.S_EVENT;
1246
+ event: string;
1247
+ data?: Record<string, unknown>;
1248
+ }
1249
+ /**
1250
+ * Server error message.
1251
+ */
1252
+ interface S_Error {
1253
+ op: ServerOpcode.S_ERROR;
1254
+ code: string;
1255
+ message: string;
1256
+ }
1257
+ /**
1258
+ * Union of all client messages.
1259
+ */
1260
+ type ClientMessage = C_Move | C_Stop | C_Action;
1261
+ /**
1262
+ * Union of all server messages.
1263
+ */
1264
+ type ServerMessage = S_Init | S_Update | S_Spawn | S_Despawn | S_Event | S_Error;
1265
+
1266
+ export { type AABB, AABBCollision, type ActionCommand, type Boundary, type C_Action, type C_Move, type C_Stop, type ClientMessage, ClientOpcode, type Command, Entity, type EntitySnapshot, GameLoop, type GameRules, GameServer, Grid, InputQueue, Logger, type LoopMetrics, type MoveCommand, Movement, Network, type NetworkEvent, Registry, RingBuffer, Room, type RoomConfig, type RoomMetrics, type S_Despawn, type S_Error, type S_Event, type S_Init, type S_Spawn, type S_Update, type ServerMessage, type ServerMetrics, ServerOpcode, Snapshot, type StateSnapshot, type StopCommand, type TickHandler, createChildLogger, logger, setLogger };