quake2ts 0.0.561 → 0.0.563
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/packages/client/dist/browser/index.global.js +15 -15
- package/packages/client/dist/browser/index.global.js.map +1 -1
- package/packages/client/dist/cjs/index.cjs +273 -1
- package/packages/client/dist/cjs/index.cjs.map +1 -1
- package/packages/client/dist/esm/index.js +273 -1
- package/packages/client/dist/esm/index.js.map +1 -1
- package/packages/client/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/client/dist/types/net/connection.d.ts +2 -0
- package/packages/client/dist/types/net/connection.d.ts.map +1 -1
- package/packages/server/dist/client.d.ts +51 -0
- package/packages/server/dist/client.js +100 -0
- package/packages/server/dist/dedicated.d.ts +69 -0
- package/packages/server/dist/dedicated.js +1013 -0
- package/packages/server/dist/index.cjs +27 -2
- package/packages/server/dist/index.d.ts +7 -161
- package/packages/server/dist/index.js +26 -2
- package/packages/server/dist/net/nodeWsDriver.d.ts +16 -0
- package/packages/server/dist/net/nodeWsDriver.js +122 -0
- package/packages/server/dist/protocol/player.d.ts +23 -0
- package/packages/server/dist/protocol/player.js +137 -0
- package/packages/server/dist/protocol/write.d.ts +7 -0
- package/packages/server/dist/protocol/write.js +167 -0
- package/packages/server/dist/protocol.d.ts +17 -0
- package/packages/server/dist/protocol.js +71 -0
- package/packages/server/dist/server.d.ts +50 -0
- package/packages/server/dist/server.js +12 -0
- package/packages/server/dist/server.test.d.ts +1 -0
- package/packages/server/dist/server.test.js +69 -0
- package/packages/server/dist/transport.d.ts +7 -0
- package/packages/server/dist/transport.js +1 -0
- package/packages/server/dist/transports/websocket.d.ts +11 -0
- package/packages/server/dist/transports/websocket.js +38 -0
- package/packages/test-utils/dist/index.cjs +1610 -1188
- package/packages/test-utils/dist/index.cjs.map +1 -1
- package/packages/test-utils/dist/index.d.cts +326 -132
- package/packages/test-utils/dist/index.d.ts +326 -132
- package/packages/test-utils/dist/index.js +1596 -1189
- package/packages/test-utils/dist/index.js.map +1 -1
- package/packages/server/dist/index.d.cts +0 -161
|
@@ -32,12 +32,16 @@ var index_exports = {};
|
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
InputInjector: () => InputInjector,
|
|
34
34
|
MockPointerLock: () => MockPointerLock,
|
|
35
|
+
MockTransport: () => MockTransport,
|
|
35
36
|
captureAudioEvents: () => captureAudioEvents,
|
|
36
37
|
captureCanvasDrawCalls: () => captureCanvasDrawCalls,
|
|
38
|
+
captureGameScreenshot: () => captureGameScreenshot,
|
|
37
39
|
captureGameState: () => captureGameState,
|
|
40
|
+
compareScreenshots: () => compareScreenshots,
|
|
38
41
|
createBinaryStreamMock: () => createBinaryStreamMock,
|
|
39
42
|
createBinaryWriterMock: () => createBinaryWriterMock,
|
|
40
43
|
createControlledTimer: () => createControlledTimer,
|
|
44
|
+
createCustomNetworkCondition: () => createCustomNetworkCondition,
|
|
41
45
|
createEntity: () => createEntity,
|
|
42
46
|
createEntityStateFactory: () => createEntityStateFactory,
|
|
43
47
|
createGameStateSnapshotFactory: () => createGameStateSnapshotFactory,
|
|
@@ -46,13 +50,21 @@ __export(index_exports, {
|
|
|
46
50
|
createMockCanvasContext2D: () => createMockCanvasContext2D,
|
|
47
51
|
createMockEngine: () => createMockEngine,
|
|
48
52
|
createMockGame: () => createMockGame,
|
|
53
|
+
createMockGameState: () => createMockGameState,
|
|
49
54
|
createMockImage: () => createMockImage,
|
|
50
55
|
createMockImageData: () => createMockImageData,
|
|
51
56
|
createMockIndexedDB: () => createMockIndexedDB,
|
|
52
57
|
createMockLocalStorage: () => createMockLocalStorage,
|
|
58
|
+
createMockNetworkAddress: () => createMockNetworkAddress,
|
|
53
59
|
createMockPerformance: () => createMockPerformance,
|
|
54
60
|
createMockRAF: () => createMockRAF,
|
|
61
|
+
createMockServer: () => createMockServer,
|
|
62
|
+
createMockServerClient: () => createMockServerClient,
|
|
63
|
+
createMockServerState: () => createMockServerState,
|
|
64
|
+
createMockServerStatic: () => createMockServerStatic,
|
|
55
65
|
createMockSessionStorage: () => createMockSessionStorage,
|
|
66
|
+
createMockTransport: () => createMockTransport,
|
|
67
|
+
createMockUDPSocket: () => createMockUDPSocket,
|
|
56
68
|
createMockWebGL2Context: () => createMockWebGL2Context,
|
|
57
69
|
createNetChanMock: () => createNetChanMock,
|
|
58
70
|
createPlayerStateFactory: () => createPlayerStateFactory,
|
|
@@ -60,6 +72,7 @@ __export(index_exports, {
|
|
|
60
72
|
createSpawnContext: () => createSpawnContext,
|
|
61
73
|
createStorageTestScenario: () => createStorageTestScenario,
|
|
62
74
|
createTestContext: () => createTestContext,
|
|
75
|
+
createVisualTestScenario: () => createVisualTestScenario,
|
|
63
76
|
intersects: () => import_shared3.intersects,
|
|
64
77
|
ladderTrace: () => import_shared3.ladderTrace,
|
|
65
78
|
makeAxisBrush: () => makeAxisBrush,
|
|
@@ -74,1297 +87,1695 @@ __export(index_exports, {
|
|
|
74
87
|
setupNodeEnvironment: () => setupNodeEnvironment,
|
|
75
88
|
simulateFrames: () => simulateFrames,
|
|
76
89
|
simulateFramesWithMock: () => simulateFramesWithMock,
|
|
90
|
+
simulateNetworkCondition: () => simulateNetworkCondition,
|
|
77
91
|
stairTrace: () => import_shared3.stairTrace,
|
|
78
92
|
teardownBrowserEnvironment: () => teardownBrowserEnvironment,
|
|
79
93
|
teardownMockAudioContext: () => teardownMockAudioContext,
|
|
94
|
+
throttleBandwidth: () => throttleBandwidth,
|
|
80
95
|
waitForGameReady: () => waitForGameReady
|
|
81
96
|
});
|
|
82
97
|
module.exports = __toCommonJS(index_exports);
|
|
83
98
|
|
|
84
|
-
// src/
|
|
85
|
-
var
|
|
86
|
-
var
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
bubbles: true,
|
|
174
|
-
cancelable: true,
|
|
175
|
-
view: this.win
|
|
176
|
-
});
|
|
177
|
-
const target = this.doc.pointerLockElement || this.doc;
|
|
178
|
-
target.dispatchEvent(event);
|
|
179
|
-
}
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
// src/setup/webgl.ts
|
|
183
|
-
function createMockWebGL2Context(canvas) {
|
|
184
|
-
const gl = {
|
|
185
|
-
canvas,
|
|
186
|
-
drawingBufferWidth: canvas.width,
|
|
187
|
-
drawingBufferHeight: canvas.height,
|
|
188
|
-
// Constants
|
|
189
|
-
VERTEX_SHADER: 35633,
|
|
190
|
-
FRAGMENT_SHADER: 35632,
|
|
191
|
-
COMPILE_STATUS: 35713,
|
|
192
|
-
LINK_STATUS: 35714,
|
|
193
|
-
ARRAY_BUFFER: 34962,
|
|
194
|
-
ELEMENT_ARRAY_BUFFER: 34963,
|
|
195
|
-
STATIC_DRAW: 35044,
|
|
196
|
-
DYNAMIC_DRAW: 35048,
|
|
197
|
-
FLOAT: 5126,
|
|
198
|
-
DEPTH_TEST: 2929,
|
|
199
|
-
BLEND: 3042,
|
|
200
|
-
SRC_ALPHA: 770,
|
|
201
|
-
ONE_MINUS_SRC_ALPHA: 771,
|
|
202
|
-
TEXTURE_2D: 3553,
|
|
203
|
-
RGBA: 6408,
|
|
204
|
-
UNSIGNED_BYTE: 5121,
|
|
205
|
-
COLOR_BUFFER_BIT: 16384,
|
|
206
|
-
DEPTH_BUFFER_BIT: 256,
|
|
207
|
-
TRIANGLES: 4,
|
|
208
|
-
TRIANGLE_STRIP: 5,
|
|
209
|
-
TRIANGLE_FAN: 6,
|
|
210
|
-
// Methods
|
|
211
|
-
createShader: () => ({}),
|
|
212
|
-
shaderSource: () => {
|
|
213
|
-
},
|
|
214
|
-
compileShader: () => {
|
|
215
|
-
},
|
|
216
|
-
getShaderParameter: (_, param) => {
|
|
217
|
-
if (param === 35713) return true;
|
|
218
|
-
return true;
|
|
219
|
-
},
|
|
220
|
-
getShaderInfoLog: () => "",
|
|
221
|
-
createProgram: () => ({}),
|
|
222
|
-
attachShader: () => {
|
|
223
|
-
},
|
|
224
|
-
linkProgram: () => {
|
|
225
|
-
},
|
|
226
|
-
getProgramParameter: (_, param) => {
|
|
227
|
-
if (param === 35714) return true;
|
|
228
|
-
return true;
|
|
229
|
-
},
|
|
230
|
-
getProgramInfoLog: () => "",
|
|
231
|
-
useProgram: () => {
|
|
232
|
-
},
|
|
233
|
-
createBuffer: () => ({}),
|
|
234
|
-
bindBuffer: () => {
|
|
235
|
-
},
|
|
236
|
-
bufferData: () => {
|
|
237
|
-
},
|
|
238
|
-
enableVertexAttribArray: () => {
|
|
239
|
-
},
|
|
240
|
-
vertexAttribPointer: () => {
|
|
241
|
-
},
|
|
242
|
-
enable: () => {
|
|
243
|
-
},
|
|
244
|
-
disable: () => {
|
|
245
|
-
},
|
|
246
|
-
depthMask: () => {
|
|
247
|
-
},
|
|
248
|
-
blendFunc: () => {
|
|
249
|
-
},
|
|
250
|
-
viewport: () => {
|
|
251
|
-
},
|
|
252
|
-
clearColor: () => {
|
|
253
|
-
},
|
|
254
|
-
clear: () => {
|
|
255
|
-
},
|
|
256
|
-
createTexture: () => ({}),
|
|
257
|
-
bindTexture: () => {
|
|
258
|
-
},
|
|
259
|
-
texImage2D: () => {
|
|
260
|
-
},
|
|
261
|
-
texParameteri: () => {
|
|
262
|
-
},
|
|
263
|
-
activeTexture: () => {
|
|
264
|
-
},
|
|
265
|
-
uniform1i: () => {
|
|
266
|
-
},
|
|
267
|
-
uniform1f: () => {
|
|
268
|
-
},
|
|
269
|
-
uniform2f: () => {
|
|
270
|
-
},
|
|
271
|
-
uniform3f: () => {
|
|
272
|
-
},
|
|
273
|
-
uniform4f: () => {
|
|
274
|
-
},
|
|
275
|
-
uniformMatrix4fv: () => {
|
|
276
|
-
},
|
|
277
|
-
getUniformLocation: () => ({}),
|
|
278
|
-
getAttribLocation: () => 0,
|
|
279
|
-
drawArrays: () => {
|
|
280
|
-
},
|
|
281
|
-
drawElements: () => {
|
|
282
|
-
},
|
|
283
|
-
createVertexArray: () => ({}),
|
|
284
|
-
bindVertexArray: () => {
|
|
285
|
-
},
|
|
286
|
-
deleteShader: () => {
|
|
287
|
-
},
|
|
288
|
-
deleteProgram: () => {
|
|
289
|
-
},
|
|
290
|
-
deleteBuffer: () => {
|
|
291
|
-
},
|
|
292
|
-
deleteTexture: () => {
|
|
293
|
-
},
|
|
294
|
-
deleteVertexArray: () => {
|
|
295
|
-
},
|
|
296
|
-
// WebGL2 specific
|
|
297
|
-
texImage3D: () => {
|
|
298
|
-
},
|
|
299
|
-
uniformBlockBinding: () => {
|
|
300
|
-
},
|
|
301
|
-
getExtension: () => null
|
|
302
|
-
};
|
|
303
|
-
return gl;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// src/setup/browser.ts
|
|
307
|
-
function setupBrowserEnvironment(options = {}) {
|
|
308
|
-
const {
|
|
309
|
-
url = "http://localhost",
|
|
310
|
-
pretendToBeVisual = true,
|
|
311
|
-
resources = void 0,
|
|
312
|
-
enableWebGL2 = false,
|
|
313
|
-
enablePointerLock = false
|
|
314
|
-
} = options;
|
|
315
|
-
const dom = new import_jsdom.JSDOM("<!DOCTYPE html><html><head></head><body></body></html>", {
|
|
316
|
-
url,
|
|
317
|
-
pretendToBeVisual,
|
|
318
|
-
resources
|
|
319
|
-
});
|
|
320
|
-
global.window = dom.window;
|
|
321
|
-
global.document = dom.window.document;
|
|
322
|
-
try {
|
|
323
|
-
global.navigator = dom.window.navigator;
|
|
324
|
-
} catch (e) {
|
|
325
|
-
try {
|
|
326
|
-
Object.defineProperty(global, "navigator", {
|
|
327
|
-
value: dom.window.navigator,
|
|
328
|
-
writable: true,
|
|
329
|
-
configurable: true
|
|
330
|
-
});
|
|
331
|
-
} catch (e2) {
|
|
332
|
-
console.warn("Could not assign global.navigator, skipping.");
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
global.location = dom.window.location;
|
|
336
|
-
global.HTMLElement = dom.window.HTMLElement;
|
|
337
|
-
global.HTMLCanvasElement = dom.window.HTMLCanvasElement;
|
|
338
|
-
global.Event = dom.window.Event;
|
|
339
|
-
global.CustomEvent = dom.window.CustomEvent;
|
|
340
|
-
global.DragEvent = dom.window.DragEvent;
|
|
341
|
-
global.MouseEvent = dom.window.MouseEvent;
|
|
342
|
-
global.KeyboardEvent = dom.window.KeyboardEvent;
|
|
343
|
-
global.FocusEvent = dom.window.FocusEvent;
|
|
344
|
-
global.WheelEvent = dom.window.WheelEvent;
|
|
345
|
-
global.InputEvent = dom.window.InputEvent;
|
|
346
|
-
global.UIEvent = dom.window.UIEvent;
|
|
347
|
-
try {
|
|
348
|
-
global.localStorage = dom.window.localStorage;
|
|
349
|
-
} catch (e) {
|
|
350
|
-
}
|
|
351
|
-
if (!global.localStorage) {
|
|
352
|
-
const storage = /* @__PURE__ */ new Map();
|
|
353
|
-
global.localStorage = {
|
|
354
|
-
getItem: (key) => storage.get(key) || null,
|
|
355
|
-
setItem: (key, value) => storage.set(key, value),
|
|
356
|
-
removeItem: (key) => storage.delete(key),
|
|
357
|
-
clear: () => storage.clear(),
|
|
358
|
-
key: (index) => Array.from(storage.keys())[index] || null,
|
|
359
|
-
get length() {
|
|
360
|
-
return storage.size;
|
|
361
|
-
}
|
|
362
|
-
};
|
|
363
|
-
}
|
|
364
|
-
const originalCreateElement = document.createElement.bind(document);
|
|
365
|
-
document.createElement = function(tagName, options2) {
|
|
366
|
-
if (tagName.toLowerCase() === "canvas") {
|
|
367
|
-
const napiCanvas = new import_canvas.Canvas(300, 150);
|
|
368
|
-
const domCanvas = originalCreateElement("canvas", options2);
|
|
369
|
-
Object.defineProperty(domCanvas, "width", {
|
|
370
|
-
get: () => napiCanvas.width,
|
|
371
|
-
set: (value) => {
|
|
372
|
-
napiCanvas.width = value;
|
|
373
|
-
},
|
|
374
|
-
enumerable: true,
|
|
375
|
-
configurable: true
|
|
376
|
-
});
|
|
377
|
-
Object.defineProperty(domCanvas, "height", {
|
|
378
|
-
get: () => napiCanvas.height,
|
|
379
|
-
set: (value) => {
|
|
380
|
-
napiCanvas.height = value;
|
|
381
|
-
},
|
|
382
|
-
enumerable: true,
|
|
383
|
-
configurable: true
|
|
384
|
-
});
|
|
385
|
-
const originalGetContext = domCanvas.getContext.bind(domCanvas);
|
|
386
|
-
domCanvas.getContext = function(contextId, options3) {
|
|
387
|
-
if (contextId === "2d") {
|
|
388
|
-
return napiCanvas.getContext("2d", options3);
|
|
389
|
-
}
|
|
390
|
-
if (enableWebGL2 && contextId === "webgl2") {
|
|
391
|
-
return createMockWebGL2Context(domCanvas);
|
|
392
|
-
}
|
|
393
|
-
if (contextId === "webgl" || contextId === "webgl2") {
|
|
394
|
-
return originalGetContext(contextId, options3);
|
|
395
|
-
}
|
|
396
|
-
return napiCanvas.getContext(contextId, options3);
|
|
397
|
-
};
|
|
398
|
-
domCanvas.__napiCanvas = napiCanvas;
|
|
399
|
-
return domCanvas;
|
|
400
|
-
}
|
|
401
|
-
return originalCreateElement(tagName, options2);
|
|
99
|
+
// src/shared/mocks.ts
|
|
100
|
+
var import_vitest = require("vitest");
|
|
101
|
+
var createBinaryWriterMock = () => ({
|
|
102
|
+
writeByte: import_vitest.vi.fn(),
|
|
103
|
+
writeShort: import_vitest.vi.fn(),
|
|
104
|
+
writeLong: import_vitest.vi.fn(),
|
|
105
|
+
writeString: import_vitest.vi.fn(),
|
|
106
|
+
writeBytes: import_vitest.vi.fn(),
|
|
107
|
+
getBuffer: import_vitest.vi.fn(() => new Uint8Array(0)),
|
|
108
|
+
reset: import_vitest.vi.fn(),
|
|
109
|
+
// Legacy methods (if any)
|
|
110
|
+
writeInt8: import_vitest.vi.fn(),
|
|
111
|
+
writeUint8: import_vitest.vi.fn(),
|
|
112
|
+
writeInt16: import_vitest.vi.fn(),
|
|
113
|
+
writeUint16: import_vitest.vi.fn(),
|
|
114
|
+
writeInt32: import_vitest.vi.fn(),
|
|
115
|
+
writeUint32: import_vitest.vi.fn(),
|
|
116
|
+
writeFloat: import_vitest.vi.fn(),
|
|
117
|
+
getData: import_vitest.vi.fn(() => new Uint8Array(0))
|
|
118
|
+
});
|
|
119
|
+
var createNetChanMock = () => ({
|
|
120
|
+
qport: 1234,
|
|
121
|
+
// Sequencing
|
|
122
|
+
incomingSequence: 0,
|
|
123
|
+
outgoingSequence: 0,
|
|
124
|
+
incomingAcknowledged: 0,
|
|
125
|
+
// Reliable messaging
|
|
126
|
+
incomingReliableAcknowledged: false,
|
|
127
|
+
incomingReliableSequence: 0,
|
|
128
|
+
outgoingReliableSequence: 0,
|
|
129
|
+
reliableMessage: createBinaryWriterMock(),
|
|
130
|
+
reliableLength: 0,
|
|
131
|
+
// Fragmentation
|
|
132
|
+
fragmentSendOffset: 0,
|
|
133
|
+
fragmentBuffer: null,
|
|
134
|
+
fragmentLength: 0,
|
|
135
|
+
fragmentReceived: 0,
|
|
136
|
+
// Timing
|
|
137
|
+
lastReceived: 0,
|
|
138
|
+
lastSent: 0,
|
|
139
|
+
remoteAddress: { type: "IP", port: 1234 },
|
|
140
|
+
// Methods
|
|
141
|
+
setup: import_vitest.vi.fn(),
|
|
142
|
+
reset: import_vitest.vi.fn(),
|
|
143
|
+
transmit: import_vitest.vi.fn(),
|
|
144
|
+
process: import_vitest.vi.fn(),
|
|
145
|
+
canSendReliable: import_vitest.vi.fn(() => true),
|
|
146
|
+
writeReliableByte: import_vitest.vi.fn(),
|
|
147
|
+
writeReliableShort: import_vitest.vi.fn(),
|
|
148
|
+
writeReliableLong: import_vitest.vi.fn(),
|
|
149
|
+
writeReliableString: import_vitest.vi.fn(),
|
|
150
|
+
getReliableData: import_vitest.vi.fn(() => new Uint8Array(0)),
|
|
151
|
+
needsKeepalive: import_vitest.vi.fn(() => false),
|
|
152
|
+
isTimedOut: import_vitest.vi.fn(() => false)
|
|
153
|
+
});
|
|
154
|
+
var createBinaryStreamMock = () => ({
|
|
155
|
+
getPosition: import_vitest.vi.fn(() => 0),
|
|
156
|
+
getReadPosition: import_vitest.vi.fn(() => 0),
|
|
157
|
+
getLength: import_vitest.vi.fn(() => 0),
|
|
158
|
+
getRemaining: import_vitest.vi.fn(() => 0),
|
|
159
|
+
seek: import_vitest.vi.fn(),
|
|
160
|
+
setReadPosition: import_vitest.vi.fn(),
|
|
161
|
+
hasMore: import_vitest.vi.fn(() => true),
|
|
162
|
+
hasBytes: import_vitest.vi.fn((amount) => true),
|
|
163
|
+
readChar: import_vitest.vi.fn(() => 0),
|
|
164
|
+
readByte: import_vitest.vi.fn(() => 0),
|
|
165
|
+
readShort: import_vitest.vi.fn(() => 0),
|
|
166
|
+
readUShort: import_vitest.vi.fn(() => 0),
|
|
167
|
+
readLong: import_vitest.vi.fn(() => 0),
|
|
168
|
+
readULong: import_vitest.vi.fn(() => 0),
|
|
169
|
+
readFloat: import_vitest.vi.fn(() => 0),
|
|
170
|
+
readString: import_vitest.vi.fn(() => ""),
|
|
171
|
+
readStringLine: import_vitest.vi.fn(() => ""),
|
|
172
|
+
readCoord: import_vitest.vi.fn(() => 0),
|
|
173
|
+
readAngle: import_vitest.vi.fn(() => 0),
|
|
174
|
+
readAngle16: import_vitest.vi.fn(() => 0),
|
|
175
|
+
readData: import_vitest.vi.fn((length) => new Uint8Array(length)),
|
|
176
|
+
readPos: import_vitest.vi.fn(),
|
|
177
|
+
readDir: import_vitest.vi.fn()
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// src/shared/bsp.ts
|
|
181
|
+
var import_shared = require("@quake2ts/shared");
|
|
182
|
+
function makePlane(normal, dist) {
|
|
183
|
+
return {
|
|
184
|
+
normal,
|
|
185
|
+
dist,
|
|
186
|
+
type: Math.abs(normal.x) === 1 ? 0 : Math.abs(normal.y) === 1 ? 1 : Math.abs(normal.z) === 1 ? 2 : 3,
|
|
187
|
+
signbits: (0, import_shared.computePlaneSignBits)(normal)
|
|
402
188
|
};
|
|
403
|
-
if (enableWebGL2) {
|
|
404
|
-
const originalProtoGetContext = global.HTMLCanvasElement.prototype.getContext;
|
|
405
|
-
global.HTMLCanvasElement.prototype.getContext = function(contextId, options2) {
|
|
406
|
-
if (contextId === "webgl2") {
|
|
407
|
-
return createMockWebGL2Context(this);
|
|
408
|
-
}
|
|
409
|
-
return originalProtoGetContext.call(this, contextId, options2);
|
|
410
|
-
};
|
|
411
|
-
}
|
|
412
|
-
global.Image = import_canvas.Image;
|
|
413
|
-
global.ImageData = import_canvas.ImageData;
|
|
414
|
-
if (typeof global.createImageBitmap === "undefined") {
|
|
415
|
-
global.createImageBitmap = async function(image, _options) {
|
|
416
|
-
if (image && typeof image.width === "number" && typeof image.height === "number") {
|
|
417
|
-
const canvas2 = new import_canvas.Canvas(image.width, image.height);
|
|
418
|
-
const ctx = canvas2.getContext("2d");
|
|
419
|
-
if (image.data) {
|
|
420
|
-
ctx.putImageData(image, 0, 0);
|
|
421
|
-
}
|
|
422
|
-
return canvas2;
|
|
423
|
-
}
|
|
424
|
-
const canvas = new import_canvas.Canvas(100, 100);
|
|
425
|
-
return canvas;
|
|
426
|
-
};
|
|
427
|
-
}
|
|
428
|
-
if (typeof global.btoa === "undefined") {
|
|
429
|
-
global.btoa = function(str) {
|
|
430
|
-
return Buffer.from(str, "binary").toString("base64");
|
|
431
|
-
};
|
|
432
|
-
}
|
|
433
|
-
if (typeof global.atob === "undefined") {
|
|
434
|
-
global.atob = function(str) {
|
|
435
|
-
return Buffer.from(str, "base64").toString("binary");
|
|
436
|
-
};
|
|
437
|
-
}
|
|
438
|
-
if (enablePointerLock) {
|
|
439
|
-
MockPointerLock.setup(global.document);
|
|
440
|
-
}
|
|
441
|
-
if (typeof global.requestAnimationFrame === "undefined") {
|
|
442
|
-
let lastTime = 0;
|
|
443
|
-
global.requestAnimationFrame = (callback) => {
|
|
444
|
-
const currTime = Date.now();
|
|
445
|
-
const timeToCall = Math.max(0, 16 - (currTime - lastTime));
|
|
446
|
-
const id = setTimeout(() => {
|
|
447
|
-
callback(currTime + timeToCall);
|
|
448
|
-
}, timeToCall);
|
|
449
|
-
lastTime = currTime + timeToCall;
|
|
450
|
-
return id;
|
|
451
|
-
};
|
|
452
|
-
global.cancelAnimationFrame = (id) => {
|
|
453
|
-
clearTimeout(id);
|
|
454
|
-
};
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
function teardownBrowserEnvironment() {
|
|
458
|
-
delete global.window;
|
|
459
|
-
delete global.document;
|
|
460
|
-
delete global.navigator;
|
|
461
|
-
delete global.localStorage;
|
|
462
|
-
delete global.location;
|
|
463
|
-
delete global.HTMLElement;
|
|
464
|
-
delete global.HTMLCanvasElement;
|
|
465
|
-
delete global.Image;
|
|
466
|
-
delete global.ImageData;
|
|
467
|
-
delete global.createImageBitmap;
|
|
468
|
-
delete global.Event;
|
|
469
|
-
delete global.CustomEvent;
|
|
470
|
-
delete global.DragEvent;
|
|
471
|
-
delete global.MouseEvent;
|
|
472
|
-
delete global.KeyboardEvent;
|
|
473
|
-
delete global.FocusEvent;
|
|
474
|
-
delete global.WheelEvent;
|
|
475
|
-
delete global.InputEvent;
|
|
476
|
-
delete global.UIEvent;
|
|
477
189
|
}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
if (contextId === "webgl2") {
|
|
492
|
-
return createMockWebGL2Context(canvas);
|
|
493
|
-
}
|
|
494
|
-
if (contextId === "2d") {
|
|
495
|
-
return originalGetContext("2d", options);
|
|
496
|
-
}
|
|
497
|
-
return originalGetContext(contextId, options);
|
|
190
|
+
function makeAxisBrush(size, contents = import_shared.CONTENTS_SOLID) {
|
|
191
|
+
const half = size / 2;
|
|
192
|
+
const planes = [
|
|
193
|
+
makePlane({ x: 1, y: 0, z: 0 }, half),
|
|
194
|
+
makePlane({ x: -1, y: 0, z: 0 }, half),
|
|
195
|
+
makePlane({ x: 0, y: 1, z: 0 }, half),
|
|
196
|
+
makePlane({ x: 0, y: -1, z: 0 }, half),
|
|
197
|
+
makePlane({ x: 0, y: 0, z: 1 }, half),
|
|
198
|
+
makePlane({ x: 0, y: 0, z: -1 }, half)
|
|
199
|
+
];
|
|
200
|
+
return {
|
|
201
|
+
contents,
|
|
202
|
+
sides: planes.map((plane) => ({ plane, surfaceFlags: 0 }))
|
|
498
203
|
};
|
|
499
|
-
return canvas;
|
|
500
204
|
}
|
|
501
|
-
function
|
|
502
|
-
|
|
503
|
-
canvas = createMockCanvas();
|
|
504
|
-
}
|
|
505
|
-
return canvas.getContext("2d");
|
|
205
|
+
function makeNode(plane, children) {
|
|
206
|
+
return { plane, children };
|
|
506
207
|
}
|
|
507
|
-
function
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
"beginPath",
|
|
517
|
-
"closePath",
|
|
518
|
-
"moveTo",
|
|
519
|
-
"lineTo",
|
|
520
|
-
"arc",
|
|
521
|
-
"arcTo",
|
|
522
|
-
"bezierCurveTo",
|
|
523
|
-
"quadraticCurveTo",
|
|
524
|
-
"stroke",
|
|
525
|
-
"fill",
|
|
526
|
-
"putImageData"
|
|
527
|
-
];
|
|
528
|
-
methodsToSpy.forEach((method) => {
|
|
529
|
-
const original = context[method];
|
|
530
|
-
if (typeof original === "function") {
|
|
531
|
-
context[method] = function(...args) {
|
|
532
|
-
drawCalls.push({ method, args });
|
|
533
|
-
return original.apply(this, args);
|
|
534
|
-
};
|
|
535
|
-
}
|
|
536
|
-
});
|
|
537
|
-
return drawCalls;
|
|
208
|
+
function makeBspModel(planes, nodes, leaves, brushes, leafBrushes) {
|
|
209
|
+
return {
|
|
210
|
+
planes,
|
|
211
|
+
nodes,
|
|
212
|
+
leaves,
|
|
213
|
+
brushes,
|
|
214
|
+
leafBrushes,
|
|
215
|
+
bmodels: []
|
|
216
|
+
};
|
|
538
217
|
}
|
|
539
|
-
function
|
|
540
|
-
|
|
541
|
-
if (fillColor) {
|
|
542
|
-
const [r, g, b, a] = fillColor;
|
|
543
|
-
for (let i = 0; i < imageData.data.length; i += 4) {
|
|
544
|
-
imageData.data[i] = r;
|
|
545
|
-
imageData.data[i + 1] = g;
|
|
546
|
-
imageData.data[i + 2] = b;
|
|
547
|
-
imageData.data[i + 3] = a;
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
return imageData;
|
|
218
|
+
function makeLeaf(contents, firstLeafBrush, numLeafBrushes) {
|
|
219
|
+
return { contents, cluster: 0, area: 0, firstLeafBrush, numLeafBrushes };
|
|
551
220
|
}
|
|
552
|
-
function
|
|
553
|
-
const
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
221
|
+
function makeLeafModel(brushes) {
|
|
222
|
+
const planes = brushes.flatMap((brush) => brush.sides.map((side) => side.plane));
|
|
223
|
+
return {
|
|
224
|
+
planes,
|
|
225
|
+
nodes: [],
|
|
226
|
+
leaves: [makeLeaf(0, 0, brushes.length)],
|
|
227
|
+
brushes,
|
|
228
|
+
leafBrushes: brushes.map((_, i) => i),
|
|
229
|
+
bmodels: []
|
|
230
|
+
};
|
|
558
231
|
}
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
232
|
+
function makeBrushFromMinsMaxs(mins, maxs, contents = import_shared.CONTENTS_SOLID) {
|
|
233
|
+
const planes = [
|
|
234
|
+
makePlane({ x: 1, y: 0, z: 0 }, maxs.x),
|
|
235
|
+
makePlane({ x: -1, y: 0, z: 0 }, -mins.x),
|
|
236
|
+
makePlane({ x: 0, y: 1, z: 0 }, maxs.y),
|
|
237
|
+
makePlane({ x: 0, y: -1, z: 0 }, -mins.y),
|
|
238
|
+
makePlane({ x: 0, y: 0, z: 1 }, maxs.z),
|
|
239
|
+
makePlane({ x: 0, y: 0, z: -1 }, -mins.z)
|
|
240
|
+
];
|
|
241
|
+
return {
|
|
242
|
+
contents,
|
|
243
|
+
sides: planes.map((plane) => ({ plane, surfaceFlags: 0 }))
|
|
244
|
+
};
|
|
564
245
|
}
|
|
565
246
|
|
|
566
|
-
// src/
|
|
567
|
-
var
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
247
|
+
// src/game/factories.ts
|
|
248
|
+
var createPlayerStateFactory = (overrides) => ({
|
|
249
|
+
pm_type: 0,
|
|
250
|
+
pm_time: 0,
|
|
251
|
+
pm_flags: 0,
|
|
252
|
+
origin: { x: 0, y: 0, z: 0 },
|
|
253
|
+
velocity: { x: 0, y: 0, z: 0 },
|
|
254
|
+
viewAngles: { x: 0, y: 0, z: 0 },
|
|
255
|
+
onGround: false,
|
|
256
|
+
waterLevel: 0,
|
|
257
|
+
watertype: 0,
|
|
258
|
+
mins: { x: 0, y: 0, z: 0 },
|
|
259
|
+
maxs: { x: 0, y: 0, z: 0 },
|
|
260
|
+
damageAlpha: 0,
|
|
261
|
+
damageIndicators: [],
|
|
262
|
+
blend: [0, 0, 0, 0],
|
|
263
|
+
stats: [],
|
|
264
|
+
kick_angles: { x: 0, y: 0, z: 0 },
|
|
265
|
+
kick_origin: { x: 0, y: 0, z: 0 },
|
|
266
|
+
gunoffset: { x: 0, y: 0, z: 0 },
|
|
267
|
+
gunangles: { x: 0, y: 0, z: 0 },
|
|
268
|
+
gunindex: 0,
|
|
269
|
+
gun_frame: 0,
|
|
270
|
+
rdflags: 0,
|
|
271
|
+
fov: 90,
|
|
272
|
+
renderfx: 0,
|
|
273
|
+
...overrides
|
|
274
|
+
});
|
|
275
|
+
var createEntityStateFactory = (overrides) => ({
|
|
276
|
+
number: 0,
|
|
277
|
+
origin: { x: 0, y: 0, z: 0 },
|
|
278
|
+
angles: { x: 0, y: 0, z: 0 },
|
|
279
|
+
oldOrigin: { x: 0, y: 0, z: 0 },
|
|
280
|
+
modelIndex: 0,
|
|
281
|
+
modelIndex2: 0,
|
|
282
|
+
modelIndex3: 0,
|
|
283
|
+
modelIndex4: 0,
|
|
284
|
+
frame: 0,
|
|
285
|
+
skinNum: 0,
|
|
286
|
+
effects: 0,
|
|
287
|
+
renderfx: 0,
|
|
288
|
+
solid: 0,
|
|
289
|
+
sound: 0,
|
|
290
|
+
event: 0,
|
|
291
|
+
...overrides
|
|
292
|
+
});
|
|
293
|
+
var createGameStateSnapshotFactory = (overrides) => ({
|
|
294
|
+
gravity: { x: 0, y: 0, z: -800 },
|
|
295
|
+
origin: { x: 0, y: 0, z: 0 },
|
|
296
|
+
velocity: { x: 0, y: 0, z: 0 },
|
|
297
|
+
viewangles: { x: 0, y: 0, z: 0 },
|
|
298
|
+
level: { timeSeconds: 0, frameNumber: 0, previousTimeSeconds: 0, deltaSeconds: 0.1 },
|
|
299
|
+
entities: {
|
|
300
|
+
activeCount: 0,
|
|
301
|
+
worldClassname: "worldspawn"
|
|
302
|
+
},
|
|
303
|
+
packetEntities: [],
|
|
304
|
+
pmFlags: 0,
|
|
305
|
+
pmType: 0,
|
|
306
|
+
waterlevel: 0,
|
|
307
|
+
watertype: 0,
|
|
308
|
+
deltaAngles: { x: 0, y: 0, z: 0 },
|
|
309
|
+
health: 100,
|
|
310
|
+
armor: 0,
|
|
311
|
+
ammo: 0,
|
|
312
|
+
blend: [0, 0, 0, 0],
|
|
313
|
+
damageAlpha: 0,
|
|
314
|
+
damageIndicators: [],
|
|
315
|
+
stats: [],
|
|
316
|
+
kick_angles: { x: 0, y: 0, z: 0 },
|
|
317
|
+
kick_origin: { x: 0, y: 0, z: 0 },
|
|
318
|
+
gunoffset: { x: 0, y: 0, z: 0 },
|
|
319
|
+
gunangles: { x: 0, y: 0, z: 0 },
|
|
320
|
+
gunindex: 0,
|
|
321
|
+
pm_time: 0,
|
|
322
|
+
gun_frame: 0,
|
|
323
|
+
rdflags: 0,
|
|
324
|
+
fov: 90,
|
|
325
|
+
renderfx: 0,
|
|
326
|
+
pm_flags: 0,
|
|
327
|
+
pm_type: 0,
|
|
328
|
+
...overrides
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
// src/game/helpers.ts
|
|
332
|
+
var import_vitest2 = require("vitest");
|
|
333
|
+
var import_game = require("@quake2ts/game");
|
|
334
|
+
var import_shared2 = require("@quake2ts/shared");
|
|
335
|
+
var import_shared3 = require("@quake2ts/shared");
|
|
336
|
+
var createMockEngine = () => ({
|
|
337
|
+
sound: import_vitest2.vi.fn(),
|
|
338
|
+
soundIndex: import_vitest2.vi.fn((sound) => 0),
|
|
339
|
+
modelIndex: import_vitest2.vi.fn((model) => 0),
|
|
340
|
+
centerprintf: import_vitest2.vi.fn()
|
|
341
|
+
});
|
|
342
|
+
var createMockGame = (seed = 12345) => {
|
|
343
|
+
const spawnRegistry = new import_game.SpawnRegistry();
|
|
344
|
+
const hooks = new import_game.ScriptHookRegistry();
|
|
345
|
+
const game = {
|
|
346
|
+
random: (0, import_shared2.createRandomGenerator)({ seed }),
|
|
347
|
+
registerEntitySpawn: import_vitest2.vi.fn((classname, spawnFunc) => {
|
|
348
|
+
spawnRegistry.register(classname, (entity) => spawnFunc(entity));
|
|
349
|
+
}),
|
|
350
|
+
unregisterEntitySpawn: import_vitest2.vi.fn((classname) => {
|
|
351
|
+
spawnRegistry.unregister(classname);
|
|
352
|
+
}),
|
|
353
|
+
getCustomEntities: import_vitest2.vi.fn(() => Array.from(spawnRegistry.keys())),
|
|
354
|
+
hooks,
|
|
355
|
+
registerHooks: import_vitest2.vi.fn((newHooks) => hooks.register(newHooks)),
|
|
356
|
+
spawnWorld: import_vitest2.vi.fn(() => {
|
|
357
|
+
hooks.onMapLoad("q2dm1");
|
|
358
|
+
}),
|
|
359
|
+
clientBegin: import_vitest2.vi.fn((client) => {
|
|
360
|
+
hooks.onPlayerSpawn({});
|
|
361
|
+
}),
|
|
362
|
+
damage: import_vitest2.vi.fn((amount) => {
|
|
363
|
+
hooks.onDamage({}, null, null, amount, 0, 0);
|
|
364
|
+
})
|
|
600
365
|
};
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
366
|
+
return { game, spawnRegistry };
|
|
367
|
+
};
|
|
368
|
+
function createTestContext(options) {
|
|
369
|
+
const engine = createMockEngine();
|
|
370
|
+
const seed = options?.seed ?? 12345;
|
|
371
|
+
const { game, spawnRegistry } = createMockGame(seed);
|
|
372
|
+
const traceFn = import_vitest2.vi.fn((start, end, mins, maxs) => ({
|
|
373
|
+
fraction: 1,
|
|
374
|
+
ent: null,
|
|
375
|
+
allsolid: false,
|
|
376
|
+
startsolid: false,
|
|
377
|
+
endpos: end,
|
|
378
|
+
plane: { normal: { x: 0, y: 0, z: 1 }, dist: 0 },
|
|
379
|
+
surfaceFlags: 0,
|
|
380
|
+
contents: 0
|
|
381
|
+
}));
|
|
382
|
+
const entityList = options?.initialEntities ? [...options.initialEntities] : [];
|
|
383
|
+
const hooks = game.hooks;
|
|
384
|
+
const entities = {
|
|
385
|
+
spawn: import_vitest2.vi.fn(() => {
|
|
386
|
+
const ent = new import_game.Entity(entityList.length + 1);
|
|
387
|
+
entityList.push(ent);
|
|
388
|
+
hooks.onEntitySpawn(ent);
|
|
389
|
+
return ent;
|
|
611
390
|
}),
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
},
|
|
619
|
-
frequency: { value: 440 }
|
|
391
|
+
free: import_vitest2.vi.fn((ent) => {
|
|
392
|
+
const idx = entityList.indexOf(ent);
|
|
393
|
+
if (idx !== -1) {
|
|
394
|
+
entityList.splice(idx, 1);
|
|
395
|
+
}
|
|
396
|
+
hooks.onEntityRemove(ent);
|
|
620
397
|
}),
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
},
|
|
628
|
-
buffer: null,
|
|
629
|
-
playbackRate: { value: 1 },
|
|
630
|
-
loop: false
|
|
398
|
+
finalizeSpawn: import_vitest2.vi.fn(),
|
|
399
|
+
freeImmediate: import_vitest2.vi.fn((ent) => {
|
|
400
|
+
const idx = entityList.indexOf(ent);
|
|
401
|
+
if (idx !== -1) {
|
|
402
|
+
entityList.splice(idx, 1);
|
|
403
|
+
}
|
|
631
404
|
}),
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
405
|
+
setSpawnRegistry: import_vitest2.vi.fn(),
|
|
406
|
+
timeSeconds: 10,
|
|
407
|
+
deltaSeconds: 0.1,
|
|
408
|
+
modelIndex: import_vitest2.vi.fn(() => 0),
|
|
409
|
+
scheduleThink: import_vitest2.vi.fn((entity, time) => {
|
|
410
|
+
entity.nextthink = time;
|
|
411
|
+
}),
|
|
412
|
+
linkentity: import_vitest2.vi.fn(),
|
|
413
|
+
trace: traceFn,
|
|
414
|
+
pointcontents: import_vitest2.vi.fn(() => 0),
|
|
415
|
+
multicast: import_vitest2.vi.fn(),
|
|
416
|
+
unicast: import_vitest2.vi.fn(),
|
|
417
|
+
engine,
|
|
418
|
+
game,
|
|
419
|
+
sound: import_vitest2.vi.fn((ent, chan, sound, vol, attn, timeofs) => {
|
|
420
|
+
engine.sound(ent, chan, sound, vol, attn, timeofs);
|
|
421
|
+
}),
|
|
422
|
+
soundIndex: import_vitest2.vi.fn((sound) => engine.soundIndex(sound)),
|
|
423
|
+
useTargets: import_vitest2.vi.fn((entity, activator) => {
|
|
424
|
+
}),
|
|
425
|
+
findByTargetName: import_vitest2.vi.fn(() => []),
|
|
426
|
+
pickTarget: import_vitest2.vi.fn(() => null),
|
|
427
|
+
killBox: import_vitest2.vi.fn(),
|
|
428
|
+
rng: (0, import_shared2.createRandomGenerator)({ seed }),
|
|
429
|
+
imports: {
|
|
430
|
+
configstring: import_vitest2.vi.fn(),
|
|
431
|
+
trace: traceFn,
|
|
432
|
+
pointcontents: import_vitest2.vi.fn(() => 0)
|
|
638
433
|
},
|
|
639
|
-
|
|
434
|
+
level: {
|
|
435
|
+
intermission_angle: { x: 0, y: 0, z: 0 },
|
|
436
|
+
intermission_origin: { x: 0, y: 0, z: 0 },
|
|
437
|
+
next_auto_save: 0,
|
|
438
|
+
health_bar_entities: null
|
|
640
439
|
},
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
sampleRate: 44100,
|
|
645
|
-
numberOfChannels: 2,
|
|
646
|
-
getChannelData: () => new Float32Array(44100)
|
|
440
|
+
targetNameIndex: /* @__PURE__ */ new Map(),
|
|
441
|
+
forEachEntity: import_vitest2.vi.fn((callback) => {
|
|
442
|
+
entityList.forEach(callback);
|
|
647
443
|
}),
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
444
|
+
find: import_vitest2.vi.fn((predicate) => {
|
|
445
|
+
return entityList.find(predicate);
|
|
446
|
+
}),
|
|
447
|
+
findByClassname: import_vitest2.vi.fn((classname) => {
|
|
448
|
+
return entityList.find((e) => e.classname === classname);
|
|
449
|
+
}),
|
|
450
|
+
beginFrame: import_vitest2.vi.fn((timeSeconds) => {
|
|
451
|
+
entities.timeSeconds = timeSeconds;
|
|
452
|
+
}),
|
|
453
|
+
targetAwareness: {
|
|
454
|
+
timeSeconds: 10,
|
|
455
|
+
frameNumber: 1,
|
|
456
|
+
sightEntity: null,
|
|
457
|
+
soundEntity: null
|
|
458
|
+
},
|
|
459
|
+
// Adding missing properties to satisfy EntitySystem interface partially or fully
|
|
460
|
+
// We cast to unknown first anyway, but filling these in makes it safer for consumers
|
|
461
|
+
skill: 1,
|
|
462
|
+
deathmatch: false,
|
|
463
|
+
coop: false,
|
|
464
|
+
activeCount: entityList.length,
|
|
465
|
+
world: entityList.find((e) => e.classname === "worldspawn") || new import_game.Entity(0)
|
|
466
|
+
// ... other EntitySystem properties would go here
|
|
467
|
+
};
|
|
468
|
+
return {
|
|
469
|
+
keyValues: {},
|
|
470
|
+
entities,
|
|
471
|
+
game,
|
|
472
|
+
engine,
|
|
473
|
+
health_multiplier: 1,
|
|
474
|
+
warn: import_vitest2.vi.fn(),
|
|
475
|
+
free: import_vitest2.vi.fn(),
|
|
476
|
+
// Mock precache functions if they are part of SpawnContext in future or TestContext extensions
|
|
477
|
+
precacheModel: import_vitest2.vi.fn(),
|
|
478
|
+
precacheSound: import_vitest2.vi.fn(),
|
|
479
|
+
precacheImage: import_vitest2.vi.fn()
|
|
655
480
|
};
|
|
656
481
|
}
|
|
657
|
-
function
|
|
658
|
-
|
|
659
|
-
global.AudioContext = class {
|
|
660
|
-
constructor() {
|
|
661
|
-
return createMockAudioContext();
|
|
662
|
-
}
|
|
663
|
-
};
|
|
664
|
-
global.window.AudioContext = global.AudioContext;
|
|
665
|
-
global.window.webkitAudioContext = global.AudioContext;
|
|
666
|
-
}
|
|
482
|
+
function createSpawnContext() {
|
|
483
|
+
return createTestContext();
|
|
667
484
|
}
|
|
668
|
-
function
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
485
|
+
function createEntity() {
|
|
486
|
+
return new import_game.Entity(1);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// src/game/mocks.ts
|
|
490
|
+
function createMockGameState(overrides) {
|
|
491
|
+
return {
|
|
492
|
+
levelName: "test_level",
|
|
493
|
+
time: 0,
|
|
494
|
+
entities: [],
|
|
495
|
+
clients: [],
|
|
496
|
+
...overrides
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// src/server/mocks/transport.ts
|
|
501
|
+
var import_vitest3 = require("vitest");
|
|
502
|
+
var MockTransport = class {
|
|
503
|
+
constructor() {
|
|
504
|
+
this.address = "127.0.0.1";
|
|
505
|
+
this.port = 27910;
|
|
506
|
+
this.sentMessages = [];
|
|
507
|
+
this.receivedMessages = [];
|
|
508
|
+
this.listening = false;
|
|
509
|
+
this.listenSpy = import_vitest3.vi.fn().mockImplementation(async (port) => {
|
|
510
|
+
this.port = port;
|
|
511
|
+
this.listening = true;
|
|
512
|
+
});
|
|
513
|
+
this.closeSpy = import_vitest3.vi.fn().mockImplementation(() => {
|
|
514
|
+
this.listening = false;
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Start listening on the specified port.
|
|
519
|
+
*/
|
|
520
|
+
async listen(port) {
|
|
521
|
+
return this.listenSpy(port);
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Close the transport.
|
|
525
|
+
*/
|
|
526
|
+
close() {
|
|
527
|
+
this.closeSpy();
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Register a callback for new connections.
|
|
531
|
+
*/
|
|
532
|
+
onConnection(callback) {
|
|
533
|
+
this.onConnectionCallback = callback;
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Register a callback for errors.
|
|
537
|
+
*/
|
|
538
|
+
onError(callback) {
|
|
539
|
+
this.onErrorCallback = callback;
|
|
673
540
|
}
|
|
541
|
+
/**
|
|
542
|
+
* Check if the transport is currently listening.
|
|
543
|
+
*/
|
|
544
|
+
isListening() {
|
|
545
|
+
return this.listening;
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Helper to simulate a new connection.
|
|
549
|
+
* @param driver The network driver for the connection.
|
|
550
|
+
* @param info Optional connection info.
|
|
551
|
+
*/
|
|
552
|
+
simulateConnection(driver, info) {
|
|
553
|
+
if (this.onConnectionCallback) {
|
|
554
|
+
this.onConnectionCallback(driver, info);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Helper to simulate an error.
|
|
559
|
+
* @param error The error to simulate.
|
|
560
|
+
*/
|
|
561
|
+
simulateError(error) {
|
|
562
|
+
if (this.onErrorCallback) {
|
|
563
|
+
this.onErrorCallback(error);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
};
|
|
567
|
+
function createMockUDPSocket(overrides) {
|
|
568
|
+
const socket = {
|
|
569
|
+
send: import_vitest3.vi.fn(),
|
|
570
|
+
on: import_vitest3.vi.fn(),
|
|
571
|
+
close: import_vitest3.vi.fn(),
|
|
572
|
+
bind: import_vitest3.vi.fn(),
|
|
573
|
+
address: import_vitest3.vi.fn().mockReturnValue({ address: "127.0.0.1", family: "IPv4", port: 0 }),
|
|
574
|
+
...overrides
|
|
575
|
+
};
|
|
576
|
+
return socket;
|
|
674
577
|
}
|
|
675
|
-
function
|
|
676
|
-
return
|
|
578
|
+
function createMockNetworkAddress(ip = "127.0.0.1", port = 27910) {
|
|
579
|
+
return { ip, port };
|
|
580
|
+
}
|
|
581
|
+
function createMockTransport(address = "127.0.0.1", port = 27910, overrides) {
|
|
582
|
+
const transport = new MockTransport();
|
|
583
|
+
transport.address = address;
|
|
584
|
+
transport.port = port;
|
|
585
|
+
Object.assign(transport, overrides);
|
|
586
|
+
return transport;
|
|
677
587
|
}
|
|
678
588
|
|
|
679
|
-
// src/
|
|
680
|
-
var
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
589
|
+
// src/server/mocks/state.ts
|
|
590
|
+
var import_server = require("@quake2ts/server");
|
|
591
|
+
var import_shared4 = require("@quake2ts/shared");
|
|
592
|
+
var import_vitest4 = require("vitest");
|
|
593
|
+
function createMockServerState(overrides) {
|
|
594
|
+
return {
|
|
595
|
+
state: import_server.ServerState.Game,
|
|
596
|
+
attractLoop: false,
|
|
597
|
+
loadGame: false,
|
|
598
|
+
startTime: Date.now(),
|
|
599
|
+
time: 0,
|
|
600
|
+
frame: 0,
|
|
601
|
+
name: "test_map",
|
|
602
|
+
collisionModel: null,
|
|
603
|
+
configStrings: new Array(import_shared4.MAX_CONFIGSTRINGS).fill(""),
|
|
604
|
+
baselines: new Array(import_shared4.MAX_EDICTS).fill(null),
|
|
605
|
+
multicastBuf: new Uint8Array(0),
|
|
606
|
+
...overrides
|
|
691
607
|
};
|
|
692
|
-
|
|
693
|
-
|
|
608
|
+
}
|
|
609
|
+
function createMockServerStatic(maxClients = 16, overrides) {
|
|
610
|
+
return {
|
|
611
|
+
initialized: true,
|
|
612
|
+
realTime: Date.now(),
|
|
613
|
+
mapCmd: "",
|
|
614
|
+
spawnCount: 1,
|
|
615
|
+
clients: new Array(maxClients).fill(null),
|
|
616
|
+
lastHeartbeat: 0,
|
|
617
|
+
challenges: [],
|
|
618
|
+
...overrides
|
|
694
619
|
};
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
620
|
+
}
|
|
621
|
+
function createMockServerClient(clientNum, overrides) {
|
|
622
|
+
const mockNet = {
|
|
623
|
+
connect: import_vitest4.vi.fn(),
|
|
624
|
+
disconnect: import_vitest4.vi.fn(),
|
|
625
|
+
send: import_vitest4.vi.fn(),
|
|
626
|
+
onMessage: import_vitest4.vi.fn(),
|
|
627
|
+
onClose: import_vitest4.vi.fn(),
|
|
628
|
+
onError: import_vitest4.vi.fn(),
|
|
629
|
+
isConnected: import_vitest4.vi.fn().mockReturnValue(true)
|
|
630
|
+
};
|
|
631
|
+
return {
|
|
632
|
+
index: clientNum,
|
|
633
|
+
state: import_server.ClientState.Connected,
|
|
634
|
+
edict: { index: clientNum + 1 },
|
|
635
|
+
net: mockNet,
|
|
636
|
+
netchan: {
|
|
637
|
+
qport: 0,
|
|
638
|
+
remoteAddress: "127.0.0.1",
|
|
639
|
+
incomingSequence: 0,
|
|
640
|
+
outgoingSequence: 0,
|
|
641
|
+
lastReceived: 0,
|
|
642
|
+
process: import_vitest4.vi.fn(),
|
|
643
|
+
transmit: import_vitest4.vi.fn(),
|
|
644
|
+
writeReliableByte: import_vitest4.vi.fn(),
|
|
645
|
+
writeReliableShort: import_vitest4.vi.fn(),
|
|
646
|
+
writeReliableLong: import_vitest4.vi.fn(),
|
|
647
|
+
writeReliableString: import_vitest4.vi.fn(),
|
|
648
|
+
writeReliableData: import_vitest4.vi.fn()
|
|
649
|
+
},
|
|
650
|
+
// Cast as any because NetChan might be complex to fully mock here
|
|
651
|
+
userInfo: "",
|
|
652
|
+
lastMessage: 0,
|
|
653
|
+
lastCommandTime: 0,
|
|
654
|
+
commandCount: 0,
|
|
655
|
+
messageQueue: [],
|
|
656
|
+
frames: [],
|
|
657
|
+
lastFrame: 0,
|
|
658
|
+
lastPacketEntities: [],
|
|
659
|
+
challenge: 0,
|
|
660
|
+
lastConnect: 0,
|
|
661
|
+
ping: 0,
|
|
662
|
+
rate: 0,
|
|
663
|
+
name: `Client${clientNum}`,
|
|
664
|
+
messageLevel: 0,
|
|
665
|
+
datagram: new Uint8Array(0),
|
|
666
|
+
downloadSize: 0,
|
|
667
|
+
downloadCount: 0,
|
|
668
|
+
commandMsec: 0,
|
|
669
|
+
frameLatency: [],
|
|
670
|
+
messageSize: [],
|
|
671
|
+
suppressCount: 0,
|
|
672
|
+
commandQueue: [],
|
|
673
|
+
lastCmd: {
|
|
674
|
+
msec: 0,
|
|
675
|
+
buttons: 0,
|
|
676
|
+
angles: { x: 0, y: 0, z: 0 },
|
|
677
|
+
forwardmove: 0,
|
|
678
|
+
sidemove: 0,
|
|
679
|
+
upmove: 0,
|
|
680
|
+
sequence: 0,
|
|
681
|
+
lightlevel: 0,
|
|
682
|
+
impulse: 0,
|
|
683
|
+
serverFrame: 0
|
|
684
|
+
},
|
|
685
|
+
...overrides
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
function createMockServer(overrides) {
|
|
689
|
+
return {
|
|
690
|
+
start: import_vitest4.vi.fn().mockResolvedValue(void 0),
|
|
691
|
+
stop: import_vitest4.vi.fn(),
|
|
692
|
+
multicast: import_vitest4.vi.fn(),
|
|
693
|
+
unicast: import_vitest4.vi.fn(),
|
|
694
|
+
configstring: import_vitest4.vi.fn(),
|
|
695
|
+
kickPlayer: import_vitest4.vi.fn(),
|
|
696
|
+
changeMap: import_vitest4.vi.fn().mockResolvedValue(void 0),
|
|
697
|
+
...overrides
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// src/setup/browser.ts
|
|
702
|
+
var import_jsdom = require("jsdom");
|
|
703
|
+
var import_canvas = require("@napi-rs/canvas");
|
|
704
|
+
var import_auto = require("fake-indexeddb/auto");
|
|
705
|
+
|
|
706
|
+
// src/e2e/input.ts
|
|
707
|
+
var MockPointerLock = class {
|
|
708
|
+
static setup(doc) {
|
|
709
|
+
let _pointerLockElement = null;
|
|
710
|
+
Object.defineProperty(doc, "pointerLockElement", {
|
|
711
|
+
get: () => _pointerLockElement,
|
|
712
|
+
configurable: true
|
|
713
|
+
});
|
|
714
|
+
doc.exitPointerLock = () => {
|
|
715
|
+
if (_pointerLockElement) {
|
|
716
|
+
_pointerLockElement = null;
|
|
717
|
+
doc.dispatchEvent(new Event("pointerlockchange"));
|
|
701
718
|
}
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
719
|
+
};
|
|
720
|
+
global.HTMLElement.prototype.requestPointerLock = function() {
|
|
721
|
+
_pointerLockElement = this;
|
|
722
|
+
doc.dispatchEvent(new Event("pointerlockchange"));
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
var InputInjector = class {
|
|
727
|
+
constructor(doc, win) {
|
|
728
|
+
this.doc = doc;
|
|
729
|
+
this.win = win;
|
|
730
|
+
}
|
|
731
|
+
keyDown(key, code) {
|
|
732
|
+
const event = new this.win.KeyboardEvent("keydown", {
|
|
733
|
+
key,
|
|
734
|
+
code: code || key,
|
|
735
|
+
bubbles: true,
|
|
736
|
+
cancelable: true,
|
|
737
|
+
view: this.win
|
|
738
|
+
});
|
|
739
|
+
this.doc.dispatchEvent(event);
|
|
740
|
+
}
|
|
741
|
+
keyUp(key, code) {
|
|
742
|
+
const event = new this.win.KeyboardEvent("keyup", {
|
|
743
|
+
key,
|
|
744
|
+
code: code || key,
|
|
745
|
+
bubbles: true,
|
|
746
|
+
cancelable: true,
|
|
747
|
+
view: this.win
|
|
748
|
+
});
|
|
749
|
+
this.doc.dispatchEvent(event);
|
|
750
|
+
}
|
|
751
|
+
mouseMove(movementX, movementY, clientX = 0, clientY = 0) {
|
|
752
|
+
const event = new this.win.MouseEvent("mousemove", {
|
|
753
|
+
bubbles: true,
|
|
754
|
+
cancelable: true,
|
|
755
|
+
view: this.win,
|
|
756
|
+
clientX,
|
|
757
|
+
clientY,
|
|
758
|
+
movementX,
|
|
759
|
+
// Note: JSDOM might not support this standard property fully on event init
|
|
760
|
+
movementY
|
|
761
|
+
});
|
|
762
|
+
Object.defineProperty(event, "movementX", { value: movementX });
|
|
763
|
+
Object.defineProperty(event, "movementY", { value: movementY });
|
|
764
|
+
const target = this.doc.pointerLockElement || this.doc;
|
|
765
|
+
target.dispatchEvent(event);
|
|
766
|
+
}
|
|
767
|
+
mouseDown(button = 0) {
|
|
768
|
+
const event = new this.win.MouseEvent("mousedown", {
|
|
769
|
+
button,
|
|
770
|
+
bubbles: true,
|
|
771
|
+
cancelable: true,
|
|
772
|
+
view: this.win
|
|
773
|
+
});
|
|
774
|
+
const target = this.doc.pointerLockElement || this.doc;
|
|
775
|
+
target.dispatchEvent(event);
|
|
776
|
+
}
|
|
777
|
+
mouseUp(button = 0) {
|
|
778
|
+
const event = new this.win.MouseEvent("mouseup", {
|
|
779
|
+
button,
|
|
780
|
+
bubbles: true,
|
|
781
|
+
cancelable: true,
|
|
782
|
+
view: this.win
|
|
783
|
+
});
|
|
784
|
+
const target = this.doc.pointerLockElement || this.doc;
|
|
785
|
+
target.dispatchEvent(event);
|
|
786
|
+
}
|
|
787
|
+
wheel(deltaY) {
|
|
788
|
+
const event = new this.win.WheelEvent("wheel", {
|
|
789
|
+
deltaY,
|
|
790
|
+
bubbles: true,
|
|
791
|
+
cancelable: true,
|
|
792
|
+
view: this.win
|
|
793
|
+
});
|
|
794
|
+
const target = this.doc.pointerLockElement || this.doc;
|
|
795
|
+
target.dispatchEvent(event);
|
|
796
|
+
}
|
|
797
|
+
};
|
|
798
|
+
|
|
799
|
+
// src/setup/webgl.ts
|
|
800
|
+
function createMockWebGL2Context(canvas) {
|
|
801
|
+
const gl = {
|
|
802
|
+
canvas,
|
|
803
|
+
drawingBufferWidth: canvas.width,
|
|
804
|
+
drawingBufferHeight: canvas.height,
|
|
805
|
+
// Constants
|
|
806
|
+
VERTEX_SHADER: 35633,
|
|
807
|
+
FRAGMENT_SHADER: 35632,
|
|
808
|
+
COMPILE_STATUS: 35713,
|
|
809
|
+
LINK_STATUS: 35714,
|
|
810
|
+
ARRAY_BUFFER: 34962,
|
|
811
|
+
ELEMENT_ARRAY_BUFFER: 34963,
|
|
812
|
+
STATIC_DRAW: 35044,
|
|
813
|
+
DYNAMIC_DRAW: 35048,
|
|
814
|
+
FLOAT: 5126,
|
|
815
|
+
DEPTH_TEST: 2929,
|
|
816
|
+
BLEND: 3042,
|
|
817
|
+
SRC_ALPHA: 770,
|
|
818
|
+
ONE_MINUS_SRC_ALPHA: 771,
|
|
819
|
+
TEXTURE_2D: 3553,
|
|
820
|
+
RGBA: 6408,
|
|
821
|
+
UNSIGNED_BYTE: 5121,
|
|
822
|
+
COLOR_BUFFER_BIT: 16384,
|
|
823
|
+
DEPTH_BUFFER_BIT: 256,
|
|
824
|
+
TRIANGLES: 4,
|
|
825
|
+
TRIANGLE_STRIP: 5,
|
|
826
|
+
TRIANGLE_FAN: 6,
|
|
827
|
+
// Methods
|
|
828
|
+
createShader: () => ({}),
|
|
829
|
+
shaderSource: () => {
|
|
830
|
+
},
|
|
831
|
+
compileShader: () => {
|
|
832
|
+
},
|
|
833
|
+
getShaderParameter: (_, param) => {
|
|
834
|
+
if (param === 35713) return true;
|
|
835
|
+
return true;
|
|
836
|
+
},
|
|
837
|
+
getShaderInfoLog: () => "",
|
|
838
|
+
createProgram: () => ({}),
|
|
839
|
+
attachShader: () => {
|
|
840
|
+
},
|
|
841
|
+
linkProgram: () => {
|
|
842
|
+
},
|
|
843
|
+
getProgramParameter: (_, param) => {
|
|
844
|
+
if (param === 35714) return true;
|
|
845
|
+
return true;
|
|
846
|
+
},
|
|
847
|
+
getProgramInfoLog: () => "",
|
|
848
|
+
useProgram: () => {
|
|
849
|
+
},
|
|
850
|
+
createBuffer: () => ({}),
|
|
851
|
+
bindBuffer: () => {
|
|
852
|
+
},
|
|
853
|
+
bufferData: () => {
|
|
854
|
+
},
|
|
855
|
+
enableVertexAttribArray: () => {
|
|
856
|
+
},
|
|
857
|
+
vertexAttribPointer: () => {
|
|
858
|
+
},
|
|
859
|
+
enable: () => {
|
|
860
|
+
},
|
|
861
|
+
disable: () => {
|
|
862
|
+
},
|
|
863
|
+
depthMask: () => {
|
|
864
|
+
},
|
|
865
|
+
blendFunc: () => {
|
|
866
|
+
},
|
|
867
|
+
viewport: () => {
|
|
868
|
+
},
|
|
869
|
+
clearColor: () => {
|
|
707
870
|
},
|
|
708
|
-
|
|
709
|
-
this.tick(currentTime + deltaMs);
|
|
871
|
+
clear: () => {
|
|
710
872
|
},
|
|
711
|
-
|
|
712
|
-
|
|
873
|
+
createTexture: () => ({}),
|
|
874
|
+
bindTexture: () => {
|
|
713
875
|
},
|
|
714
|
-
|
|
715
|
-
callbacks = [];
|
|
716
|
-
nextId = 1;
|
|
717
|
-
currentTime = 0;
|
|
876
|
+
texImage2D: () => {
|
|
718
877
|
},
|
|
719
|
-
|
|
720
|
-
activeMockRAF = this;
|
|
721
|
-
global.requestAnimationFrame = raf;
|
|
722
|
-
global.cancelAnimationFrame = cancel;
|
|
878
|
+
texParameteri: () => {
|
|
723
879
|
},
|
|
724
|
-
|
|
725
|
-
if (activeMockRAF === this) {
|
|
726
|
-
activeMockRAF = void 0;
|
|
727
|
-
}
|
|
728
|
-
if (originalRAF) {
|
|
729
|
-
global.requestAnimationFrame = originalRAF;
|
|
730
|
-
} else {
|
|
731
|
-
delete global.requestAnimationFrame;
|
|
732
|
-
}
|
|
733
|
-
if (originalCancelRAF) {
|
|
734
|
-
global.cancelAnimationFrame = originalCancelRAF;
|
|
735
|
-
} else {
|
|
736
|
-
delete global.cancelAnimationFrame;
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
};
|
|
740
|
-
return mock;
|
|
741
|
-
}
|
|
742
|
-
function createMockPerformance(startTime = 0) {
|
|
743
|
-
let currentTime = startTime;
|
|
744
|
-
const mockPerf = {
|
|
745
|
-
now: () => currentTime,
|
|
746
|
-
timeOrigin: startTime,
|
|
747
|
-
timing: {
|
|
748
|
-
navigationStart: startTime
|
|
880
|
+
activeTexture: () => {
|
|
749
881
|
},
|
|
750
|
-
|
|
882
|
+
uniform1i: () => {
|
|
751
883
|
},
|
|
752
|
-
|
|
884
|
+
uniform1f: () => {
|
|
753
885
|
},
|
|
754
|
-
|
|
886
|
+
uniform2f: () => {
|
|
755
887
|
},
|
|
756
|
-
|
|
757
|
-
getEntriesByName: () => [],
|
|
758
|
-
getEntriesByType: () => [],
|
|
759
|
-
mark: () => {
|
|
888
|
+
uniform3f: () => {
|
|
760
889
|
},
|
|
761
|
-
|
|
890
|
+
uniform4f: () => {
|
|
762
891
|
},
|
|
763
|
-
|
|
892
|
+
uniformMatrix4fv: () => {
|
|
764
893
|
},
|
|
765
|
-
|
|
766
|
-
|
|
894
|
+
getUniformLocation: () => ({}),
|
|
895
|
+
getAttribLocation: () => 0,
|
|
896
|
+
drawArrays: () => {
|
|
767
897
|
},
|
|
768
|
-
|
|
898
|
+
drawElements: () => {
|
|
769
899
|
},
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
900
|
+
createVertexArray: () => ({}),
|
|
901
|
+
bindVertexArray: () => {
|
|
902
|
+
},
|
|
903
|
+
deleteShader: () => {
|
|
904
|
+
},
|
|
905
|
+
deleteProgram: () => {
|
|
906
|
+
},
|
|
907
|
+
deleteBuffer: () => {
|
|
908
|
+
},
|
|
909
|
+
deleteTexture: () => {
|
|
910
|
+
},
|
|
911
|
+
deleteVertexArray: () => {
|
|
912
|
+
},
|
|
913
|
+
// WebGL2 specific
|
|
914
|
+
texImage3D: () => {
|
|
915
|
+
},
|
|
916
|
+
uniformBlockBinding: () => {
|
|
917
|
+
},
|
|
918
|
+
getExtension: () => null
|
|
777
919
|
};
|
|
778
|
-
return
|
|
920
|
+
return gl;
|
|
779
921
|
}
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
922
|
+
|
|
923
|
+
// src/setup/browser.ts
|
|
924
|
+
function setupBrowserEnvironment(options = {}) {
|
|
925
|
+
const {
|
|
926
|
+
url = "http://localhost",
|
|
927
|
+
pretendToBeVisual = true,
|
|
928
|
+
resources = void 0,
|
|
929
|
+
enableWebGL2 = false,
|
|
930
|
+
enablePointerLock = false
|
|
931
|
+
} = options;
|
|
932
|
+
const dom = new import_jsdom.JSDOM("<!DOCTYPE html><html><head></head><body></body></html>", {
|
|
933
|
+
url,
|
|
934
|
+
pretendToBeVisual,
|
|
935
|
+
resources
|
|
936
|
+
});
|
|
937
|
+
global.window = dom.window;
|
|
938
|
+
global.document = dom.window.document;
|
|
939
|
+
try {
|
|
940
|
+
global.navigator = dom.window.navigator;
|
|
941
|
+
} catch (e) {
|
|
942
|
+
try {
|
|
943
|
+
Object.defineProperty(global, "navigator", {
|
|
944
|
+
value: dom.window.navigator,
|
|
945
|
+
writable: true,
|
|
946
|
+
configurable: true
|
|
947
|
+
});
|
|
948
|
+
} catch (e2) {
|
|
949
|
+
console.warn("Could not assign global.navigator, skipping.");
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
global.location = dom.window.location;
|
|
953
|
+
global.HTMLElement = dom.window.HTMLElement;
|
|
954
|
+
global.HTMLCanvasElement = dom.window.HTMLCanvasElement;
|
|
955
|
+
global.Event = dom.window.Event;
|
|
956
|
+
global.CustomEvent = dom.window.CustomEvent;
|
|
957
|
+
global.DragEvent = dom.window.DragEvent;
|
|
958
|
+
global.MouseEvent = dom.window.MouseEvent;
|
|
959
|
+
global.KeyboardEvent = dom.window.KeyboardEvent;
|
|
960
|
+
global.FocusEvent = dom.window.FocusEvent;
|
|
961
|
+
global.WheelEvent = dom.window.WheelEvent;
|
|
962
|
+
global.InputEvent = dom.window.InputEvent;
|
|
963
|
+
global.UIEvent = dom.window.UIEvent;
|
|
964
|
+
try {
|
|
965
|
+
global.localStorage = dom.window.localStorage;
|
|
966
|
+
} catch (e) {
|
|
967
|
+
}
|
|
968
|
+
if (!global.localStorage) {
|
|
969
|
+
const storage = /* @__PURE__ */ new Map();
|
|
970
|
+
global.localStorage = {
|
|
971
|
+
getItem: (key) => storage.get(key) || null,
|
|
972
|
+
setItem: (key, value) => storage.set(key, value),
|
|
973
|
+
removeItem: (key) => storage.delete(key),
|
|
974
|
+
clear: () => storage.clear(),
|
|
975
|
+
key: (index) => Array.from(storage.keys())[index] || null,
|
|
976
|
+
get length() {
|
|
977
|
+
return storage.size;
|
|
978
|
+
}
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
const originalCreateElement = document.createElement.bind(document);
|
|
982
|
+
document.createElement = function(tagName, options2) {
|
|
983
|
+
if (tagName.toLowerCase() === "canvas") {
|
|
984
|
+
const napiCanvas = new import_canvas.Canvas(300, 150);
|
|
985
|
+
const domCanvas = originalCreateElement("canvas", options2);
|
|
986
|
+
Object.defineProperty(domCanvas, "width", {
|
|
987
|
+
get: () => napiCanvas.width,
|
|
988
|
+
set: (value) => {
|
|
989
|
+
napiCanvas.width = value;
|
|
990
|
+
},
|
|
991
|
+
enumerable: true,
|
|
992
|
+
configurable: true
|
|
993
|
+
});
|
|
994
|
+
Object.defineProperty(domCanvas, "height", {
|
|
995
|
+
get: () => napiCanvas.height,
|
|
996
|
+
set: (value) => {
|
|
997
|
+
napiCanvas.height = value;
|
|
998
|
+
},
|
|
999
|
+
enumerable: true,
|
|
1000
|
+
configurable: true
|
|
1001
|
+
});
|
|
1002
|
+
const originalGetContext = domCanvas.getContext.bind(domCanvas);
|
|
1003
|
+
domCanvas.getContext = function(contextId, options3) {
|
|
1004
|
+
if (contextId === "2d") {
|
|
1005
|
+
return napiCanvas.getContext("2d", options3);
|
|
820
1006
|
}
|
|
821
|
-
if (
|
|
822
|
-
|
|
1007
|
+
if (enableWebGL2 && contextId === "webgl2") {
|
|
1008
|
+
return createMockWebGL2Context(domCanvas);
|
|
823
1009
|
}
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
1010
|
+
if (contextId === "webgl" || contextId === "webgl2") {
|
|
1011
|
+
return originalGetContext(contextId, options3);
|
|
1012
|
+
}
|
|
1013
|
+
return napiCanvas.getContext(contextId, options3);
|
|
1014
|
+
};
|
|
1015
|
+
domCanvas.__napiCanvas = napiCanvas;
|
|
1016
|
+
return domCanvas;
|
|
1017
|
+
}
|
|
1018
|
+
return originalCreateElement(tagName, options2);
|
|
1019
|
+
};
|
|
1020
|
+
if (enableWebGL2) {
|
|
1021
|
+
const originalProtoGetContext = global.HTMLCanvasElement.prototype.getContext;
|
|
1022
|
+
global.HTMLCanvasElement.prototype.getContext = function(contextId, options2) {
|
|
1023
|
+
if (contextId === "webgl2") {
|
|
1024
|
+
return createMockWebGL2Context(this);
|
|
1025
|
+
}
|
|
1026
|
+
return originalProtoGetContext.call(this, contextId, options2);
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
global.Image = import_canvas.Image;
|
|
1030
|
+
global.ImageData = import_canvas.ImageData;
|
|
1031
|
+
if (typeof global.createImageBitmap === "undefined") {
|
|
1032
|
+
global.createImageBitmap = async function(image, _options) {
|
|
1033
|
+
if (image && typeof image.width === "number" && typeof image.height === "number") {
|
|
1034
|
+
const canvas2 = new import_canvas.Canvas(image.width, image.height);
|
|
1035
|
+
const ctx = canvas2.getContext("2d");
|
|
1036
|
+
if (image.data) {
|
|
1037
|
+
ctx.putImageData(image, 0, 0);
|
|
831
1038
|
}
|
|
832
|
-
|
|
1039
|
+
return canvas2;
|
|
833
1040
|
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
timers = [];
|
|
838
|
-
},
|
|
839
|
-
restore() {
|
|
840
|
-
global.setTimeout = originalSetTimeout;
|
|
841
|
-
global.clearTimeout = originalClearTimeout;
|
|
842
|
-
global.setInterval = originalSetInterval;
|
|
843
|
-
global.clearInterval = originalClearInterval;
|
|
844
|
-
}
|
|
845
|
-
};
|
|
846
|
-
}
|
|
847
|
-
function simulateFrames(count, frameTimeMs = 16.6, callback) {
|
|
848
|
-
if (!activeMockRAF) {
|
|
849
|
-
throw new Error("simulateFrames requires an active MockRAF. Ensure createMockRAF().enable() is called.");
|
|
1041
|
+
const canvas = new import_canvas.Canvas(100, 100);
|
|
1042
|
+
return canvas;
|
|
1043
|
+
};
|
|
850
1044
|
}
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
1045
|
+
if (typeof global.btoa === "undefined") {
|
|
1046
|
+
global.btoa = function(str) {
|
|
1047
|
+
return Buffer.from(str, "binary").toString("base64");
|
|
1048
|
+
};
|
|
854
1049
|
}
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
1050
|
+
if (typeof global.atob === "undefined") {
|
|
1051
|
+
global.atob = function(str) {
|
|
1052
|
+
return Buffer.from(str, "base64").toString("binary");
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1055
|
+
if (enablePointerLock) {
|
|
1056
|
+
MockPointerLock.setup(global.document);
|
|
1057
|
+
}
|
|
1058
|
+
if (typeof global.requestAnimationFrame === "undefined") {
|
|
1059
|
+
let lastTime = 0;
|
|
1060
|
+
global.requestAnimationFrame = (callback) => {
|
|
1061
|
+
const currTime = Date.now();
|
|
1062
|
+
const timeToCall = Math.max(0, 16 - (currTime - lastTime));
|
|
1063
|
+
const id = setTimeout(() => {
|
|
1064
|
+
callback(currTime + timeToCall);
|
|
1065
|
+
}, timeToCall);
|
|
1066
|
+
lastTime = currTime + timeToCall;
|
|
1067
|
+
return id;
|
|
1068
|
+
};
|
|
1069
|
+
global.cancelAnimationFrame = (id) => {
|
|
1070
|
+
clearTimeout(id);
|
|
1071
|
+
};
|
|
860
1072
|
}
|
|
861
1073
|
}
|
|
1074
|
+
function teardownBrowserEnvironment() {
|
|
1075
|
+
delete global.window;
|
|
1076
|
+
delete global.document;
|
|
1077
|
+
delete global.navigator;
|
|
1078
|
+
delete global.localStorage;
|
|
1079
|
+
delete global.location;
|
|
1080
|
+
delete global.HTMLElement;
|
|
1081
|
+
delete global.HTMLCanvasElement;
|
|
1082
|
+
delete global.Image;
|
|
1083
|
+
delete global.ImageData;
|
|
1084
|
+
delete global.createImageBitmap;
|
|
1085
|
+
delete global.Event;
|
|
1086
|
+
delete global.CustomEvent;
|
|
1087
|
+
delete global.DragEvent;
|
|
1088
|
+
delete global.MouseEvent;
|
|
1089
|
+
delete global.KeyboardEvent;
|
|
1090
|
+
delete global.FocusEvent;
|
|
1091
|
+
delete global.WheelEvent;
|
|
1092
|
+
delete global.InputEvent;
|
|
1093
|
+
delete global.UIEvent;
|
|
1094
|
+
}
|
|
862
1095
|
|
|
863
|
-
// src/
|
|
864
|
-
var
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
if (!clientUrl) {
|
|
872
|
-
staticServer = (0, import_http.createServer)((request, response) => {
|
|
873
|
-
return (0, import_serve_handler.default)(request, response, {
|
|
874
|
-
public: rootPath,
|
|
875
|
-
cleanUrls: false,
|
|
876
|
-
headers: [
|
|
877
|
-
{
|
|
878
|
-
source: "**/*",
|
|
879
|
-
headers: [
|
|
880
|
-
{ key: "Cache-Control", value: "no-cache" },
|
|
881
|
-
{ key: "Access-Control-Allow-Origin", value: "*" },
|
|
882
|
-
{ key: "Cross-Origin-Opener-Policy", value: "same-origin" },
|
|
883
|
-
{ key: "Cross-Origin-Embedder-Policy", value: "require-corp" }
|
|
884
|
-
]
|
|
885
|
-
}
|
|
886
|
-
]
|
|
887
|
-
});
|
|
888
|
-
});
|
|
889
|
-
await new Promise((resolve) => {
|
|
890
|
-
if (!staticServer) return;
|
|
891
|
-
staticServer.listen(0, () => {
|
|
892
|
-
const addr = staticServer?.address();
|
|
893
|
-
const port = typeof addr === "object" ? addr?.port : 0;
|
|
894
|
-
clientUrl = `http://localhost:${port}`;
|
|
895
|
-
console.log(`Test client serving from ${rootPath} at ${clientUrl}`);
|
|
896
|
-
resolve();
|
|
897
|
-
});
|
|
898
|
-
});
|
|
1096
|
+
// src/setup/canvas.ts
|
|
1097
|
+
var import_canvas2 = require("@napi-rs/canvas");
|
|
1098
|
+
function createMockCanvas(width = 300, height = 150) {
|
|
1099
|
+
if (typeof document !== "undefined" && document.createElement) {
|
|
1100
|
+
const canvas2 = document.createElement("canvas");
|
|
1101
|
+
canvas2.width = width;
|
|
1102
|
+
canvas2.height = height;
|
|
1103
|
+
return canvas2;
|
|
899
1104
|
}
|
|
900
|
-
const
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
...options.launchOptions?.args || []
|
|
906
|
-
],
|
|
907
|
-
...options.launchOptions
|
|
908
|
-
});
|
|
909
|
-
const width = options.width || 1280;
|
|
910
|
-
const height = options.height || 720;
|
|
911
|
-
const context = await browser.newContext({
|
|
912
|
-
viewport: { width, height },
|
|
913
|
-
deviceScaleFactor: 1,
|
|
914
|
-
...options.contextOptions
|
|
915
|
-
});
|
|
916
|
-
const page = await context.newPage();
|
|
917
|
-
const close = async () => {
|
|
918
|
-
await browser.close();
|
|
919
|
-
if (staticServer) {
|
|
920
|
-
staticServer.close();
|
|
921
|
-
}
|
|
922
|
-
};
|
|
923
|
-
const navigate = async (url) => {
|
|
924
|
-
const targetUrl = url || clientUrl;
|
|
925
|
-
if (!targetUrl) throw new Error("No URL to navigate to");
|
|
926
|
-
let finalUrl = targetUrl;
|
|
927
|
-
if (options.serverUrl && !targetUrl.includes("connect=")) {
|
|
928
|
-
const separator = targetUrl.includes("?") ? "&" : "?";
|
|
929
|
-
finalUrl = `${targetUrl}${separator}connect=${encodeURIComponent(options.serverUrl)}`;
|
|
1105
|
+
const canvas = new import_canvas2.Canvas(width, height);
|
|
1106
|
+
const originalGetContext = canvas.getContext.bind(canvas);
|
|
1107
|
+
canvas.getContext = function(contextId, options) {
|
|
1108
|
+
if (contextId === "webgl2") {
|
|
1109
|
+
return createMockWebGL2Context(canvas);
|
|
930
1110
|
}
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
};
|
|
934
|
-
return {
|
|
935
|
-
browser,
|
|
936
|
-
context,
|
|
937
|
-
page,
|
|
938
|
-
server: staticServer,
|
|
939
|
-
close,
|
|
940
|
-
navigate,
|
|
941
|
-
waitForGame: async (timeout = 1e4) => {
|
|
942
|
-
await waitForGameReady(page, timeout);
|
|
943
|
-
},
|
|
944
|
-
injectInput: async (type, data) => {
|
|
945
|
-
await page.evaluate(({ type: type2, data: data2 }) => {
|
|
946
|
-
if (window.injectGameInput) window.injectGameInput(type2, data2);
|
|
947
|
-
}, { type, data });
|
|
1111
|
+
if (contextId === "2d") {
|
|
1112
|
+
return originalGetContext("2d", options);
|
|
948
1113
|
}
|
|
1114
|
+
return originalGetContext(contextId, options);
|
|
949
1115
|
};
|
|
1116
|
+
return canvas;
|
|
950
1117
|
}
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
return window.gameInstance && window.gameInstance.isReady;
|
|
955
|
-
}, null, { timeout });
|
|
956
|
-
} catch (e) {
|
|
957
|
-
await page.waitForSelector("canvas", { timeout });
|
|
1118
|
+
function createMockCanvasContext2D(canvas) {
|
|
1119
|
+
if (!canvas) {
|
|
1120
|
+
canvas = createMockCanvas();
|
|
958
1121
|
}
|
|
1122
|
+
return canvas.getContext("2d");
|
|
959
1123
|
}
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
1124
|
+
function captureCanvasDrawCalls(context) {
|
|
1125
|
+
const drawCalls = [];
|
|
1126
|
+
const methodsToSpy = [
|
|
1127
|
+
"fillRect",
|
|
1128
|
+
"strokeRect",
|
|
1129
|
+
"clearRect",
|
|
1130
|
+
"fillText",
|
|
1131
|
+
"strokeText",
|
|
1132
|
+
"drawImage",
|
|
1133
|
+
"beginPath",
|
|
1134
|
+
"closePath",
|
|
1135
|
+
"moveTo",
|
|
1136
|
+
"lineTo",
|
|
1137
|
+
"arc",
|
|
1138
|
+
"arcTo",
|
|
1139
|
+
"bezierCurveTo",
|
|
1140
|
+
"quadraticCurveTo",
|
|
1141
|
+
"stroke",
|
|
1142
|
+
"fill",
|
|
1143
|
+
"putImageData"
|
|
1144
|
+
];
|
|
1145
|
+
methodsToSpy.forEach((method) => {
|
|
1146
|
+
const original = context[method];
|
|
1147
|
+
if (typeof original === "function") {
|
|
1148
|
+
context[method] = function(...args) {
|
|
1149
|
+
drawCalls.push({ method, args });
|
|
1150
|
+
return original.apply(this, args);
|
|
1151
|
+
};
|
|
964
1152
|
}
|
|
965
|
-
return {};
|
|
966
1153
|
});
|
|
1154
|
+
return drawCalls;
|
|
967
1155
|
}
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
const half = size / 2;
|
|
981
|
-
const planes = [
|
|
982
|
-
makePlane({ x: 1, y: 0, z: 0 }, half),
|
|
983
|
-
makePlane({ x: -1, y: 0, z: 0 }, half),
|
|
984
|
-
makePlane({ x: 0, y: 1, z: 0 }, half),
|
|
985
|
-
makePlane({ x: 0, y: -1, z: 0 }, half),
|
|
986
|
-
makePlane({ x: 0, y: 0, z: 1 }, half),
|
|
987
|
-
makePlane({ x: 0, y: 0, z: -1 }, half)
|
|
988
|
-
];
|
|
989
|
-
return {
|
|
990
|
-
contents,
|
|
991
|
-
sides: planes.map((plane) => ({ plane, surfaceFlags: 0 }))
|
|
992
|
-
};
|
|
1156
|
+
function createMockImageData(width, height, fillColor) {
|
|
1157
|
+
const imageData = new import_canvas2.ImageData(width, height);
|
|
1158
|
+
if (fillColor) {
|
|
1159
|
+
const [r, g, b, a] = fillColor;
|
|
1160
|
+
for (let i = 0; i < imageData.data.length; i += 4) {
|
|
1161
|
+
imageData.data[i] = r;
|
|
1162
|
+
imageData.data[i + 1] = g;
|
|
1163
|
+
imageData.data[i + 2] = b;
|
|
1164
|
+
imageData.data[i + 3] = a;
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
return imageData;
|
|
993
1168
|
}
|
|
994
|
-
function
|
|
995
|
-
|
|
1169
|
+
function createMockImage(width, height, src) {
|
|
1170
|
+
const img = new import_canvas2.Image();
|
|
1171
|
+
if (width) img.width = width;
|
|
1172
|
+
if (height) img.height = height;
|
|
1173
|
+
if (src) img.src = src;
|
|
1174
|
+
return img;
|
|
996
1175
|
}
|
|
997
|
-
|
|
1176
|
+
|
|
1177
|
+
// src/setup/node.ts
|
|
1178
|
+
function setupNodeEnvironment(options = {}) {
|
|
1179
|
+
if (options.polyfillFetch && typeof global.fetch === "undefined") {
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
// src/setup/storage.ts
|
|
1184
|
+
var import_auto2 = require("fake-indexeddb/auto");
|
|
1185
|
+
function createMockLocalStorage(initialData = {}) {
|
|
1186
|
+
const storage = new Map(Object.entries(initialData));
|
|
998
1187
|
return {
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1188
|
+
getItem: (key) => storage.get(key) || null,
|
|
1189
|
+
setItem: (key, value) => storage.set(key, value),
|
|
1190
|
+
removeItem: (key) => storage.delete(key),
|
|
1191
|
+
clear: () => storage.clear(),
|
|
1192
|
+
key: (index) => Array.from(storage.keys())[index] || null,
|
|
1193
|
+
get length() {
|
|
1194
|
+
return storage.size;
|
|
1195
|
+
}
|
|
1005
1196
|
};
|
|
1006
1197
|
}
|
|
1007
|
-
function
|
|
1008
|
-
return
|
|
1198
|
+
function createMockSessionStorage(initialData = {}) {
|
|
1199
|
+
return createMockLocalStorage(initialData);
|
|
1009
1200
|
}
|
|
1010
|
-
function
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
leaves: [makeLeaf(0, 0, brushes.length)],
|
|
1016
|
-
brushes,
|
|
1017
|
-
leafBrushes: brushes.map((_, i) => i),
|
|
1018
|
-
bmodels: []
|
|
1019
|
-
};
|
|
1201
|
+
function createMockIndexedDB() {
|
|
1202
|
+
if (typeof indexedDB === "undefined") {
|
|
1203
|
+
throw new Error("IndexedDB mock not found. Ensure fake-indexeddb is loaded.");
|
|
1204
|
+
}
|
|
1205
|
+
return indexedDB;
|
|
1020
1206
|
}
|
|
1021
|
-
function
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1207
|
+
function createStorageTestScenario(storageType = "local") {
|
|
1208
|
+
if (storageType === "indexed") {
|
|
1209
|
+
const dbName = `test-db-${Math.random().toString(36).substring(7)}`;
|
|
1210
|
+
const storeName = "test-store";
|
|
1211
|
+
const storage2 = createMockIndexedDB();
|
|
1212
|
+
return {
|
|
1213
|
+
storage: storage2,
|
|
1214
|
+
populate: async (data) => {
|
|
1215
|
+
return new Promise((resolve, reject) => {
|
|
1216
|
+
const req = storage2.open(dbName, 1);
|
|
1217
|
+
req.onupgradeneeded = (e) => {
|
|
1218
|
+
const db = e.target.result;
|
|
1219
|
+
db.createObjectStore(storeName);
|
|
1220
|
+
};
|
|
1221
|
+
req.onsuccess = (e) => {
|
|
1222
|
+
const db = e.target.result;
|
|
1223
|
+
const tx = db.transaction(storeName, "readwrite");
|
|
1224
|
+
const store = tx.objectStore(storeName);
|
|
1225
|
+
Object.entries(data).forEach(([k, v]) => store.put(v, k));
|
|
1226
|
+
tx.oncomplete = () => {
|
|
1227
|
+
db.close();
|
|
1228
|
+
resolve();
|
|
1229
|
+
};
|
|
1230
|
+
tx.onerror = () => reject(tx.error);
|
|
1231
|
+
};
|
|
1232
|
+
req.onerror = () => reject(req.error);
|
|
1233
|
+
});
|
|
1234
|
+
},
|
|
1235
|
+
verify: async (key, value) => {
|
|
1236
|
+
return new Promise((resolve, reject) => {
|
|
1237
|
+
const req = storage2.open(dbName, 1);
|
|
1238
|
+
req.onsuccess = (e) => {
|
|
1239
|
+
const db = e.target.result;
|
|
1240
|
+
if (!db.objectStoreNames.contains(storeName)) {
|
|
1241
|
+
db.close();
|
|
1242
|
+
resolve(false);
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1245
|
+
const tx = db.transaction(storeName, "readonly");
|
|
1246
|
+
const store = tx.objectStore(storeName);
|
|
1247
|
+
const getReq = store.get(key);
|
|
1248
|
+
getReq.onsuccess = () => {
|
|
1249
|
+
const result = getReq.result === value;
|
|
1250
|
+
db.close();
|
|
1251
|
+
resolve(result);
|
|
1252
|
+
};
|
|
1253
|
+
getReq.onerror = () => {
|
|
1254
|
+
db.close();
|
|
1255
|
+
resolve(false);
|
|
1256
|
+
};
|
|
1257
|
+
};
|
|
1258
|
+
req.onerror = () => reject(req.error);
|
|
1259
|
+
});
|
|
1260
|
+
}
|
|
1261
|
+
};
|
|
1262
|
+
}
|
|
1263
|
+
const storage = storageType === "local" ? createMockLocalStorage() : createMockSessionStorage();
|
|
1030
1264
|
return {
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
writeInt16: import_vitest.vi.fn(),
|
|
1050
|
-
writeUint16: import_vitest.vi.fn(),
|
|
1051
|
-
writeInt32: import_vitest.vi.fn(),
|
|
1052
|
-
writeUint32: import_vitest.vi.fn(),
|
|
1053
|
-
writeFloat: import_vitest.vi.fn(),
|
|
1054
|
-
getData: import_vitest.vi.fn(() => new Uint8Array(0))
|
|
1055
|
-
});
|
|
1056
|
-
var createNetChanMock = () => ({
|
|
1057
|
-
qport: 1234,
|
|
1058
|
-
// Sequencing
|
|
1059
|
-
incomingSequence: 0,
|
|
1060
|
-
outgoingSequence: 0,
|
|
1061
|
-
incomingAcknowledged: 0,
|
|
1062
|
-
// Reliable messaging
|
|
1063
|
-
incomingReliableAcknowledged: false,
|
|
1064
|
-
incomingReliableSequence: 0,
|
|
1065
|
-
outgoingReliableSequence: 0,
|
|
1066
|
-
reliableMessage: createBinaryWriterMock(),
|
|
1067
|
-
reliableLength: 0,
|
|
1068
|
-
// Fragmentation
|
|
1069
|
-
fragmentSendOffset: 0,
|
|
1070
|
-
fragmentBuffer: null,
|
|
1071
|
-
fragmentLength: 0,
|
|
1072
|
-
fragmentReceived: 0,
|
|
1073
|
-
// Timing
|
|
1074
|
-
lastReceived: 0,
|
|
1075
|
-
lastSent: 0,
|
|
1076
|
-
remoteAddress: { type: "IP", port: 1234 },
|
|
1077
|
-
// Methods
|
|
1078
|
-
setup: import_vitest.vi.fn(),
|
|
1079
|
-
reset: import_vitest.vi.fn(),
|
|
1080
|
-
transmit: import_vitest.vi.fn(),
|
|
1081
|
-
process: import_vitest.vi.fn(),
|
|
1082
|
-
canSendReliable: import_vitest.vi.fn(() => true),
|
|
1083
|
-
writeReliableByte: import_vitest.vi.fn(),
|
|
1084
|
-
writeReliableShort: import_vitest.vi.fn(),
|
|
1085
|
-
writeReliableLong: import_vitest.vi.fn(),
|
|
1086
|
-
writeReliableString: import_vitest.vi.fn(),
|
|
1087
|
-
getReliableData: import_vitest.vi.fn(() => new Uint8Array(0)),
|
|
1088
|
-
needsKeepalive: import_vitest.vi.fn(() => false),
|
|
1089
|
-
isTimedOut: import_vitest.vi.fn(() => false)
|
|
1090
|
-
});
|
|
1091
|
-
var createBinaryStreamMock = () => ({
|
|
1092
|
-
getPosition: import_vitest.vi.fn(() => 0),
|
|
1093
|
-
getReadPosition: import_vitest.vi.fn(() => 0),
|
|
1094
|
-
getLength: import_vitest.vi.fn(() => 0),
|
|
1095
|
-
getRemaining: import_vitest.vi.fn(() => 0),
|
|
1096
|
-
seek: import_vitest.vi.fn(),
|
|
1097
|
-
setReadPosition: import_vitest.vi.fn(),
|
|
1098
|
-
hasMore: import_vitest.vi.fn(() => true),
|
|
1099
|
-
hasBytes: import_vitest.vi.fn((amount) => true),
|
|
1100
|
-
readChar: import_vitest.vi.fn(() => 0),
|
|
1101
|
-
readByte: import_vitest.vi.fn(() => 0),
|
|
1102
|
-
readShort: import_vitest.vi.fn(() => 0),
|
|
1103
|
-
readUShort: import_vitest.vi.fn(() => 0),
|
|
1104
|
-
readLong: import_vitest.vi.fn(() => 0),
|
|
1105
|
-
readULong: import_vitest.vi.fn(() => 0),
|
|
1106
|
-
readFloat: import_vitest.vi.fn(() => 0),
|
|
1107
|
-
readString: import_vitest.vi.fn(() => ""),
|
|
1108
|
-
readStringLine: import_vitest.vi.fn(() => ""),
|
|
1109
|
-
readCoord: import_vitest.vi.fn(() => 0),
|
|
1110
|
-
readAngle: import_vitest.vi.fn(() => 0),
|
|
1111
|
-
readAngle16: import_vitest.vi.fn(() => 0),
|
|
1112
|
-
readData: import_vitest.vi.fn((length) => new Uint8Array(length)),
|
|
1113
|
-
readPos: import_vitest.vi.fn(),
|
|
1114
|
-
readDir: import_vitest.vi.fn()
|
|
1115
|
-
});
|
|
1116
|
-
|
|
1117
|
-
// src/game/factories.ts
|
|
1118
|
-
var createPlayerStateFactory = (overrides) => ({
|
|
1119
|
-
pm_type: 0,
|
|
1120
|
-
pm_time: 0,
|
|
1121
|
-
pm_flags: 0,
|
|
1122
|
-
origin: { x: 0, y: 0, z: 0 },
|
|
1123
|
-
velocity: { x: 0, y: 0, z: 0 },
|
|
1124
|
-
viewAngles: { x: 0, y: 0, z: 0 },
|
|
1125
|
-
onGround: false,
|
|
1126
|
-
waterLevel: 0,
|
|
1127
|
-
watertype: 0,
|
|
1128
|
-
mins: { x: 0, y: 0, z: 0 },
|
|
1129
|
-
maxs: { x: 0, y: 0, z: 0 },
|
|
1130
|
-
damageAlpha: 0,
|
|
1131
|
-
damageIndicators: [],
|
|
1132
|
-
blend: [0, 0, 0, 0],
|
|
1133
|
-
stats: [],
|
|
1134
|
-
kick_angles: { x: 0, y: 0, z: 0 },
|
|
1135
|
-
kick_origin: { x: 0, y: 0, z: 0 },
|
|
1136
|
-
gunoffset: { x: 0, y: 0, z: 0 },
|
|
1137
|
-
gunangles: { x: 0, y: 0, z: 0 },
|
|
1138
|
-
gunindex: 0,
|
|
1139
|
-
gun_frame: 0,
|
|
1140
|
-
rdflags: 0,
|
|
1141
|
-
fov: 90,
|
|
1142
|
-
renderfx: 0,
|
|
1143
|
-
...overrides
|
|
1144
|
-
});
|
|
1145
|
-
var createEntityStateFactory = (overrides) => ({
|
|
1146
|
-
number: 0,
|
|
1147
|
-
origin: { x: 0, y: 0, z: 0 },
|
|
1148
|
-
angles: { x: 0, y: 0, z: 0 },
|
|
1149
|
-
oldOrigin: { x: 0, y: 0, z: 0 },
|
|
1150
|
-
modelIndex: 0,
|
|
1151
|
-
modelIndex2: 0,
|
|
1152
|
-
modelIndex3: 0,
|
|
1153
|
-
modelIndex4: 0,
|
|
1154
|
-
frame: 0,
|
|
1155
|
-
skinNum: 0,
|
|
1156
|
-
effects: 0,
|
|
1157
|
-
renderfx: 0,
|
|
1158
|
-
solid: 0,
|
|
1159
|
-
sound: 0,
|
|
1160
|
-
event: 0,
|
|
1161
|
-
...overrides
|
|
1162
|
-
});
|
|
1163
|
-
var createGameStateSnapshotFactory = (overrides) => ({
|
|
1164
|
-
gravity: { x: 0, y: 0, z: -800 },
|
|
1165
|
-
origin: { x: 0, y: 0, z: 0 },
|
|
1166
|
-
velocity: { x: 0, y: 0, z: 0 },
|
|
1167
|
-
viewangles: { x: 0, y: 0, z: 0 },
|
|
1168
|
-
level: { timeSeconds: 0, frameNumber: 0, previousTimeSeconds: 0, deltaSeconds: 0.1 },
|
|
1169
|
-
entities: {
|
|
1170
|
-
activeCount: 0,
|
|
1171
|
-
worldClassname: "worldspawn"
|
|
1172
|
-
},
|
|
1173
|
-
packetEntities: [],
|
|
1174
|
-
pmFlags: 0,
|
|
1175
|
-
pmType: 0,
|
|
1176
|
-
waterlevel: 0,
|
|
1177
|
-
watertype: 0,
|
|
1178
|
-
deltaAngles: { x: 0, y: 0, z: 0 },
|
|
1179
|
-
health: 100,
|
|
1180
|
-
armor: 0,
|
|
1181
|
-
ammo: 0,
|
|
1182
|
-
blend: [0, 0, 0, 0],
|
|
1183
|
-
damageAlpha: 0,
|
|
1184
|
-
damageIndicators: [],
|
|
1185
|
-
stats: [],
|
|
1186
|
-
kick_angles: { x: 0, y: 0, z: 0 },
|
|
1187
|
-
kick_origin: { x: 0, y: 0, z: 0 },
|
|
1188
|
-
gunoffset: { x: 0, y: 0, z: 0 },
|
|
1189
|
-
gunangles: { x: 0, y: 0, z: 0 },
|
|
1190
|
-
gunindex: 0,
|
|
1191
|
-
pm_time: 0,
|
|
1192
|
-
gun_frame: 0,
|
|
1193
|
-
rdflags: 0,
|
|
1194
|
-
fov: 90,
|
|
1195
|
-
renderfx: 0,
|
|
1196
|
-
pm_flags: 0,
|
|
1197
|
-
pm_type: 0,
|
|
1198
|
-
...overrides
|
|
1199
|
-
});
|
|
1200
|
-
|
|
1201
|
-
// src/game/helpers.ts
|
|
1202
|
-
var import_vitest2 = require("vitest");
|
|
1203
|
-
var import_game = require("@quake2ts/game");
|
|
1204
|
-
var import_shared2 = require("@quake2ts/shared");
|
|
1205
|
-
var import_shared3 = require("@quake2ts/shared");
|
|
1206
|
-
var createMockEngine = () => ({
|
|
1207
|
-
sound: import_vitest2.vi.fn(),
|
|
1208
|
-
soundIndex: import_vitest2.vi.fn((sound) => 0),
|
|
1209
|
-
modelIndex: import_vitest2.vi.fn((model) => 0),
|
|
1210
|
-
centerprintf: import_vitest2.vi.fn()
|
|
1211
|
-
});
|
|
1212
|
-
var createMockGame = (seed = 12345) => {
|
|
1213
|
-
const spawnRegistry = new import_game.SpawnRegistry();
|
|
1214
|
-
const hooks = new import_game.ScriptHookRegistry();
|
|
1215
|
-
const game = {
|
|
1216
|
-
random: (0, import_shared2.createRandomGenerator)({ seed }),
|
|
1217
|
-
registerEntitySpawn: import_vitest2.vi.fn((classname, spawnFunc) => {
|
|
1218
|
-
spawnRegistry.register(classname, (entity) => spawnFunc(entity));
|
|
1265
|
+
storage,
|
|
1266
|
+
populate(data) {
|
|
1267
|
+
Object.entries(data).forEach(([k, v]) => storage.setItem(k, v));
|
|
1268
|
+
},
|
|
1269
|
+
verify(key, value) {
|
|
1270
|
+
return storage.getItem(key) === value;
|
|
1271
|
+
}
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
// src/setup/audio.ts
|
|
1276
|
+
function createMockAudioContext() {
|
|
1277
|
+
const context = {
|
|
1278
|
+
createGain: () => ({
|
|
1279
|
+
connect: () => {
|
|
1280
|
+
},
|
|
1281
|
+
gain: { value: 1, setValueAtTime: () => {
|
|
1282
|
+
} }
|
|
1219
1283
|
}),
|
|
1220
|
-
|
|
1221
|
-
|
|
1284
|
+
createOscillator: () => ({
|
|
1285
|
+
connect: () => {
|
|
1286
|
+
},
|
|
1287
|
+
start: () => {
|
|
1288
|
+
},
|
|
1289
|
+
stop: () => {
|
|
1290
|
+
},
|
|
1291
|
+
frequency: { value: 440 }
|
|
1222
1292
|
}),
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1293
|
+
createBufferSource: () => ({
|
|
1294
|
+
connect: () => {
|
|
1295
|
+
},
|
|
1296
|
+
start: () => {
|
|
1297
|
+
},
|
|
1298
|
+
stop: () => {
|
|
1299
|
+
},
|
|
1300
|
+
buffer: null,
|
|
1301
|
+
playbackRate: { value: 1 },
|
|
1302
|
+
loop: false
|
|
1228
1303
|
}),
|
|
1229
|
-
|
|
1230
|
-
|
|
1304
|
+
destination: {},
|
|
1305
|
+
currentTime: 0,
|
|
1306
|
+
state: "running",
|
|
1307
|
+
resume: async () => {
|
|
1308
|
+
},
|
|
1309
|
+
suspend: async () => {
|
|
1310
|
+
},
|
|
1311
|
+
close: async () => {
|
|
1312
|
+
},
|
|
1313
|
+
decodeAudioData: async (buffer) => ({
|
|
1314
|
+
duration: 1,
|
|
1315
|
+
length: 44100,
|
|
1316
|
+
sampleRate: 44100,
|
|
1317
|
+
numberOfChannels: 2,
|
|
1318
|
+
getChannelData: () => new Float32Array(44100)
|
|
1231
1319
|
}),
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
function createTestContext(options) {
|
|
1239
|
-
const engine = createMockEngine();
|
|
1240
|
-
const seed = options?.seed ?? 12345;
|
|
1241
|
-
const { game, spawnRegistry } = createMockGame(seed);
|
|
1242
|
-
const traceFn = import_vitest2.vi.fn((start, end, mins, maxs) => ({
|
|
1243
|
-
fraction: 1,
|
|
1244
|
-
ent: null,
|
|
1245
|
-
allsolid: false,
|
|
1246
|
-
startsolid: false,
|
|
1247
|
-
endpos: end,
|
|
1248
|
-
plane: { normal: { x: 0, y: 0, z: 1 }, dist: 0 },
|
|
1249
|
-
surfaceFlags: 0,
|
|
1250
|
-
contents: 0
|
|
1251
|
-
}));
|
|
1252
|
-
const entityList = options?.initialEntities ? [...options.initialEntities] : [];
|
|
1253
|
-
const hooks = game.hooks;
|
|
1254
|
-
const entities = {
|
|
1255
|
-
spawn: import_vitest2.vi.fn(() => {
|
|
1256
|
-
const ent = new import_game.Entity(entityList.length + 1);
|
|
1257
|
-
entityList.push(ent);
|
|
1258
|
-
hooks.onEntitySpawn(ent);
|
|
1259
|
-
return ent;
|
|
1320
|
+
createBuffer: (channels, length, sampleRate) => ({
|
|
1321
|
+
duration: length / sampleRate,
|
|
1322
|
+
length,
|
|
1323
|
+
sampleRate,
|
|
1324
|
+
numberOfChannels: channels,
|
|
1325
|
+
getChannelData: () => new Float32Array(length)
|
|
1260
1326
|
}),
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1327
|
+
// Helper to track events if needed
|
|
1328
|
+
_events: []
|
|
1329
|
+
};
|
|
1330
|
+
return new Proxy(context, {
|
|
1331
|
+
get(target, prop, receiver) {
|
|
1332
|
+
if (prop === "_events") return target._events;
|
|
1333
|
+
const value = Reflect.get(target, prop, receiver);
|
|
1334
|
+
if (typeof value === "function") {
|
|
1335
|
+
return (...args) => {
|
|
1336
|
+
target._events.push({ type: String(prop), args });
|
|
1337
|
+
return Reflect.apply(value, target, args);
|
|
1338
|
+
};
|
|
1265
1339
|
}
|
|
1266
|
-
|
|
1267
|
-
}
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1340
|
+
return value;
|
|
1341
|
+
}
|
|
1342
|
+
});
|
|
1343
|
+
}
|
|
1344
|
+
function setupMockAudioContext() {
|
|
1345
|
+
if (typeof global.AudioContext === "undefined" && typeof global.window !== "undefined") {
|
|
1346
|
+
global.AudioContext = class {
|
|
1347
|
+
constructor() {
|
|
1348
|
+
return createMockAudioContext();
|
|
1349
|
+
}
|
|
1350
|
+
};
|
|
1351
|
+
global.window.AudioContext = global.AudioContext;
|
|
1352
|
+
global.window.webkitAudioContext = global.AudioContext;
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
function teardownMockAudioContext() {
|
|
1356
|
+
if (global.AudioContext && global.AudioContext.toString().includes("class")) {
|
|
1357
|
+
delete global.AudioContext;
|
|
1358
|
+
delete global.window.AudioContext;
|
|
1359
|
+
delete global.window.webkitAudioContext;
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
function captureAudioEvents(context) {
|
|
1363
|
+
return context._events || [];
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
// src/setup/timing.ts
|
|
1367
|
+
var activeMockRAF;
|
|
1368
|
+
function createMockRAF() {
|
|
1369
|
+
let callbacks = [];
|
|
1370
|
+
let nextId = 1;
|
|
1371
|
+
let currentTime = 0;
|
|
1372
|
+
const originalRAF = global.requestAnimationFrame;
|
|
1373
|
+
const originalCancelRAF = global.cancelAnimationFrame;
|
|
1374
|
+
const raf = (callback) => {
|
|
1375
|
+
const id = nextId++;
|
|
1376
|
+
callbacks.push({ id, callback });
|
|
1377
|
+
return id;
|
|
1378
|
+
};
|
|
1379
|
+
const cancel = (id) => {
|
|
1380
|
+
callbacks = callbacks.filter((cb) => cb.id !== id);
|
|
1381
|
+
};
|
|
1382
|
+
const mock = {
|
|
1383
|
+
tick(timestamp) {
|
|
1384
|
+
if (typeof timestamp !== "number") {
|
|
1385
|
+
currentTime += 16.6;
|
|
1386
|
+
} else {
|
|
1387
|
+
currentTime = timestamp;
|
|
1388
|
+
}
|
|
1389
|
+
const currentCallbacks = [...callbacks];
|
|
1390
|
+
callbacks = [];
|
|
1391
|
+
currentCallbacks.forEach(({ callback }) => {
|
|
1392
|
+
callback(currentTime);
|
|
1393
|
+
});
|
|
1394
|
+
},
|
|
1395
|
+
advance(deltaMs = 16.6) {
|
|
1396
|
+
this.tick(currentTime + deltaMs);
|
|
1397
|
+
},
|
|
1398
|
+
getCallbacks() {
|
|
1399
|
+
return callbacks.map((c) => c.callback);
|
|
1400
|
+
},
|
|
1401
|
+
reset() {
|
|
1402
|
+
callbacks = [];
|
|
1403
|
+
nextId = 1;
|
|
1404
|
+
currentTime = 0;
|
|
1405
|
+
},
|
|
1406
|
+
enable() {
|
|
1407
|
+
activeMockRAF = this;
|
|
1408
|
+
global.requestAnimationFrame = raf;
|
|
1409
|
+
global.cancelAnimationFrame = cancel;
|
|
1410
|
+
},
|
|
1411
|
+
disable() {
|
|
1412
|
+
if (activeMockRAF === this) {
|
|
1413
|
+
activeMockRAF = void 0;
|
|
1414
|
+
}
|
|
1415
|
+
if (originalRAF) {
|
|
1416
|
+
global.requestAnimationFrame = originalRAF;
|
|
1417
|
+
} else {
|
|
1418
|
+
delete global.requestAnimationFrame;
|
|
1419
|
+
}
|
|
1420
|
+
if (originalCancelRAF) {
|
|
1421
|
+
global.cancelAnimationFrame = originalCancelRAF;
|
|
1422
|
+
} else {
|
|
1423
|
+
delete global.cancelAnimationFrame;
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
};
|
|
1427
|
+
return mock;
|
|
1428
|
+
}
|
|
1429
|
+
function createMockPerformance(startTime = 0) {
|
|
1430
|
+
let currentTime = startTime;
|
|
1431
|
+
const mockPerf = {
|
|
1432
|
+
now: () => currentTime,
|
|
1433
|
+
timeOrigin: startTime,
|
|
1434
|
+
timing: {
|
|
1435
|
+
navigationStart: startTime
|
|
1436
|
+
},
|
|
1437
|
+
clearMarks: () => {
|
|
1438
|
+
},
|
|
1439
|
+
clearMeasures: () => {
|
|
1440
|
+
},
|
|
1441
|
+
clearResourceTimings: () => {
|
|
1442
|
+
},
|
|
1443
|
+
getEntries: () => [],
|
|
1444
|
+
getEntriesByName: () => [],
|
|
1445
|
+
getEntriesByType: () => [],
|
|
1446
|
+
mark: () => {
|
|
1447
|
+
},
|
|
1448
|
+
measure: () => {
|
|
1449
|
+
},
|
|
1450
|
+
setResourceTimingBufferSize: () => {
|
|
1451
|
+
},
|
|
1452
|
+
toJSON: () => ({}),
|
|
1453
|
+
addEventListener: () => {
|
|
1454
|
+
},
|
|
1455
|
+
removeEventListener: () => {
|
|
1456
|
+
},
|
|
1457
|
+
dispatchEvent: () => true
|
|
1458
|
+
};
|
|
1459
|
+
mockPerf.advance = (deltaMs) => {
|
|
1460
|
+
currentTime += deltaMs;
|
|
1461
|
+
};
|
|
1462
|
+
mockPerf.setTime = (time) => {
|
|
1463
|
+
currentTime = time;
|
|
1464
|
+
};
|
|
1465
|
+
return mockPerf;
|
|
1466
|
+
}
|
|
1467
|
+
function createControlledTimer() {
|
|
1468
|
+
let currentTime = 0;
|
|
1469
|
+
let timers = [];
|
|
1470
|
+
let nextId = 1;
|
|
1471
|
+
const originalSetTimeout = global.setTimeout;
|
|
1472
|
+
const originalClearTimeout = global.clearTimeout;
|
|
1473
|
+
const originalSetInterval = global.setInterval;
|
|
1474
|
+
const originalClearInterval = global.clearInterval;
|
|
1475
|
+
const mockSetTimeout = (callback, delay = 0, ...args) => {
|
|
1476
|
+
const id = nextId++;
|
|
1477
|
+
timers.push({ id, callback, dueTime: currentTime + delay, args });
|
|
1478
|
+
return id;
|
|
1479
|
+
};
|
|
1480
|
+
const mockClearTimeout = (id) => {
|
|
1481
|
+
timers = timers.filter((t) => t.id !== id);
|
|
1482
|
+
};
|
|
1483
|
+
const mockSetInterval = (callback, delay = 0, ...args) => {
|
|
1484
|
+
const id = nextId++;
|
|
1485
|
+
timers.push({ id, callback, dueTime: currentTime + delay, interval: delay, args });
|
|
1486
|
+
return id;
|
|
1487
|
+
};
|
|
1488
|
+
const mockClearInterval = (id) => {
|
|
1489
|
+
timers = timers.filter((t) => t.id !== id);
|
|
1490
|
+
};
|
|
1491
|
+
global.setTimeout = mockSetTimeout;
|
|
1492
|
+
global.clearTimeout = mockClearTimeout;
|
|
1493
|
+
global.setInterval = mockSetInterval;
|
|
1494
|
+
global.clearInterval = mockClearInterval;
|
|
1495
|
+
return {
|
|
1496
|
+
tick() {
|
|
1497
|
+
this.advanceBy(0);
|
|
1498
|
+
},
|
|
1499
|
+
advanceBy(ms) {
|
|
1500
|
+
const targetTime = currentTime + ms;
|
|
1501
|
+
while (true) {
|
|
1502
|
+
let earliest = null;
|
|
1503
|
+
for (const t of timers) {
|
|
1504
|
+
if (!earliest || t.dueTime < earliest.dueTime) {
|
|
1505
|
+
earliest = t;
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
if (!earliest || earliest.dueTime > targetTime) {
|
|
1509
|
+
break;
|
|
1510
|
+
}
|
|
1511
|
+
currentTime = earliest.dueTime;
|
|
1512
|
+
const { callback, args, interval, id } = earliest;
|
|
1513
|
+
if (interval !== void 0) {
|
|
1514
|
+
earliest.dueTime += interval;
|
|
1515
|
+
if (interval === 0) earliest.dueTime += 1;
|
|
1516
|
+
} else {
|
|
1517
|
+
timers = timers.filter((t) => t.id !== id);
|
|
1518
|
+
}
|
|
1519
|
+
callback(...args);
|
|
1273
1520
|
}
|
|
1274
|
-
|
|
1275
|
-
setSpawnRegistry: import_vitest2.vi.fn(),
|
|
1276
|
-
timeSeconds: 10,
|
|
1277
|
-
deltaSeconds: 0.1,
|
|
1278
|
-
modelIndex: import_vitest2.vi.fn(() => 0),
|
|
1279
|
-
scheduleThink: import_vitest2.vi.fn((entity, time) => {
|
|
1280
|
-
entity.nextthink = time;
|
|
1281
|
-
}),
|
|
1282
|
-
linkentity: import_vitest2.vi.fn(),
|
|
1283
|
-
trace: traceFn,
|
|
1284
|
-
pointcontents: import_vitest2.vi.fn(() => 0),
|
|
1285
|
-
multicast: import_vitest2.vi.fn(),
|
|
1286
|
-
unicast: import_vitest2.vi.fn(),
|
|
1287
|
-
engine,
|
|
1288
|
-
game,
|
|
1289
|
-
sound: import_vitest2.vi.fn((ent, chan, sound, vol, attn, timeofs) => {
|
|
1290
|
-
engine.sound(ent, chan, sound, vol, attn, timeofs);
|
|
1291
|
-
}),
|
|
1292
|
-
soundIndex: import_vitest2.vi.fn((sound) => engine.soundIndex(sound)),
|
|
1293
|
-
useTargets: import_vitest2.vi.fn((entity, activator) => {
|
|
1294
|
-
}),
|
|
1295
|
-
findByTargetName: import_vitest2.vi.fn(() => []),
|
|
1296
|
-
pickTarget: import_vitest2.vi.fn(() => null),
|
|
1297
|
-
killBox: import_vitest2.vi.fn(),
|
|
1298
|
-
rng: (0, import_shared2.createRandomGenerator)({ seed }),
|
|
1299
|
-
imports: {
|
|
1300
|
-
configstring: import_vitest2.vi.fn(),
|
|
1301
|
-
trace: traceFn,
|
|
1302
|
-
pointcontents: import_vitest2.vi.fn(() => 0)
|
|
1521
|
+
currentTime = targetTime;
|
|
1303
1522
|
},
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
intermission_origin: { x: 0, y: 0, z: 0 },
|
|
1307
|
-
next_auto_save: 0,
|
|
1308
|
-
health_bar_entities: null
|
|
1523
|
+
clear() {
|
|
1524
|
+
timers = [];
|
|
1309
1525
|
},
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1526
|
+
restore() {
|
|
1527
|
+
global.setTimeout = originalSetTimeout;
|
|
1528
|
+
global.clearTimeout = originalClearTimeout;
|
|
1529
|
+
global.setInterval = originalSetInterval;
|
|
1530
|
+
global.clearInterval = originalClearInterval;
|
|
1531
|
+
}
|
|
1532
|
+
};
|
|
1533
|
+
}
|
|
1534
|
+
function simulateFrames(count, frameTimeMs = 16.6, callback) {
|
|
1535
|
+
if (!activeMockRAF) {
|
|
1536
|
+
throw new Error("simulateFrames requires an active MockRAF. Ensure createMockRAF().enable() is called.");
|
|
1537
|
+
}
|
|
1538
|
+
for (let i = 0; i < count; i++) {
|
|
1539
|
+
if (callback) callback(i);
|
|
1540
|
+
activeMockRAF.advance(frameTimeMs);
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
function simulateFramesWithMock(mock, count, frameTimeMs = 16.6, callback) {
|
|
1544
|
+
for (let i = 0; i < count; i++) {
|
|
1545
|
+
if (callback) callback(i);
|
|
1546
|
+
mock.advance(frameTimeMs);
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
// src/e2e/playwright.ts
|
|
1551
|
+
var import_playwright = require("playwright");
|
|
1552
|
+
var import_http = require("http");
|
|
1553
|
+
var import_serve_handler = __toESM(require("serve-handler"), 1);
|
|
1554
|
+
async function createPlaywrightTestClient(options = {}) {
|
|
1555
|
+
let staticServer;
|
|
1556
|
+
let clientUrl = options.clientUrl;
|
|
1557
|
+
const rootPath = options.rootPath || process.cwd();
|
|
1558
|
+
if (!clientUrl) {
|
|
1559
|
+
staticServer = (0, import_http.createServer)((request, response) => {
|
|
1560
|
+
return (0, import_serve_handler.default)(request, response, {
|
|
1561
|
+
public: rootPath,
|
|
1562
|
+
cleanUrls: false,
|
|
1563
|
+
headers: [
|
|
1564
|
+
{
|
|
1565
|
+
source: "**/*",
|
|
1566
|
+
headers: [
|
|
1567
|
+
{ key: "Cache-Control", value: "no-cache" },
|
|
1568
|
+
{ key: "Access-Control-Allow-Origin", value: "*" },
|
|
1569
|
+
{ key: "Cross-Origin-Opener-Policy", value: "same-origin" },
|
|
1570
|
+
{ key: "Cross-Origin-Embedder-Policy", value: "require-corp" }
|
|
1571
|
+
]
|
|
1572
|
+
}
|
|
1573
|
+
]
|
|
1574
|
+
});
|
|
1575
|
+
});
|
|
1576
|
+
await new Promise((resolve) => {
|
|
1577
|
+
if (!staticServer) return;
|
|
1578
|
+
staticServer.listen(0, () => {
|
|
1579
|
+
const addr = staticServer?.address();
|
|
1580
|
+
const port = typeof addr === "object" ? addr?.port : 0;
|
|
1581
|
+
clientUrl = `http://localhost:${port}`;
|
|
1582
|
+
console.log(`Test client serving from ${rootPath} at ${clientUrl}`);
|
|
1583
|
+
resolve();
|
|
1584
|
+
});
|
|
1585
|
+
});
|
|
1586
|
+
}
|
|
1587
|
+
const browser = await import_playwright.chromium.launch({
|
|
1588
|
+
headless: options.headless ?? true,
|
|
1589
|
+
args: [
|
|
1590
|
+
"--use-gl=egl",
|
|
1591
|
+
"--ignore-gpu-blocklist",
|
|
1592
|
+
...options.launchOptions?.args || []
|
|
1593
|
+
],
|
|
1594
|
+
...options.launchOptions
|
|
1595
|
+
});
|
|
1596
|
+
const width = options.width || 1280;
|
|
1597
|
+
const height = options.height || 720;
|
|
1598
|
+
const context = await browser.newContext({
|
|
1599
|
+
viewport: { width, height },
|
|
1600
|
+
deviceScaleFactor: 1,
|
|
1601
|
+
...options.contextOptions
|
|
1602
|
+
});
|
|
1603
|
+
const page = await context.newPage();
|
|
1604
|
+
const close = async () => {
|
|
1605
|
+
await browser.close();
|
|
1606
|
+
if (staticServer) {
|
|
1607
|
+
staticServer.close();
|
|
1608
|
+
}
|
|
1609
|
+
};
|
|
1610
|
+
const navigate = async (url) => {
|
|
1611
|
+
const targetUrl = url || clientUrl;
|
|
1612
|
+
if (!targetUrl) throw new Error("No URL to navigate to");
|
|
1613
|
+
let finalUrl = targetUrl;
|
|
1614
|
+
if (options.serverUrl && !targetUrl.includes("connect=")) {
|
|
1615
|
+
const separator = targetUrl.includes("?") ? "&" : "?";
|
|
1616
|
+
finalUrl = `${targetUrl}${separator}connect=${encodeURIComponent(options.serverUrl)}`;
|
|
1617
|
+
}
|
|
1618
|
+
console.log(`Navigating to: ${finalUrl}`);
|
|
1619
|
+
await page.goto(finalUrl, { waitUntil: "domcontentloaded" });
|
|
1620
|
+
};
|
|
1621
|
+
return {
|
|
1622
|
+
browser,
|
|
1623
|
+
context,
|
|
1624
|
+
page,
|
|
1625
|
+
server: staticServer,
|
|
1626
|
+
close,
|
|
1627
|
+
navigate,
|
|
1628
|
+
waitForGame: async (timeout = 1e4) => {
|
|
1629
|
+
await waitForGameReady(page, timeout);
|
|
1328
1630
|
},
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
activeCount: entityList.length,
|
|
1335
|
-
world: entityList.find((e) => e.classname === "worldspawn") || new import_game.Entity(0)
|
|
1336
|
-
// ... other EntitySystem properties would go here
|
|
1631
|
+
injectInput: async (type, data) => {
|
|
1632
|
+
await page.evaluate(({ type: type2, data: data2 }) => {
|
|
1633
|
+
if (window.injectGameInput) window.injectGameInput(type2, data2);
|
|
1634
|
+
}, { type, data });
|
|
1635
|
+
}
|
|
1337
1636
|
};
|
|
1637
|
+
}
|
|
1638
|
+
async function waitForGameReady(page, timeout = 1e4) {
|
|
1639
|
+
try {
|
|
1640
|
+
await page.waitForFunction(() => {
|
|
1641
|
+
return window.gameInstance && window.gameInstance.isReady;
|
|
1642
|
+
}, null, { timeout });
|
|
1643
|
+
} catch (e) {
|
|
1644
|
+
await page.waitForSelector("canvas", { timeout });
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
async function captureGameState(page) {
|
|
1648
|
+
return await page.evaluate(() => {
|
|
1649
|
+
if (window.gameInstance && window.gameInstance.getState) {
|
|
1650
|
+
return window.gameInstance.getState();
|
|
1651
|
+
}
|
|
1652
|
+
return {};
|
|
1653
|
+
});
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
// src/e2e/network.ts
|
|
1657
|
+
var CONDITIONS = {
|
|
1658
|
+
"good": {
|
|
1659
|
+
offline: false,
|
|
1660
|
+
downloadThroughput: 10 * 1024 * 1024,
|
|
1661
|
+
// 10 Mbps
|
|
1662
|
+
uploadThroughput: 5 * 1024 * 1024,
|
|
1663
|
+
// 5 Mbps
|
|
1664
|
+
latency: 20
|
|
1665
|
+
},
|
|
1666
|
+
"slow": {
|
|
1667
|
+
offline: false,
|
|
1668
|
+
downloadThroughput: 500 * 1024,
|
|
1669
|
+
// 500 Kbps
|
|
1670
|
+
uploadThroughput: 500 * 1024,
|
|
1671
|
+
latency: 400
|
|
1672
|
+
},
|
|
1673
|
+
"unstable": {
|
|
1674
|
+
offline: false,
|
|
1675
|
+
downloadThroughput: 1 * 1024 * 1024,
|
|
1676
|
+
uploadThroughput: 1 * 1024 * 1024,
|
|
1677
|
+
latency: 100
|
|
1678
|
+
},
|
|
1679
|
+
"offline": {
|
|
1680
|
+
offline: true,
|
|
1681
|
+
downloadThroughput: 0,
|
|
1682
|
+
uploadThroughput: 0,
|
|
1683
|
+
latency: 0
|
|
1684
|
+
}
|
|
1685
|
+
};
|
|
1686
|
+
function simulateNetworkCondition(condition) {
|
|
1687
|
+
const config = CONDITIONS[condition];
|
|
1688
|
+
return createCustomNetworkCondition(config.latency, 0, 0, config);
|
|
1689
|
+
}
|
|
1690
|
+
function createCustomNetworkCondition(latency, jitter = 0, packetLoss = 0, baseConfig) {
|
|
1338
1691
|
return {
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1692
|
+
async apply(page) {
|
|
1693
|
+
const client = await page.context().newCDPSession(page);
|
|
1694
|
+
await client.send("Network.enable");
|
|
1695
|
+
await client.send("Network.emulateNetworkConditions", {
|
|
1696
|
+
offline: baseConfig?.offline || false,
|
|
1697
|
+
latency: latency + Math.random() * jitter,
|
|
1698
|
+
downloadThroughput: baseConfig?.downloadThroughput || -1,
|
|
1699
|
+
uploadThroughput: baseConfig?.uploadThroughput || -1
|
|
1700
|
+
});
|
|
1701
|
+
},
|
|
1702
|
+
async clear(page) {
|
|
1703
|
+
const client = await page.context().newCDPSession(page);
|
|
1704
|
+
await client.send("Network.emulateNetworkConditions", {
|
|
1705
|
+
offline: false,
|
|
1706
|
+
latency: 0,
|
|
1707
|
+
downloadThroughput: -1,
|
|
1708
|
+
uploadThroughput: -1
|
|
1709
|
+
});
|
|
1710
|
+
}
|
|
1350
1711
|
};
|
|
1351
1712
|
}
|
|
1352
|
-
function
|
|
1353
|
-
|
|
1713
|
+
async function throttleBandwidth(page, bytesPerSecond) {
|
|
1714
|
+
const simulator = createCustomNetworkCondition(0, 0, 0, {
|
|
1715
|
+
offline: false,
|
|
1716
|
+
latency: 0,
|
|
1717
|
+
downloadThroughput: bytesPerSecond,
|
|
1718
|
+
uploadThroughput: bytesPerSecond
|
|
1719
|
+
});
|
|
1720
|
+
await simulator.apply(page);
|
|
1354
1721
|
}
|
|
1355
|
-
|
|
1356
|
-
|
|
1722
|
+
|
|
1723
|
+
// src/e2e/visual.ts
|
|
1724
|
+
var import_path = __toESM(require("path"), 1);
|
|
1725
|
+
var import_promises = __toESM(require("fs/promises"), 1);
|
|
1726
|
+
async function captureGameScreenshot(page, name, options = {}) {
|
|
1727
|
+
const dir = options.dir || "__screenshots__";
|
|
1728
|
+
const screenshotPath = import_path.default.join(dir, `${name}.png`);
|
|
1729
|
+
await import_promises.default.mkdir(dir, { recursive: true });
|
|
1730
|
+
return await page.screenshot({
|
|
1731
|
+
path: screenshotPath,
|
|
1732
|
+
fullPage: options.fullPage ?? false,
|
|
1733
|
+
animations: "disabled",
|
|
1734
|
+
caret: "hide"
|
|
1735
|
+
});
|
|
1736
|
+
}
|
|
1737
|
+
async function compareScreenshots(baseline, current, threshold = 0.1) {
|
|
1738
|
+
if (baseline.equals(current)) {
|
|
1739
|
+
return { pixelDiff: 0, matched: true };
|
|
1740
|
+
}
|
|
1741
|
+
return {
|
|
1742
|
+
pixelDiff: -1,
|
|
1743
|
+
// Unknown magnitude
|
|
1744
|
+
matched: false
|
|
1745
|
+
};
|
|
1746
|
+
}
|
|
1747
|
+
function createVisualTestScenario(page, sceneName) {
|
|
1748
|
+
return {
|
|
1749
|
+
async capture(snapshotName) {
|
|
1750
|
+
return await captureGameScreenshot(page, `${sceneName}-${snapshotName}`);
|
|
1751
|
+
},
|
|
1752
|
+
async compare(snapshotName, baselineDir) {
|
|
1753
|
+
const name = `${sceneName}-${snapshotName}`;
|
|
1754
|
+
const current = await captureGameScreenshot(page, name, { dir: "__screenshots__/current" });
|
|
1755
|
+
try {
|
|
1756
|
+
const baselinePath = import_path.default.join(baselineDir, `${name}.png`);
|
|
1757
|
+
const baseline = await import_promises.default.readFile(baselinePath);
|
|
1758
|
+
return await compareScreenshots(baseline, current);
|
|
1759
|
+
} catch (e) {
|
|
1760
|
+
return { pixelDiff: -1, matched: false };
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
};
|
|
1357
1764
|
}
|
|
1358
1765
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1359
1766
|
0 && (module.exports = {
|
|
1360
1767
|
InputInjector,
|
|
1361
1768
|
MockPointerLock,
|
|
1769
|
+
MockTransport,
|
|
1362
1770
|
captureAudioEvents,
|
|
1363
1771
|
captureCanvasDrawCalls,
|
|
1772
|
+
captureGameScreenshot,
|
|
1364
1773
|
captureGameState,
|
|
1774
|
+
compareScreenshots,
|
|
1365
1775
|
createBinaryStreamMock,
|
|
1366
1776
|
createBinaryWriterMock,
|
|
1367
1777
|
createControlledTimer,
|
|
1778
|
+
createCustomNetworkCondition,
|
|
1368
1779
|
createEntity,
|
|
1369
1780
|
createEntityStateFactory,
|
|
1370
1781
|
createGameStateSnapshotFactory,
|
|
@@ -1373,13 +1784,21 @@ function createEntity() {
|
|
|
1373
1784
|
createMockCanvasContext2D,
|
|
1374
1785
|
createMockEngine,
|
|
1375
1786
|
createMockGame,
|
|
1787
|
+
createMockGameState,
|
|
1376
1788
|
createMockImage,
|
|
1377
1789
|
createMockImageData,
|
|
1378
1790
|
createMockIndexedDB,
|
|
1379
1791
|
createMockLocalStorage,
|
|
1792
|
+
createMockNetworkAddress,
|
|
1380
1793
|
createMockPerformance,
|
|
1381
1794
|
createMockRAF,
|
|
1795
|
+
createMockServer,
|
|
1796
|
+
createMockServerClient,
|
|
1797
|
+
createMockServerState,
|
|
1798
|
+
createMockServerStatic,
|
|
1382
1799
|
createMockSessionStorage,
|
|
1800
|
+
createMockTransport,
|
|
1801
|
+
createMockUDPSocket,
|
|
1383
1802
|
createMockWebGL2Context,
|
|
1384
1803
|
createNetChanMock,
|
|
1385
1804
|
createPlayerStateFactory,
|
|
@@ -1387,6 +1806,7 @@ function createEntity() {
|
|
|
1387
1806
|
createSpawnContext,
|
|
1388
1807
|
createStorageTestScenario,
|
|
1389
1808
|
createTestContext,
|
|
1809
|
+
createVisualTestScenario,
|
|
1390
1810
|
intersects,
|
|
1391
1811
|
ladderTrace,
|
|
1392
1812
|
makeAxisBrush,
|
|
@@ -1401,9 +1821,11 @@ function createEntity() {
|
|
|
1401
1821
|
setupNodeEnvironment,
|
|
1402
1822
|
simulateFrames,
|
|
1403
1823
|
simulateFramesWithMock,
|
|
1824
|
+
simulateNetworkCondition,
|
|
1404
1825
|
stairTrace,
|
|
1405
1826
|
teardownBrowserEnvironment,
|
|
1406
1827
|
teardownMockAudioContext,
|
|
1828
|
+
throttleBandwidth,
|
|
1407
1829
|
waitForGameReady
|
|
1408
1830
|
});
|
|
1409
1831
|
//# sourceMappingURL=index.cjs.map
|