@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
package/README.md ADDED
@@ -0,0 +1,379 @@
1
+ # @gamerstake/game-core
2
+
3
+ Reusable multiplayer game engine for the GamerStake platform.
4
+
5
+ ## Overview
6
+
7
+ `game-core` is a battle-tested, high-performance multiplayer game engine extracted from the GamerStake metaverse. It provides:
8
+
9
+ - **Game-agnostic multiplayer infrastructure** (tick loop, networking, state management)
10
+ - **Pluggable game rules** via the `GameRules` interface
11
+ - **Room/Match lifecycle** management (persistent worlds OR match-based games)
12
+ - **Spatial partitioning** with O(1) queries via grid system
13
+ - **Basic 2D physics** (position, velocity, AABB collision)
14
+ - **Zero-allocation hot paths** for optimal performance
15
+
16
+ ## Features
17
+
18
+ ### Core Systems
19
+
20
+ - **GameServer** - Multi-room server management
21
+ - **Room** - Single game instance with tick loop
22
+ - **GameLoop** - Fixed 20 TPS with drift compensation
23
+ - **Entity System** - Base entity class with dirty flag tracking
24
+ - **Registry** - Entity lifecycle management
25
+
26
+ ### Spatial & Physics
27
+
28
+ - **Grid** - Spatial partitioning for efficient queries
29
+ - **AABB Collision** - Bounding box collision detection
30
+ - **Movement** - Velocity integration and boundary constraints
31
+
32
+ ### Networking
33
+
34
+ - **Network Layer** - Socket.io abstraction with opcodes
35
+ - **Snapshot System** - Full and delta state synchronization
36
+ - **InputQueue** - Buffered input processing with RingBuffer
37
+
38
+ ### Performance
39
+
40
+ - **Zero-allocation buffers** - Reused arrays in hot paths
41
+ - **Dirty flag tracking** - Only broadcast changed entities
42
+ - **RingBuffer** - O(1) push/pop for input queues
43
+ - **Performance metrics** - Real-time tick time monitoring
44
+
45
+ ## Installation
46
+
47
+ ### From Monorepo Workspace
48
+
49
+ If you're in the GamerStake monorepo:
50
+
51
+ ```json
52
+ {
53
+ "dependencies": {
54
+ "@gamerstake/game-core": "workspace:*"
55
+ }
56
+ }
57
+ ```
58
+
59
+ ### From NPM Registry
60
+
61
+ ```bash
62
+ pnpm add @gamerstake/game-core
63
+ ```
64
+
65
+ See [PUBLISHING.md](./PUBLISHING.md) for publishing and distribution options.
66
+
67
+ ## Quick Start
68
+
69
+ ### 1. Implement GameRules
70
+
71
+ ```typescript
72
+ import { GameRules, Room, Entity, MoveCommand } from '@gamerstake/game-core';
73
+
74
+ class MyGameRules implements GameRules {
75
+ onRoomCreated(room: Room): void {
76
+ console.log('Room created!');
77
+ }
78
+
79
+ onPlayerJoin(room: Room, player: Entity): void {
80
+ console.log(`Player ${player.id} joined`);
81
+ // Spawn player at random position
82
+ player.setPosition(Math.random() * 1000, Math.random() * 1000);
83
+ }
84
+
85
+ onPlayerLeave(room: Room, playerId: string): void {
86
+ console.log(`Player ${playerId} left`);
87
+ }
88
+
89
+ onTick(room: Room, delta: number): void {
90
+ // Update all entities
91
+ room.getRegistry().forEach((entity) => {
92
+ entity.updatePosition(delta);
93
+ });
94
+ }
95
+
96
+ onCommand(room: Room, playerId: string, command: Command): void {
97
+ const player = room.getRegistry().get(playerId);
98
+ if (!player) return;
99
+
100
+ if (command.type === 'move') {
101
+ const moveCmd = command as MoveCommand;
102
+ // Set velocity (e.g., 200 units/second)
103
+ const speed = 200;
104
+ player.setVelocity(moveCmd.dir.x * speed, moveCmd.dir.y * speed);
105
+ } else if (command.type === 'stop') {
106
+ player.setVelocity(0, 0);
107
+ }
108
+ }
109
+
110
+ shouldEndRoom(room: Room): boolean {
111
+ // For persistent worlds, return false
112
+ // For match-based games, check win condition
113
+ return false;
114
+ }
115
+ }
116
+ ```
117
+
118
+ ### 2. Create Server and Room
119
+
120
+ ```typescript
121
+ import { GameServer, Entity } from '@gamerstake/game-core';
122
+ import { Server } from 'socket.io';
123
+
124
+ // Create Socket.io server
125
+ const io = new Server(3000);
126
+
127
+ // Create game server
128
+ const gameServer = new GameServer();
129
+ gameServer.setServer(io);
130
+
131
+ // Create a room
132
+ const room = gameServer.createRoom('room-1', new MyGameRules(), {
133
+ tickRate: 20, // 20 ticks per second
134
+ cellSize: 512, // Grid cell size
135
+ maxEntities: 1000, // Max entities per room
136
+ });
137
+
138
+ // Handle player connections
139
+ io.on('connection', (socket) => {
140
+ const playerId = socket.id;
141
+
142
+ // Create player entity
143
+ const player = new Entity(playerId, 0, 0);
144
+ room.addPlayer(player);
145
+
146
+ // Register socket for networking
147
+ room.getNetwork().registerSocket(playerId, socket);
148
+
149
+ // Send initial state
150
+ room.sendTo(playerId, {
151
+ op: 'S_INIT',
152
+ playerId,
153
+ ...room.getSnapshot(),
154
+ });
155
+
156
+ // Handle player inputs
157
+ socket.on('C_MOVE', (data) => {
158
+ room.queueInput(playerId, {
159
+ seq: data.seq,
160
+ type: 'move',
161
+ dir: data.dir,
162
+ timestamp: Date.now(),
163
+ });
164
+ });
165
+
166
+ socket.on('disconnect', () => {
167
+ room.removePlayer(playerId);
168
+ room.getNetwork().unregisterSocket(playerId);
169
+ });
170
+ });
171
+
172
+ console.log('Game server running on port 3000');
173
+ ```
174
+
175
+ ### 3. Client Example
176
+
177
+ ```typescript
178
+ import { io } from 'socket.io-client';
179
+
180
+ const socket = io('http://localhost:3000');
181
+
182
+ let seq = 0;
183
+
184
+ socket.on('S_INIT', (data) => {
185
+ console.log('Initial state:', data);
186
+ });
187
+
188
+ socket.on('S_UPDATE', (data) => {
189
+ console.log('Delta update:', data);
190
+ // Update local entities
191
+ });
192
+
193
+ // Send movement input
194
+ function move(dirX: number, dirY: number) {
195
+ socket.emit('C_MOVE', {
196
+ seq: ++seq,
197
+ dir: { x: dirX, y: dirY },
198
+ timestamp: Date.now(),
199
+ });
200
+ }
201
+
202
+ // Example: Move right
203
+ move(1, 0);
204
+ ```
205
+
206
+ ## Architecture
207
+
208
+ ```
209
+ GameServer
210
+ Room 1
211
+ GameLoop (20 TPS)
212
+ Registry (Entities)
213
+ Grid (Spatial)
214
+ InputQueue
215
+ Network
216
+ GameRules (Your logic)
217
+ Room 2
218
+ Room N...
219
+ ```
220
+
221
+ ## API Reference
222
+
223
+ ### GameServer
224
+
225
+ ```typescript
226
+ const server = new GameServer();
227
+ server.setServer(io);
228
+ const room = server.createRoom(id, rules, config);
229
+ server.destroyRoom(id);
230
+ const metrics = server.getMetrics();
231
+ ```
232
+
233
+ ### Room
234
+
235
+ ```typescript
236
+ room.start();
237
+ room.stop();
238
+ room.addPlayer(entity);
239
+ room.removePlayer(playerId);
240
+ room.spawnEntity(entity);
241
+ room.destroyEntity(entityId);
242
+ room.queueInput(playerId, command);
243
+ room.broadcast(event);
244
+ room.sendTo(playerId, event);
245
+ const snapshot = room.getSnapshot();
246
+ ```
247
+
248
+ ### Entity
249
+
250
+ ```typescript
251
+ const entity = new Entity(id, x, y);
252
+ entity.setPosition(x, y);
253
+ entity.setVelocity(vx, vy);
254
+ entity.updatePosition(deltaMs);
255
+ entity.markDirty();
256
+ ```
257
+
258
+ ### Grid
259
+
260
+ ```typescript
261
+ const grid = new Grid(cellSize);
262
+ grid.addEntity(id, x, y);
263
+ grid.removeEntity(id);
264
+ grid.moveEntity(id, oldX, oldY, newX, newY);
265
+ const nearby = grid.getNearbyEntities(x, y, range);
266
+ ```
267
+
268
+ ## Configuration
269
+
270
+ ```typescript
271
+ interface RoomConfig {
272
+ tickRate?: number; // Default: 20 TPS
273
+ cellSize?: number; // Default: 512 units
274
+ maxInputQueueSize?: number; // Default: 100
275
+ maxEntities?: number; // Default: 1000
276
+ visibilityRange?: number; // Default: 1 (3x3 cells)
277
+ }
278
+ ```
279
+
280
+ ## Performance
281
+
282
+ ### Benchmarks
283
+
284
+ - **20+ TPS** with 100+ entities per room
285
+ - **<40ms** average tick time (80% budget)
286
+ - **O(1)** spatial queries via grid partitioning
287
+ - **Zero allocations** in hot paths (tick loop)
288
+
289
+ ### Optimizations
290
+
291
+ 1. **Dirty Flag Tracking** - Only broadcast changed entities
292
+ 2. **Buffer Reuse** - Pre-allocated arrays for queries
293
+ 3. **RingBuffer** - O(1) input queue operations
294
+ 4. **Spatial Grid** - Avoid O(n²) collision checks
295
+
296
+ ## Testing
297
+
298
+ ### Automated Tests
299
+
300
+ ```bash
301
+ # Run unit tests
302
+ pnpm test
303
+
304
+ # Run with coverage
305
+ pnpm test:coverage
306
+
307
+ # Run in watch mode
308
+ pnpm test:watch
309
+
310
+ # Type check
311
+ pnpm typecheck
312
+
313
+ # Build
314
+ pnpm build
315
+ ```
316
+
317
+ ### Manual Testing
318
+
319
+ To manually test the game engine with a live server:
320
+
321
+ ```bash
322
+ # 1. Build the package
323
+ pnpm build
324
+
325
+ # 2. Start the test server
326
+ node examples/simple-game/server.js
327
+
328
+ # 3. In another terminal, start a client
329
+ node examples/simple-game/client.js
330
+ ```
331
+
332
+ See `examples/simple-game/README.md` for detailed manual testing instructions.
333
+
334
+ ## Getting Started
335
+
336
+ Choose your path:
337
+
338
+ - ** Quick Start** → See [QUICK_START.md](./QUICK_START.md) - Get running in 5 minutes
339
+ - ** Full Guide** → See [DEVELOPER_GUIDE.md](./DEVELOPER_GUIDE.md) - Complete tutorial
340
+ - ** Examples** → See `examples/` directory - Working code samples
341
+
342
+ ## Examples
343
+
344
+ The `examples/` directory contains:
345
+
346
+ - `simple-game/` - Manual testing server & client with automated movement tests
347
+
348
+ ## Migration from Metaverse Shard
349
+
350
+ If you're migrating from the metaverse shard:
351
+
352
+ 1. Install `@gamerstake/game-core`
353
+ 2. Implement `GameRules` with your metaverse logic
354
+ 3. Replace direct `GameLoop`, `Grid`, `WorldState` usage
355
+ 4. Keep metaverse-specific: persistence, zones, gateway
356
+
357
+ See `docs/features/game-core/N2-architecture/game-core-rfc.md` for details.
358
+
359
+ ## Documentation
360
+
361
+ ### For Developers
362
+
363
+ - **[Quick Start Guide](./QUICK_START.md)** - Get a multiplayer game running in 5 minutes
364
+ - **[Developer Guide](./DEVELOPER_GUIDE.md)** - Comprehensive guide with examples and patterns
365
+ - **[Manual Testing](./examples/simple-game/README.md)** - How to manually test the engine
366
+ - **[Publishing Guide](./PUBLISHING.md)** - How to publish and distribute the package
367
+
368
+ ### Architecture
369
+
370
+ - [N1 Requirements (PRD)](../../docs/features/game-core/N1-requirements/game-core-prd.md)
371
+ - [N2 Architecture (RFC)](../../docs/features/game-core/N2-architecture/game-core-rfc.md)
372
+
373
+ ## License
374
+
375
+ UNLICENSED - Internal GamerStake package
376
+
377
+ ## Contributing
378
+
379
+ This is an internal package. For issues or feature requests, contact the GamerStake engineering team.