@quake2ts/test-utils 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +454 -0
  2. package/dist/index.cjs +5432 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.cts +2150 -0
  5. package/dist/index.d.ts +2150 -0
  6. package/dist/index.js +5165 -0
  7. package/dist/index.js.map +1 -0
  8. package/package.json +82 -0
  9. package/src/client/helpers/hud.ts +114 -0
  10. package/src/client/helpers/prediction.ts +136 -0
  11. package/src/client/helpers/view.ts +201 -0
  12. package/src/client/mocks/console.ts +75 -0
  13. package/src/client/mocks/download.ts +48 -0
  14. package/src/client/mocks/input.ts +246 -0
  15. package/src/client/mocks/network.ts +148 -0
  16. package/src/client/mocks/state.ts +148 -0
  17. package/src/e2e/network.ts +47 -0
  18. package/src/e2e/playwright.ts +90 -0
  19. package/src/e2e/visual.ts +172 -0
  20. package/src/engine/helpers/pipeline-test-template.ts +113 -0
  21. package/src/engine/helpers/webgpu-rendering.ts +251 -0
  22. package/src/engine/mocks/assets.ts +129 -0
  23. package/src/engine/mocks/audio.ts +152 -0
  24. package/src/engine/mocks/buffers.ts +88 -0
  25. package/src/engine/mocks/lighting.ts +64 -0
  26. package/src/engine/mocks/particles.ts +76 -0
  27. package/src/engine/mocks/renderer.ts +218 -0
  28. package/src/engine/mocks/webgl.ts +267 -0
  29. package/src/engine/mocks/webgpu.ts +262 -0
  30. package/src/engine/rendering.ts +103 -0
  31. package/src/game/factories.ts +204 -0
  32. package/src/game/helpers/physics.ts +171 -0
  33. package/src/game/helpers/save.ts +232 -0
  34. package/src/game/helpers.ts +310 -0
  35. package/src/game/mocks/ai.ts +67 -0
  36. package/src/game/mocks/combat.ts +61 -0
  37. package/src/game/mocks/items.ts +166 -0
  38. package/src/game/mocks.ts +105 -0
  39. package/src/index.ts +93 -0
  40. package/src/server/helpers/bandwidth.ts +127 -0
  41. package/src/server/helpers/multiplayer.ts +158 -0
  42. package/src/server/helpers/snapshot.ts +241 -0
  43. package/src/server/mockNetDriver.ts +106 -0
  44. package/src/server/mockTransport.ts +50 -0
  45. package/src/server/mocks/commands.ts +93 -0
  46. package/src/server/mocks/connection.ts +139 -0
  47. package/src/server/mocks/master.ts +97 -0
  48. package/src/server/mocks/physics.ts +32 -0
  49. package/src/server/mocks/state.ts +162 -0
  50. package/src/server/mocks/transport.ts +161 -0
  51. package/src/setup/audio.ts +118 -0
  52. package/src/setup/browser.ts +249 -0
  53. package/src/setup/canvas.ts +142 -0
  54. package/src/setup/node.ts +21 -0
  55. package/src/setup/storage.ts +60 -0
  56. package/src/setup/timing.ts +142 -0
  57. package/src/setup/webgl.ts +8 -0
  58. package/src/setup/webgpu.ts +113 -0
  59. package/src/shared/bsp.ts +145 -0
  60. package/src/shared/collision.ts +64 -0
  61. package/src/shared/factories.ts +88 -0
  62. package/src/shared/math.ts +65 -0
  63. package/src/shared/mocks.ts +243 -0
  64. package/src/shared/pak-loader.ts +45 -0
  65. package/src/visual/snapshots.ts +292 -0
@@ -0,0 +1,106 @@
1
+ import { NetDriver } from '@quake2ts/shared';
2
+ import { vi } from 'vitest';
3
+
4
+ export interface MockNetDriverState {
5
+ connected: boolean;
6
+ messagesSent: Uint8Array[];
7
+ messageHandlers: ((data: Uint8Array) => void)[];
8
+ closeHandlers: (() => void)[];
9
+ errorHandlers: ((err: Error) => void)[];
10
+ }
11
+
12
+ /**
13
+ * A mock implementation of NetDriver that provides testing hooks.
14
+ * Unlike the simple createMockNetDriver factory, this class maintains state
15
+ * and allows for stimulating events (receiving messages, triggering errors).
16
+ */
17
+ export class MockNetDriver implements NetDriver {
18
+ public state: MockNetDriverState = {
19
+ connected: false,
20
+ messagesSent: [],
21
+ messageHandlers: [],
22
+ closeHandlers: [],
23
+ errorHandlers: []
24
+ };
25
+
26
+ public connectSpy = vi.fn().mockImplementation(async (url: string) => {
27
+ this.state.connected = true;
28
+ });
29
+
30
+ public disconnectSpy = vi.fn().mockImplementation(() => {
31
+ this.state.connected = false;
32
+ this.state.closeHandlers.forEach(h => h());
33
+ });
34
+
35
+ public sendSpy = vi.fn().mockImplementation((data: Uint8Array) => {
36
+ this.state.messagesSent.push(new Uint8Array(data));
37
+ });
38
+
39
+ public connect(url: string): Promise<void> {
40
+ return this.connectSpy(url);
41
+ }
42
+
43
+ public disconnect(): void {
44
+ this.disconnectSpy();
45
+ }
46
+
47
+ public send(data: Uint8Array): void {
48
+ this.sendSpy(data);
49
+ }
50
+
51
+ public onMessage(cb: (data: Uint8Array) => void): void {
52
+ this.state.messageHandlers.push(cb);
53
+ }
54
+
55
+ public onClose(cb: () => void): void {
56
+ this.state.closeHandlers.push(cb);
57
+ }
58
+
59
+ public onError(cb: (err: Error) => void): void {
60
+ this.state.errorHandlers.push(cb);
61
+ }
62
+
63
+ public isConnected(): boolean {
64
+ return this.state.connected;
65
+ }
66
+
67
+ // --- Test Helpers ---
68
+
69
+ /**
70
+ * Simulate receiving a message from the network.
71
+ */
72
+ public receiveMessage(data: Uint8Array): void {
73
+ this.state.messageHandlers.forEach(h => h(data));
74
+ }
75
+
76
+ /**
77
+ * Simulate a connection close event from the remote side.
78
+ */
79
+ public simulateClose(): void {
80
+ this.state.connected = false;
81
+ this.state.closeHandlers.forEach(h => h());
82
+ }
83
+
84
+ /**
85
+ * Simulate a connection error.
86
+ */
87
+ public simulateError(err: Error): void {
88
+ this.state.errorHandlers.forEach(h => h(err));
89
+ }
90
+
91
+ /**
92
+ * Get the last sent message.
93
+ */
94
+ public getLastSentMessage(): Uint8Array | undefined {
95
+ return this.state.messagesSent.length > 0
96
+ ? this.state.messagesSent[this.state.messagesSent.length - 1]
97
+ : undefined;
98
+ }
99
+
100
+ /**
101
+ * Clear recorded sent messages.
102
+ */
103
+ public clearSentMessages(): void {
104
+ this.state.messagesSent = [];
105
+ }
106
+ }
@@ -0,0 +1,50 @@
1
+ import { NetChan } from '@quake2ts/shared';
2
+
3
+ export interface RecordedPacket {
4
+ type: 'sent' | 'received';
5
+ timestamp: number;
6
+ data: Uint8Array;
7
+ }
8
+
9
+ export class MockNetworkTransport {
10
+ public netchan: NetChan;
11
+ public recordedPackets: RecordedPacket[] = [];
12
+ public sentPackets: Uint8Array[] = [];
13
+
14
+ constructor() {
15
+ this.netchan = new NetChan();
16
+ this.netchan.setup(1234, { type: 'loopback', port: 0 });
17
+ }
18
+
19
+ reset() {
20
+ this.recordedPackets = [];
21
+ this.sentPackets = [];
22
+ this.netchan.reset();
23
+ }
24
+
25
+ // Simulate receiving data from the "server"
26
+ receive(data: Uint8Array) {
27
+ this.recordedPackets.push({
28
+ type: 'received',
29
+ timestamp: Date.now(),
30
+ data: new Uint8Array(data)
31
+ });
32
+ return this.netchan.process(data);
33
+ }
34
+
35
+ // Simulate sending data (called by the client using this transport)
36
+ transmit(unreliableData?: Uint8Array) {
37
+ const packet = this.netchan.transmit(unreliableData);
38
+ this.recordedPackets.push({
39
+ type: 'sent',
40
+ timestamp: Date.now(),
41
+ data: new Uint8Array(packet)
42
+ });
43
+ this.sentPackets.push(new Uint8Array(packet));
44
+ return packet;
45
+ }
46
+
47
+ get lastSentPacket() {
48
+ return this.sentPackets.length > 0 ? this.sentPackets[this.sentPackets.length - 1] : undefined;
49
+ }
50
+ }
@@ -0,0 +1,93 @@
1
+ import { vi } from 'vitest';
2
+ import { ServerCommand } from '@quake2ts/shared';
3
+
4
+ /**
5
+ * Interface for a mocked server console that can execute commands.
6
+ * Reference: quake2/server/sv_main.c (SV_ExecuteUserCommand)
7
+ */
8
+ export interface MockServerConsole {
9
+ exec(cmd: string): string;
10
+ print(text: string): void;
11
+ broadcast(text: string): void;
12
+ commandBuffer: string[];
13
+ outputBuffer: string[];
14
+ }
15
+
16
+ /**
17
+ * Creates a mock server console.
18
+ * @param overrides Optional overrides for the mock console.
19
+ */
20
+ export function createMockServerConsole(overrides?: Partial<MockServerConsole>): MockServerConsole {
21
+ const outputBuffer: string[] = [];
22
+ const commandBuffer: string[] = [];
23
+
24
+ return {
25
+ exec: vi.fn((cmd: string) => {
26
+ commandBuffer.push(cmd);
27
+ return `Executed: ${cmd}`;
28
+ }),
29
+ print: vi.fn((text: string) => {
30
+ outputBuffer.push(text);
31
+ }),
32
+ broadcast: vi.fn((text: string) => {
33
+ outputBuffer.push(`Broadcast: ${text}`);
34
+ }),
35
+ commandBuffer,
36
+ outputBuffer,
37
+ ...overrides
38
+ };
39
+ }
40
+
41
+ /**
42
+ * Interface for a mocked RCON client.
43
+ * Reference: quake2/server/sv_user.c (SV_ExecuteUserCommand for RCON handling)
44
+ */
45
+ export interface MockRConClient {
46
+ connect(address: string, port: number, password?: string): Promise<boolean>;
47
+ sendCommand(cmd: string): Promise<string>;
48
+ disconnect(): void;
49
+ connected: boolean;
50
+ lastCommand: string;
51
+ lastResponse: string;
52
+ }
53
+
54
+ /**
55
+ * Creates a mock RCON client.
56
+ * @param password Optional password for the client to use.
57
+ */
58
+ export function createMockRConClient(password: string = ''): MockRConClient {
59
+ return {
60
+ connected: false,
61
+ lastCommand: '',
62
+ lastResponse: '',
63
+ connect: vi.fn(async (address, port, pwd) => {
64
+ return pwd === password; // Simple mock: connect succeeds if password matches constructor password
65
+ }),
66
+ sendCommand: vi.fn(async (cmd) => {
67
+ return `RCON Response for: ${cmd}`;
68
+ }),
69
+ disconnect: vi.fn(),
70
+ };
71
+ }
72
+
73
+ /**
74
+ * Simulates a command execution on the server.
75
+ * @param server The mock server instance (assumed to have some command handling capability).
76
+ * @param command The command string to execute.
77
+ * @returns The output of the command.
78
+ */
79
+ export function simulateServerCommand(server: any, command: string): string {
80
+ // This assumes the server has a method to execute commands, or we simulate the effect.
81
+ // In a real integration, this might interact with the server's command buffer.
82
+ if (server.executeBuffer) {
83
+ server.executeBuffer(command);
84
+ return "Executed";
85
+ }
86
+
87
+ // Fallback for mocked servers
88
+ if (server.exec) {
89
+ return server.exec(command);
90
+ }
91
+
92
+ return "Unknown command handler";
93
+ }
@@ -0,0 +1,139 @@
1
+ import { Client, ClientState } from '@quake2ts/server';
2
+ import { createMockServerClient } from './state.js';
3
+
4
+ /**
5
+ * Interface representing connection state for testing.
6
+ */
7
+ export interface Connection {
8
+ state: ClientState;
9
+ address: string;
10
+ challenge: number;
11
+ userInfo: UserInfo;
12
+ }
13
+
14
+ /**
15
+ * Stages of the client connection handshake.
16
+ */
17
+ export enum HandshakeStage {
18
+ None = 0,
19
+ Challenge = 1,
20
+ Connect = 2,
21
+ Info = 3,
22
+ Active = 4
23
+ }
24
+
25
+ /**
26
+ * Interface representing a handshake state.
27
+ */
28
+ export interface Handshake {
29
+ stage: HandshakeStage;
30
+ clientNum: number;
31
+ challenge: number;
32
+ qport: number;
33
+ }
34
+
35
+ /**
36
+ * Interface for UserInfo structure.
37
+ */
38
+ export interface UserInfo {
39
+ name: string;
40
+ skin: string;
41
+ model: string;
42
+ fov: number;
43
+ hand: number;
44
+ rate: number;
45
+ msg: number;
46
+ spectator?: number;
47
+ [key: string]: string | number | undefined;
48
+ }
49
+
50
+ /**
51
+ * Helper to serialize UserInfo to Quake 2 info string format.
52
+ * Format: \key\value\key2\value2
53
+ */
54
+ export function serializeUserInfo(info: UserInfo): string {
55
+ return Object.entries(info).reduce((acc, [key, value]) => {
56
+ if (value !== undefined) {
57
+ return `${acc}\\${key}\\${value}`;
58
+ }
59
+ return acc;
60
+ }, '');
61
+ }
62
+
63
+ /**
64
+ * Creates a mock UserInfo object.
65
+ * @param overrides Optional overrides for the user info.
66
+ */
67
+ export function createMockUserInfo(overrides?: Partial<UserInfo>): UserInfo {
68
+ return {
69
+ name: 'Player',
70
+ skin: 'male/grunt',
71
+ model: 'male/grunt',
72
+ fov: 90,
73
+ hand: 0,
74
+ rate: 25000,
75
+ msg: 1,
76
+ ...overrides
77
+ };
78
+ }
79
+
80
+ /**
81
+ * Creates a mock connection object (Client) with specific state.
82
+ * @param state The client state (default: Connected).
83
+ * @param overrides Optional overrides for the client.
84
+ */
85
+ export function createMockConnection(state: ClientState = ClientState.Connected, overrides?: Partial<Client>): Client {
86
+ return createMockServerClient(0, {
87
+ state,
88
+ userInfo: serializeUserInfo(createMockUserInfo()),
89
+ challenge: Math.floor(Math.random() * 100000),
90
+ ...overrides
91
+ });
92
+ }
93
+
94
+ /**
95
+ * Creates a mock handshake object.
96
+ * @param stage The stage of the handshake (default: None).
97
+ */
98
+ export function createMockHandshake(stage: HandshakeStage = HandshakeStage.None): Handshake {
99
+ return {
100
+ stage,
101
+ clientNum: -1,
102
+ challenge: 0,
103
+ qport: Math.floor(Math.random() * 65536)
104
+ };
105
+ }
106
+
107
+ /**
108
+ * Simulates a handshake between a mock client and server.
109
+ * Note: This is a high-level simulation helper.
110
+ * @param client The mock client connection.
111
+ * @param server The mock server instance.
112
+ * @returns Promise that resolves to true if handshake succeeded.
113
+ */
114
+ export async function simulateHandshake(client: Client, server: any): Promise<boolean> {
115
+ // 1. Get Challenge
116
+ // In a real scenario, client sends getchallenge, server responds with challenge
117
+ const challenge = Math.floor(Math.random() * 100000);
118
+ client.challenge = challenge;
119
+
120
+ // 2. Connect
121
+ // Client sends connect command with qport, challenge, userinfo
122
+ // Server assigns client slot
123
+ if (server && server.clients) {
124
+ // rudimentary check if server has space
125
+ }
126
+
127
+ // 3. Update state to Connected
128
+ client.state = ClientState.Connected;
129
+
130
+ // 4. Send configstrings (usually handled by server)
131
+
132
+ // 5. Client enters game (prespawn)
133
+ // Server sends stuff
134
+
135
+ // 6. Client ready
136
+ client.state = ClientState.Active;
137
+
138
+ return true;
139
+ }
@@ -0,0 +1,97 @@
1
+ import { Client } from '@quake2ts/server';
2
+
3
+ export interface MasterServer {
4
+ registerServer(info: ServerInfo): Promise<boolean>;
5
+ heartbeat(serverAddress: string): Promise<boolean>;
6
+ getServerList(filter?: ServerListFilter): Promise<ServerInfo[]>;
7
+ }
8
+
9
+ export interface ServerInfo {
10
+ address: string;
11
+ name: string;
12
+ map: string;
13
+ players: number;
14
+ maxPlayers: number;
15
+ gametype: string;
16
+ version: string;
17
+ password?: boolean;
18
+ }
19
+
20
+ export interface ServerListFilter {
21
+ gametype?: string;
22
+ map?: string;
23
+ notEmpty?: boolean;
24
+ notFull?: boolean;
25
+ }
26
+
27
+ /**
28
+ * Creates a mock master server for testing server registration and browsing
29
+ * @param overrides - Optional overrides for the master server behavior
30
+ */
31
+ export function createMockMasterServer(overrides: Partial<MasterServer> = {}): MasterServer {
32
+ const servers = new Map<string, ServerInfo>();
33
+
34
+ return {
35
+ registerServer: async (info: ServerInfo): Promise<boolean> => {
36
+ servers.set(info.address, info);
37
+ return true;
38
+ },
39
+ heartbeat: async (serverAddress: string): Promise<boolean> => {
40
+ return servers.has(serverAddress);
41
+ },
42
+ getServerList: async (filter?: ServerListFilter): Promise<ServerInfo[]> => {
43
+ let list = Array.from(servers.values());
44
+
45
+ if (filter) {
46
+ if (filter.gametype) {
47
+ list = list.filter(s => s.gametype === filter.gametype);
48
+ }
49
+ if (filter.map) {
50
+ list = list.filter(s => s.map === filter.map);
51
+ }
52
+ if (filter.notEmpty) {
53
+ list = list.filter(s => s.players > 0);
54
+ }
55
+ if (filter.notFull) {
56
+ list = list.filter(s => s.players < s.maxPlayers);
57
+ }
58
+ }
59
+
60
+ return list;
61
+ },
62
+ ...overrides
63
+ };
64
+ }
65
+
66
+ /**
67
+ * Creates a mock server info object
68
+ * @param overrides - Optional overrides
69
+ */
70
+ export function createMockServerInfo(overrides: Partial<ServerInfo> = {}): ServerInfo {
71
+ return {
72
+ address: '127.0.0.1:27910',
73
+ name: 'Quake 2 Test Server',
74
+ map: 'q2dm1',
75
+ players: 0,
76
+ maxPlayers: 16,
77
+ gametype: 'deathmatch',
78
+ version: '1.0',
79
+ ...overrides
80
+ };
81
+ }
82
+
83
+ /**
84
+ * Simulates the process of a game server registering with the master server
85
+ * @param server - The mock game server instance (typed as any to accept mock)
86
+ * @param master - The mock master server
87
+ */
88
+ export async function simulateServerRegistration(server: any, master: MasterServer): Promise<boolean> {
89
+ const info = createMockServerInfo({
90
+ name: server.name || 'Test Server',
91
+ map: server.mapName || 'q2dm1',
92
+ players: server.clients ? server.clients.filter((c: Client | null) => c && c.state >= 2).length : 0,
93
+ maxPlayers: server.maxClients || 16
94
+ });
95
+
96
+ return master.registerServer(info);
97
+ }
@@ -0,0 +1,32 @@
1
+ import { vi } from 'vitest';
2
+ import { TraceResult } from '@quake2ts/shared';
3
+
4
+ /**
5
+ * Mock interface for CollisionEntityIndex.
6
+ */
7
+ export interface MockCollisionEntityIndex {
8
+ trace: (start: any, mins: any, maxs: any, end: any, passEntity: any, contentMask: number) => TraceResult;
9
+ link: (entity: any) => void;
10
+ unlink: (entity: any) => void;
11
+ gatherTriggerTouches: (entity: any) => any[];
12
+ }
13
+
14
+ /**
15
+ * Creates a mock CollisionEntityIndex.
16
+ * @param overrides Optional overrides for the mock methods.
17
+ */
18
+ export function createMockCollisionEntityIndex(overrides?: Partial<MockCollisionEntityIndex>): MockCollisionEntityIndex {
19
+ return {
20
+ trace: vi.fn().mockReturnValue({
21
+ fraction: 1.0,
22
+ allsolid: false,
23
+ startsolid: false,
24
+ endpos: { x: 0, y: 0, z: 0 },
25
+ entityId: null
26
+ }),
27
+ link: vi.fn(),
28
+ unlink: vi.fn(),
29
+ gatherTriggerTouches: vi.fn().mockReturnValue([]),
30
+ ...overrides
31
+ };
32
+ }
@@ -0,0 +1,162 @@
1
+ import { Server, ServerState, ServerStatic, Client, ClientState } from '@quake2ts/server';
2
+ import { NetDriver, MAX_CONFIGSTRINGS, MAX_EDICTS, EntityState } from '@quake2ts/shared';
3
+ import { Entity } from '@quake2ts/game';
4
+ import { vi } from 'vitest';
5
+ import { createMockNetDriver } from './transport.js';
6
+
7
+ // Define GameState interface locally or import from where it should be.
8
+ // Based on grep, GameStateSnapshot is in @quake2ts/game.
9
+ // But test-utils/src/game/mocks.ts defines a local GameState interface.
10
+ // Let's use that one or define a compatible one here.
11
+ import { GameState } from '../../game/mocks.js';
12
+
13
+ /**
14
+ * Creates a mock server state object.
15
+ * @param overrides Optional overrides for the server state.
16
+ */
17
+ export function createMockServerState(overrides?: Partial<Server>): Server {
18
+ return {
19
+ state: ServerState.Game,
20
+ attractLoop: false,
21
+ loadGame: false,
22
+ startTime: Date.now(),
23
+ time: 0,
24
+ frame: 0,
25
+ name: 'test_map',
26
+ collisionModel: null,
27
+ configStrings: new Array(MAX_CONFIGSTRINGS).fill(''),
28
+ baselines: new Array(MAX_EDICTS).fill(null),
29
+ multicastBuf: new Uint8Array(0),
30
+ ...overrides
31
+ };
32
+ }
33
+
34
+ /**
35
+ * Creates a mock server static object.
36
+ * @param maxClients Maximum number of clients.
37
+ * @param overrides Optional overrides for the server static state.
38
+ */
39
+ export function createMockServerStatic(maxClients: number = 16, overrides?: Partial<ServerStatic>): ServerStatic {
40
+ return {
41
+ initialized: true,
42
+ realTime: Date.now(),
43
+ mapCmd: '',
44
+ spawnCount: 1,
45
+ clients: new Array(maxClients).fill(null),
46
+ lastHeartbeat: 0,
47
+ challenges: [],
48
+ ...overrides
49
+ };
50
+ }
51
+
52
+ /**
53
+ * Creates a mock server client.
54
+ * @param clientNum The client index.
55
+ * @param overrides Optional overrides for the client.
56
+ */
57
+ export function createMockServerClient(clientNum: number, overrides?: Partial<Client>): Client {
58
+ // Create a minimal mock net driver
59
+ const mockNet: NetDriver = createMockNetDriver();
60
+
61
+ return {
62
+ index: clientNum,
63
+ state: ClientState.Connected,
64
+ edict: { index: clientNum + 1 } as Entity,
65
+ net: mockNet,
66
+ netchan: {
67
+ qport: 0,
68
+ remoteAddress: '127.0.0.1',
69
+ incomingSequence: 0,
70
+ outgoingSequence: 0,
71
+ lastReceived: 0,
72
+ process: vi.fn(),
73
+ transmit: vi.fn(),
74
+ writeReliableByte: vi.fn(),
75
+ writeReliableShort: vi.fn(),
76
+ writeReliableLong: vi.fn(),
77
+ writeReliableString: vi.fn(),
78
+ writeReliableData: vi.fn(),
79
+ } as any, // Cast as any because NetChan might be complex to fully mock here
80
+ userInfo: '',
81
+ lastMessage: 0,
82
+ lastCommandTime: 0,
83
+ commandCount: 0,
84
+ messageQueue: [],
85
+ frames: [],
86
+ lastFrame: 0,
87
+ lastPacketEntities: [],
88
+ challenge: 0,
89
+ lastConnect: 0,
90
+ ping: 0,
91
+ rate: 0,
92
+ name: `Client${clientNum}`,
93
+ messageLevel: 0,
94
+ datagram: new Uint8Array(0),
95
+ downloadSize: 0,
96
+ downloadCount: 0,
97
+ commandMsec: 0,
98
+ frameLatency: [],
99
+ messageSize: [],
100
+ suppressCount: 0,
101
+ commandQueue: [],
102
+ lastCmd: {
103
+ msec: 0,
104
+ buttons: 0,
105
+ angles: { x: 0, y: 0, z: 0 },
106
+ forwardmove: 0,
107
+ sidemove: 0,
108
+ upmove: 0,
109
+ sequence: 0,
110
+ lightlevel: 0,
111
+ impulse: 0,
112
+ serverFrame: 0
113
+ },
114
+ ...overrides
115
+ };
116
+ }
117
+
118
+ /**
119
+ * Mock interface for the Server class (DedicatedServer).
120
+ * This allows mocking the server instance itself.
121
+ */
122
+ export interface MockServer {
123
+ start(mapName: string): Promise<void>;
124
+ stop(): void;
125
+ // tick(): void; // runFrame is private in DedicatedServer, usually we simulate via game.frame
126
+ multicast(origin: any, type: any, event: any, ...args: any[]): void;
127
+ unicast(ent: Entity, reliable: boolean, event: any, ...args: any[]): void;
128
+ configstring(index: number, value: string): void;
129
+ kickPlayer(clientId: number): void;
130
+ changeMap(mapName: string): Promise<void>;
131
+ getClient(clientNum: number): Client | null;
132
+ broadcast(message: string): void;
133
+ tick(): void;
134
+ }
135
+
136
+ /**
137
+ * Creates a mock server instance.
138
+ * @param overrides Optional overrides for server methods.
139
+ */
140
+ export function createMockServer(overrides?: Partial<MockServer>): MockServer {
141
+ return {
142
+ start: vi.fn().mockResolvedValue(undefined),
143
+ stop: vi.fn(),
144
+ multicast: vi.fn(),
145
+ unicast: vi.fn(),
146
+ configstring: vi.fn(),
147
+ kickPlayer: vi.fn(),
148
+ changeMap: vi.fn().mockResolvedValue(undefined),
149
+ getClient: vi.fn().mockReturnValue(null),
150
+ broadcast: vi.fn(),
151
+ tick: vi.fn(),
152
+ ...overrides
153
+ };
154
+ }
155
+
156
+ // Re-export GameState from game mocks if needed, or use the one from game/mocks
157
+ // Since we have a createMockGameState in game/mocks.ts, we should probably use that or alias it.
158
+ // The task says "Add createMockGameState() factory".
159
+ // If it is already in game/mocks.ts, we can just export it from there or re-export it here.
160
+ // But to avoid duplication, I will just re-export the one from game mocks if the intention is to have it available under server utils.
161
+
162
+ export { createMockGameState, type GameState } from '../../game/mocks.js';