@gamerstake/game-core 0.1.0 → 0.2.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.
@@ -0,0 +1,390 @@
1
+ # N4: @gamerstake/game-core - Testing & Validation Guide
2
+
3
+ > **Version:** 0.1.0
4
+ > **Last Updated:** January 2026
5
+
6
+ ---
7
+
8
+ ## Table of Contents
9
+
10
+ 1. [Testing Overview](#1-testing-overview)
11
+ 2. [Unit Testing](#2-unit-testing)
12
+ 3. [Integration Testing](#3-integration-testing)
13
+ 4. [Manual Testing](#4-manual-testing)
14
+ 5. [Performance Testing](#5-performance-testing)
15
+ 6. [Coverage Requirements](#6-coverage-requirements)
16
+ 7. [Validation Checklist](#7-validation-checklist)
17
+
18
+ ---
19
+
20
+ ## 1. Testing Overview
21
+
22
+ ### 1.1 Testing Stack
23
+
24
+ | Tool | Purpose |
25
+ | ---------------- | ------------------------ |
26
+ | Jest | Test runner & assertions |
27
+ | ts-jest | TypeScript support |
28
+ | socket.io-client | Client mocking |
29
+
30
+ ### 1.2 Running Tests
31
+
32
+ ```bash
33
+ # Run all tests
34
+ pnpm test
35
+
36
+ # Run with coverage
37
+ pnpm test:coverage
38
+
39
+ # Run in watch mode
40
+ pnpm test:watch
41
+
42
+ # Type checking
43
+ pnpm typecheck
44
+ ```
45
+
46
+ ---
47
+
48
+ ## 2. Unit Testing
49
+
50
+ ### 2.1 Entity Tests
51
+
52
+ ```typescript
53
+ import { describe, it, expect } from '@jest/globals';
54
+ import { Entity } from '@gamerstake/game-core';
55
+
56
+ describe('Entity', () => {
57
+ it('creates entity with initial position', () => {
58
+ const entity = new Entity('test-1', 100, 200);
59
+
60
+ expect(entity.id).toBe('test-1');
61
+ expect(entity.x).toBe(100);
62
+ expect(entity.y).toBe(200);
63
+ expect(entity.dirty).toBe(true);
64
+ });
65
+
66
+ it('updates position based on velocity', () => {
67
+ const entity = new Entity('test-1', 0, 0);
68
+ entity.setVelocity(100, 50);
69
+
70
+ entity.updatePosition(1000); // 1 second
71
+
72
+ expect(entity.x).toBe(100);
73
+ expect(entity.y).toBe(50);
74
+ });
75
+
76
+ it('marks dirty when position changes', () => {
77
+ const entity = new Entity('test-1', 0, 0);
78
+ entity.markClean();
79
+
80
+ entity.setPosition(10, 20);
81
+
82
+ expect(entity.dirty).toBe(true);
83
+ });
84
+ });
85
+ ```
86
+
87
+ ### 2.2 Room Tests
88
+
89
+ ```typescript
90
+ import { describe, it, expect, jest } from '@jest/globals';
91
+ import { Room, Entity, GameRules } from '@gamerstake/game-core';
92
+
93
+ const createMockRules = (): GameRules<Entity> => ({
94
+ onRoomCreated: jest.fn(),
95
+ onPlayerJoin: jest.fn(),
96
+ onPlayerLeave: jest.fn(),
97
+ onTick: jest.fn(),
98
+ onCommand: jest.fn(),
99
+ shouldEndRoom: jest.fn(() => false),
100
+ });
101
+
102
+ describe('Room', () => {
103
+ it('adds and removes players', () => {
104
+ const rules = createMockRules();
105
+ const room = new Room('room-1', rules);
106
+ const player = new Entity('p1', 0, 0);
107
+
108
+ room.addPlayer(player);
109
+ expect(rules.onPlayerJoin).toHaveBeenCalledWith(room, player);
110
+ expect(room.getRegistry().has('p1')).toBe(true);
111
+
112
+ room.removePlayer('p1');
113
+ expect(rules.onPlayerLeave).toHaveBeenCalledWith(room, 'p1');
114
+ expect(room.getRegistry().has('p1')).toBe(false);
115
+ });
116
+
117
+ it('processes input commands on tick', () => {
118
+ const rules = createMockRules();
119
+ const room = new Room('room-1', rules);
120
+ const player = new Entity('p1', 0, 0);
121
+ room.addPlayer(player);
122
+
123
+ room.queueInput('p1', {
124
+ seq: 1,
125
+ type: 'move',
126
+ dir: { x: 1, y: 0 },
127
+ timestamp: Date.now(),
128
+ });
129
+
130
+ room.onTick(1, 50);
131
+
132
+ expect(rules.onCommand).toHaveBeenCalledTimes(1);
133
+ expect(rules.onTick).toHaveBeenCalledTimes(1);
134
+ });
135
+ });
136
+ ```
137
+
138
+ ### 2.3 Custom Entity Tests
139
+
140
+ ```typescript
141
+ describe('Player Entity', () => {
142
+ it('handles damage correctly', () => {
143
+ const player = new Player('p1', 0, 0, 'blue');
144
+ player.health = 100;
145
+
146
+ const died = player.takeDamage(30);
147
+
148
+ expect(player.health).toBe(70);
149
+ expect(died).toBe(false);
150
+ expect(player.dirty).toBe(true);
151
+ });
152
+
153
+ it('detects death', () => {
154
+ const player = new Player('p1', 0, 0, 'blue');
155
+ player.health = 20;
156
+
157
+ const died = player.takeDamage(50);
158
+
159
+ expect(player.health).toBe(0);
160
+ expect(died).toBe(true);
161
+ });
162
+ });
163
+ ```
164
+
165
+ ---
166
+
167
+ ## 3. Integration Testing
168
+
169
+ ### 3.1 GameServer Integration
170
+
171
+ ```typescript
172
+ describe('GameServer Integration', () => {
173
+ it('manages multiple rooms', () => {
174
+ const server = new GameServer();
175
+ const rules = createMockRules();
176
+
177
+ const room1 = server.createRoom('room-1', rules);
178
+ const room2 = server.createRoom('room-2', rules);
179
+
180
+ expect(server.getRoomCount()).toBe(2);
181
+ expect(server.getRoom('room-1')).toBe(room1);
182
+ });
183
+
184
+ it('prevents duplicate room IDs', () => {
185
+ const server = new GameServer();
186
+ const rules = createMockRules();
187
+
188
+ server.createRoom('room-1', rules);
189
+
190
+ expect(() => server.createRoom('room-1', rules)).toThrow('already exists');
191
+ });
192
+
193
+ it('provides aggregated metrics', () => {
194
+ const server = new GameServer();
195
+ const rules = createMockRules();
196
+
197
+ const room1 = server.createRoom('room-1', rules);
198
+ room1.addPlayer(new Entity('p1', 0, 0));
199
+ room1.addPlayer(new Entity('p2', 0, 0));
200
+
201
+ const metrics = server.getMetrics();
202
+
203
+ expect(metrics.roomCount).toBe(1);
204
+ expect(metrics.totalPlayers).toBe(2);
205
+ });
206
+ });
207
+ ```
208
+
209
+ ---
210
+
211
+ ## 4. Manual Testing
212
+
213
+ ### 4.1 Setup
214
+
215
+ ```bash
216
+ # Build the package
217
+ cd packages/game-core
218
+ pnpm build
219
+
220
+ # Start test server
221
+ node examples/simple-game/server.js
222
+
223
+ # In another terminal, start client
224
+ node examples/simple-game/client.js
225
+ ```
226
+
227
+ ### 4.2 Test Scenarios
228
+
229
+ | Scenario | Steps | Expected Result |
230
+ | ------------ | -------------------------- | ----------------------------- |
231
+ | Connection | Start server, start client | "Connected! Player ID: xxx" |
232
+ | Movement | Connect, wait 1s | Position updates logged |
233
+ | Multi-player | Start 3+ clients | All clients see each other |
234
+ | Disconnect | Kill client (Ctrl+C) | Server logs "Player xxx left" |
235
+
236
+ ### 4.3 Manual Testing Checklist
237
+
238
+ ```markdown
239
+ ## Connection Tests
240
+
241
+ - [ ] Single client connects successfully
242
+ - [ ] Multiple clients connect simultaneously
243
+ - [ ] Client receives S_INIT with player ID
244
+
245
+ ## Movement Tests
246
+
247
+ - [ ] Player moves when C_MOVE sent
248
+ - [ ] Player stops when C_STOP sent
249
+ - [ ] Position updates broadcast to all clients
250
+
251
+ ## Disconnection Tests
252
+
253
+ - [ ] Client disconnect handled gracefully
254
+ - [ ] Other clients notified of disconnect
255
+ - [ ] Server continues running after disconnect
256
+ ```
257
+
258
+ ---
259
+
260
+ ## 5. Performance Testing
261
+
262
+ ### 5.1 Benchmark Setup
263
+
264
+ ```typescript
265
+ async function runBenchmark(entityCount: number, iterations: number) {
266
+ const room = new Room('benchmark', new BenchmarkRules(), { cellSize: 512 });
267
+
268
+ // Spawn entities
269
+ for (let i = 0; i < entityCount; i++) {
270
+ const entity = new Entity(`e${i}`, Math.random() * 1000, Math.random() * 1000);
271
+ entity.setVelocity(Math.random() * 100, Math.random() * 100);
272
+ room.spawnEntity(entity);
273
+ }
274
+
275
+ // Benchmark
276
+ const times: number[] = [];
277
+ for (let i = 0; i < iterations; i++) {
278
+ const start = performance.now();
279
+ room.onTick(i, 50);
280
+ times.push(performance.now() - start);
281
+ }
282
+
283
+ const avg = times.reduce((a, b) => a + b, 0) / times.length;
284
+ console.log(`Entities: ${entityCount}, Avg tick: ${avg.toFixed(2)}ms`);
285
+ }
286
+ ```
287
+
288
+ ### 5.2 Performance Criteria
289
+
290
+ | Metric | Pass | Warn | Fail |
291
+ | ----------------------- | ------ | -------- | ------- |
292
+ | Avg tick (100 entities) | < 10ms | 10-30ms | > 30ms |
293
+ | Avg tick (500 entities) | < 25ms | 25-40ms | > 40ms |
294
+ | Max tick | < 50ms | 50-100ms | > 100ms |
295
+
296
+ ---
297
+
298
+ ## 6. Coverage Requirements
299
+
300
+ ### 6.1 Minimum Coverage
301
+
302
+ ```javascript
303
+ // jest.config.ts
304
+ coverageThreshold: {
305
+ global: {
306
+ branches: 80,
307
+ functions: 80,
308
+ lines: 80,
309
+ statements: 80,
310
+ },
311
+ }
312
+ ```
313
+
314
+ ### 6.2 Coverage by Module
315
+
316
+ | Module | Target |
317
+ | ---------- | ------ |
318
+ | Entity | 90% |
319
+ | Registry | 85% |
320
+ | Room | 85% |
321
+ | GameServer | 80% |
322
+ | Grid | 80% |
323
+ | InputQueue | 85% |
324
+ | Network | 75% |
325
+
326
+ ---
327
+
328
+ ## 7. Validation Checklist
329
+
330
+ ### 7.1 Pre-Release Validation
331
+
332
+ ```markdown
333
+ ## Code Quality
334
+
335
+ - [ ] All unit tests passing
336
+ - [ ] Coverage >= 80%
337
+ - [ ] No TypeScript errors
338
+ - [ ] No lint errors
339
+ - [ ] Build succeeds
340
+
341
+ ## Integration Tests
342
+
343
+ - [ ] GameServer manages rooms correctly
344
+ - [ ] Room tick cycle works
345
+ - [ ] Player join/leave handled
346
+ - [ ] Input processing works
347
+ - [ ] State broadcasting works
348
+
349
+ ## Manual Testing
350
+
351
+ - [ ] Connection test passed
352
+ - [ ] Movement test passed
353
+ - [ ] Multi-player test passed
354
+ - [ ] Disconnection test passed
355
+
356
+ ## Performance
357
+
358
+ - [ ] Avg tick < 40ms (100 entities)
359
+ - [ ] Max tick < 50ms
360
+ - [ ] No memory leaks
361
+ - [ ] 20 TPS maintained under load
362
+ ```
363
+
364
+ ### 7.2 Integration Validation
365
+
366
+ ```markdown
367
+ ## Setup Validation
368
+
369
+ - [ ] Package installed correctly
370
+ - [ ] Imports working
371
+ - [ ] TypeScript types available
372
+
373
+ ## Implementation Validation
374
+
375
+ - [ ] GameRules implemented completely
376
+ - [ ] All callbacks implemented
377
+ - [ ] Custom entities serialize correctly
378
+
379
+ ## Runtime Validation
380
+
381
+ - [ ] Room starts and ticks
382
+ - [ ] Players can join
383
+ - [ ] Inputs processed correctly
384
+ - [ ] State synchronized
385
+ ```
386
+
387
+ ---
388
+
389
+ **Previous:** [N3 - Implementation Guide](./N3-implementation-guide.md)
390
+ **Next:** [N5 - Integration Checklist](./N5-integration-checklist.md)
@@ -0,0 +1,90 @@
1
+ # N5: Integration Checklist
2
+
3
+ > Version: 0.1.0
4
+
5
+ ## Phase 1: Environment Setup
6
+
7
+ ### Prerequisites
8
+
9
+ - [ ] Node.js 20+ installed
10
+ - [ ] pnpm available
11
+ - [ ] TypeScript installed
12
+
13
+ ### Installation
14
+
15
+ ```bash
16
+ pnpm add @gamerstake/game-core socket.io
17
+ pnpm add -D typescript @types/node tsx
18
+ ```
19
+
20
+ ## Phase 2: Implementation
21
+
22
+ ### GameRules Checklist
23
+
24
+ - [ ] onRoomCreated implemented
25
+ - [ ] onPlayerJoin implemented
26
+ - [ ] onPlayerLeave implemented
27
+ - [ ] onTick implemented
28
+ - [ ] onCommand implemented
29
+ - [ ] shouldEndRoom implemented
30
+
31
+ ### Server Checklist
32
+
33
+ - [ ] Socket.io server created
34
+ - [ ] GameServer instance created
35
+ - [ ] Room created with config
36
+ - [ ] Connection handler with addPlayer
37
+ - [ ] C_MOVE and C_STOP handlers
38
+ - [ ] Disconnect handler with removePlayer
39
+
40
+ ## Phase 3: Testing
41
+
42
+ ### Unit Tests
43
+
44
+ - [ ] Entity tests passing
45
+ - [ ] Room tests passing
46
+ - [ ] GameRules tests passing
47
+
48
+ ### Manual Tests
49
+
50
+ - [ ] Server starts
51
+ - [ ] Client connects
52
+ - [ ] Movement works
53
+ - [ ] Multiple players work
54
+ - [ ] Disconnect handled
55
+
56
+ ## Phase 4: Verification
57
+
58
+ | Feature | Pass |
59
+ | --------------------- | ---- |
60
+ | S_INIT received | [ ] |
61
+ | S_UPDATE received | [ ] |
62
+ | Movement works | [ ] |
63
+ | Other players visible | [ ] |
64
+
65
+ ### Performance
66
+
67
+ - [ ] Avg tick < 40ms
68
+ - [ ] Max tick < 50ms
69
+ - [ ] 20 TPS maintained
70
+
71
+ ## Phase 5: Production
72
+
73
+ - [ ] Error handling added
74
+ - [ ] Graceful shutdown
75
+ - [ ] Input validation
76
+ - [ ] Logging configured
77
+
78
+ ## Sign-Off
79
+
80
+ | Phase | Complete |
81
+ | -------------- | -------- |
82
+ | Environment | [ ] |
83
+ | Implementation | [ ] |
84
+ | Testing | [ ] |
85
+ | Verification | [ ] |
86
+ | Production | [ ] |
87
+
88
+ ---
89
+
90
+ **Previous:** [N4 - Testing](./N4-testing-validation.md)
package/docs/README.md ADDED
@@ -0,0 +1,137 @@
1
+ # @gamerstake/game-core - Package Documentation
2
+
3
+ > **For External Teams**
4
+ > **Version:** 0.1.0
5
+ > **Last Updated:** January 2026
6
+
7
+ ---
8
+
9
+ ## Welcome
10
+
11
+ This documentation package provides everything you need to integrate `@gamerstake/game-core` into your multiplayer game. The package is a battle-tested, high-performance multiplayer game engine that handles real-time networking, game loops, entity management, and state synchronization.
12
+
13
+ ---
14
+
15
+ ## Document Index
16
+
17
+ | Document | Description | Audience |
18
+ | ----------------------------------------------------------- | ---------------------------------------------- | ------------- |
19
+ | [N1 - Package Overview](./N1-package-overview.md) | Executive summary, requirements, core concepts | Everyone |
20
+ | [N2 - API Reference](./N2-api-reference.md) | Complete API documentation | Developers |
21
+ | [N3 - Implementation Guide](./N3-implementation-guide.md) | Step-by-step implementation tutorial | Developers |
22
+ | [N4 - Testing & Validation](./N4-testing-validation.md) | Testing requirements and patterns | QA/Developers |
23
+ | [N5 - Integration Checklist](./N5-integration-checklist.md) | Verification checklist for integration | All Teams |
24
+
25
+ ---
26
+
27
+ ## Quick Navigation
28
+
29
+ ### Just Getting Started?
30
+
31
+ 1. **Read** [N1 - Package Overview](./N1-package-overview.md) to understand what game-core provides
32
+ 2. **Follow** [N3 - Implementation Guide](./N3-implementation-guide.md) Quick Start section
33
+ 3. **Use** [N5 - Integration Checklist](./N5-integration-checklist.md) to verify your setup
34
+
35
+ ### Need API Details?
36
+
37
+ - [N2 - API Reference](./N2-api-reference.md) has complete documentation for all exports
38
+
39
+ ### Setting Up Testing?
40
+
41
+ - [N4 - Testing & Validation](./N4-testing-validation.md) covers unit tests, integration tests, and performance testing
42
+
43
+ ---
44
+
45
+ ## What game-core Provides
46
+
47
+ - **Fixed tick-rate game loop** (20 TPS with drift compensation)
48
+ - **Authoritative server architecture** (server is source of truth)
49
+ - **Client-server networking** (via Socket.io)
50
+ - **State synchronization** (full snapshots + delta updates)
51
+ - **Input buffering** (reliable command processing)
52
+ - **Entity management** (ECS-lite with dirty tracking)
53
+ - **Spatial partitioning** (grid-based O(1) queries)
54
+ - **Basic 2D physics** (velocity, position, AABB collision)
55
+
56
+ ## What You Provide
57
+
58
+ - **Game rules** (implement the `GameRules` interface)
59
+ - **Game client** (render game state, send inputs)
60
+ - **Persistence** (optional - save/load game state)
61
+ - **Authentication** (optional - player identity)
62
+
63
+ ---
64
+
65
+ ## Minimum Example
66
+
67
+ ```typescript
68
+ // 1. Implement GameRules
69
+ import { GameRules, Room, Entity, Command, MoveCommand } from '@gamerstake/game-core';
70
+
71
+ class MyGameRules implements GameRules {
72
+ onRoomCreated(room: Room): void {}
73
+ onPlayerJoin(room: Room, player: Entity): void {
74
+ player.setPosition(Math.random() * 800, Math.random() * 600);
75
+ }
76
+ onPlayerLeave(room: Room, playerId: string): void {}
77
+ onTick(room: Room, delta: number): void {
78
+ room.getRegistry().forEach((e) => e.updatePosition(delta));
79
+ }
80
+ onCommand(room: Room, playerId: string, command: Command): void {
81
+ const player = room.getRegistry().get(playerId);
82
+ if (!player) return;
83
+ if (command.type === 'move') {
84
+ const { dir } = command as MoveCommand;
85
+ player.setVelocity(dir.x * 200, dir.y * 200);
86
+ }
87
+ }
88
+ shouldEndRoom(room: Room): boolean {
89
+ return false;
90
+ }
91
+ }
92
+
93
+ // 2. Create server
94
+ import { Server } from 'socket.io';
95
+ import { GameServer, Entity } from '@gamerstake/game-core';
96
+
97
+ const io = new Server(3000);
98
+ const gameServer = new GameServer();
99
+ gameServer.setServer(io);
100
+
101
+ const room = gameServer.createRoom('main', new MyGameRules());
102
+
103
+ io.on('connection', (socket) => {
104
+ const player = new Entity(socket.id, 0, 0);
105
+ room.addPlayer(player);
106
+ room.getNetwork().registerSocket(socket.id, socket);
107
+
108
+ socket.emit('S_INIT', { playerId: socket.id, ...room.getSnapshot() });
109
+
110
+ socket.on('C_MOVE', (data) => {
111
+ room.queueInput(socket.id, { ...data, type: 'move', timestamp: Date.now() });
112
+ });
113
+
114
+ socket.on('disconnect', () => {
115
+ room.removePlayer(socket.id);
116
+ room.getNetwork().unregisterSocket(socket.id);
117
+ });
118
+ });
119
+ ```
120
+
121
+ ---
122
+
123
+ ## Version History
124
+
125
+ | Version | Date | Changes |
126
+ | ------- | ------------ | --------------- |
127
+ | 0.1.0 | January 2026 | Initial release |
128
+
129
+ ---
130
+
131
+ ## Support
132
+
133
+ For questions or issues, contact the GamerStake engineering team.
134
+
135
+ ---
136
+
137
+ **Start with:** [N1 - Package Overview](./N1-package-overview.md)
@@ -27,6 +27,7 @@ node examples/simple-game/server.js
27
27
  ```
28
28
 
29
29
  You should see:
30
+
30
31
  ```
31
32
  Game server started on port 3000
32
33
  Room: test-room
@@ -40,6 +41,7 @@ node examples/simple-game/client.js
40
41
  ```
41
42
 
42
43
  The client will:
44
+
43
45
  - Connect to the server
44
46
  - Receive initial game state
45
47
  - Automatically move around in different patterns
@@ -75,6 +77,7 @@ Each client is a separate player. You'll see them join in the server logs!
75
77
  ### Test 1: Movement
76
78
 
77
79
  The client automatically cycles through movement patterns:
80
+
78
81
  - Right, Down, Left, Up
79
82
  - Diagonal movement
80
83
  - Stop command
@@ -83,6 +86,7 @@ The client automatically cycles through movement patterns:
83
86
  ### Test 2: Multiple Players
84
87
 
85
88
  Run 3-5 clients simultaneously to test:
89
+
86
90
  - Multiple players in the same room
87
91
  - State synchronization
88
92
  - Network broadcasting
@@ -91,6 +95,7 @@ Run 3-5 clients simultaneously to test:
91
95
  ### Test 3: Performance
92
96
 
93
97
  Monitor the server metrics logs:
98
+
94
99
  ```
95
100
  Room: X players, Y entities, Avg tick: Zms
96
101
  ```
@@ -100,6 +105,7 @@ Tick time should stay well below 50ms (the tick budget for 20 TPS).
100
105
  ### Test 4: Connection Stability
101
106
 
102
107
  Try:
108
+
103
109
  - Stopping clients (Ctrl+C) and reconnecting
104
110
  - Stopping the server and restarting
105
111
  - Network interruptions
@@ -119,6 +125,7 @@ SERVER_URL=http://localhost:4000 node examples/simple-game/client.js
119
125
  ### Modify Game Rules
120
126
 
121
127
  Edit `server.ts` and change the `SimpleGameRules` class:
128
+
122
129
  - Spawn different entities
123
130
  - Change movement speed
124
131
  - Add collision detection
@@ -127,14 +134,15 @@ Edit `server.ts` and change the `SimpleGameRules` class:
127
134
  ### Add More Commands
128
135
 
129
136
  In `server.ts`, add new socket event handlers:
137
+
130
138
  ```typescript
131
139
  socket.on('C_CUSTOM', (data) => {
132
- room.queueInput(playerId, {
133
- seq: data.seq,
134
- type: 'custom',
135
- // ...custom data
136
- timestamp: Date.now(),
137
- });
140
+ room.queueInput(playerId, {
141
+ seq: data.seq,
142
+ type: 'custom',
143
+ // ...custom data
144
+ timestamp: Date.now(),
145
+ });
138
146
  });
139
147
  ```
140
148
 
@@ -145,6 +153,7 @@ Then handle it in `onCommand()`.
145
153
  ### "Cannot find module './dist/index.js'"
146
154
 
147
155
  You need to build the package first:
156
+
148
157
  ```bash
149
158
  pnpm build
150
159
  ```
@@ -152,6 +161,7 @@ pnpm build
152
161
  ### "Port 3000 already in use"
153
162
 
154
163
  Change the port:
164
+
155
165
  ```bash
156
166
  PORT=4000 node examples/simple-game/server.js
157
167
  ```
@@ -159,6 +169,7 @@ PORT=4000 node examples/simple-game/server.js
159
169
  ### Client can't connect
160
170
 
161
171
  Make sure:
172
+
162
173
  1. Server is running
163
174
  2. Port matches (default 3000)
164
175
  3. No firewall blocking