quake2ts 0.0.557 → 0.0.561
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 +3 -1
- package/packages/client/dist/browser/index.global.js +12 -12
- package/packages/client/dist/browser/index.global.js.map +1 -1
- package/packages/client/dist/cjs/index.cjs +70 -0
- package/packages/client/dist/cjs/index.cjs.map +1 -1
- package/packages/client/dist/esm/index.js +70 -0
- package/packages/client/dist/esm/index.js.map +1 -1
- package/packages/client/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/client/dist/types/index.d.ts.map +1 -1
- package/packages/engine/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/engine/dist/types/render/bloom.d.ts +19 -0
- package/packages/engine/dist/types/render/bloom.d.ts.map +1 -0
- package/packages/engine/dist/types/render/frame.d.ts +2 -0
- package/packages/engine/dist/types/render/frame.d.ts.map +1 -1
- package/packages/engine/dist/types/render/renderer.d.ts +2 -0
- package/packages/engine/dist/types/render/renderer.d.ts.map +1 -1
- package/packages/game/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/shared/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/test-utils/dist/index.cjs +677 -693
- package/packages/test-utils/dist/index.cjs.map +1 -1
- package/packages/test-utils/dist/index.d.cts +198 -266
- package/packages/test-utils/dist/index.d.ts +198 -266
- package/packages/test-utils/dist/index.js +679 -690
- package/packages/test-utils/dist/index.js.map +1 -1
- package/packages/tools/dist/tsconfig.tsbuildinfo +1 -1
|
@@ -34,12 +34,10 @@ __export(index_exports, {
|
|
|
34
34
|
MockPointerLock: () => MockPointerLock,
|
|
35
35
|
captureAudioEvents: () => captureAudioEvents,
|
|
36
36
|
captureCanvasDrawCalls: () => captureCanvasDrawCalls,
|
|
37
|
-
captureGameScreenshot: () => captureGameScreenshot,
|
|
38
37
|
captureGameState: () => captureGameState,
|
|
39
|
-
compareScreenshots: () => compareScreenshots,
|
|
40
38
|
createBinaryStreamMock: () => createBinaryStreamMock,
|
|
41
39
|
createBinaryWriterMock: () => createBinaryWriterMock,
|
|
42
|
-
|
|
40
|
+
createControlledTimer: () => createControlledTimer,
|
|
43
41
|
createEntity: () => createEntity,
|
|
44
42
|
createEntityStateFactory: () => createEntityStateFactory,
|
|
45
43
|
createGameStateSnapshotFactory: () => createGameStateSnapshotFactory,
|
|
@@ -62,7 +60,6 @@ __export(index_exports, {
|
|
|
62
60
|
createSpawnContext: () => createSpawnContext,
|
|
63
61
|
createStorageTestScenario: () => createStorageTestScenario,
|
|
64
62
|
createTestContext: () => createTestContext,
|
|
65
|
-
createVisualTestScenario: () => createVisualTestScenario,
|
|
66
63
|
intersects: () => import_shared3.intersects,
|
|
67
64
|
ladderTrace: () => import_shared3.ladderTrace,
|
|
68
65
|
makeAxisBrush: () => makeAxisBrush,
|
|
@@ -77,405 +74,13 @@ __export(index_exports, {
|
|
|
77
74
|
setupNodeEnvironment: () => setupNodeEnvironment,
|
|
78
75
|
simulateFrames: () => simulateFrames,
|
|
79
76
|
simulateFramesWithMock: () => simulateFramesWithMock,
|
|
80
|
-
simulateNetworkCondition: () => simulateNetworkCondition,
|
|
81
77
|
stairTrace: () => import_shared3.stairTrace,
|
|
82
78
|
teardownBrowserEnvironment: () => teardownBrowserEnvironment,
|
|
83
79
|
teardownMockAudioContext: () => teardownMockAudioContext,
|
|
84
|
-
throttleBandwidth: () => throttleBandwidth,
|
|
85
80
|
waitForGameReady: () => waitForGameReady
|
|
86
81
|
});
|
|
87
82
|
module.exports = __toCommonJS(index_exports);
|
|
88
83
|
|
|
89
|
-
// src/shared/mocks.ts
|
|
90
|
-
var import_vitest = require("vitest");
|
|
91
|
-
var createBinaryWriterMock = () => ({
|
|
92
|
-
writeByte: import_vitest.vi.fn(),
|
|
93
|
-
writeShort: import_vitest.vi.fn(),
|
|
94
|
-
writeLong: import_vitest.vi.fn(),
|
|
95
|
-
writeString: import_vitest.vi.fn(),
|
|
96
|
-
writeBytes: import_vitest.vi.fn(),
|
|
97
|
-
getBuffer: import_vitest.vi.fn(() => new Uint8Array(0)),
|
|
98
|
-
reset: import_vitest.vi.fn(),
|
|
99
|
-
// Legacy methods (if any)
|
|
100
|
-
writeInt8: import_vitest.vi.fn(),
|
|
101
|
-
writeUint8: import_vitest.vi.fn(),
|
|
102
|
-
writeInt16: import_vitest.vi.fn(),
|
|
103
|
-
writeUint16: import_vitest.vi.fn(),
|
|
104
|
-
writeInt32: import_vitest.vi.fn(),
|
|
105
|
-
writeUint32: import_vitest.vi.fn(),
|
|
106
|
-
writeFloat: import_vitest.vi.fn(),
|
|
107
|
-
getData: import_vitest.vi.fn(() => new Uint8Array(0))
|
|
108
|
-
});
|
|
109
|
-
var createNetChanMock = () => ({
|
|
110
|
-
qport: 1234,
|
|
111
|
-
// Sequencing
|
|
112
|
-
incomingSequence: 0,
|
|
113
|
-
outgoingSequence: 0,
|
|
114
|
-
incomingAcknowledged: 0,
|
|
115
|
-
// Reliable messaging
|
|
116
|
-
incomingReliableAcknowledged: false,
|
|
117
|
-
incomingReliableSequence: 0,
|
|
118
|
-
outgoingReliableSequence: 0,
|
|
119
|
-
reliableMessage: createBinaryWriterMock(),
|
|
120
|
-
reliableLength: 0,
|
|
121
|
-
// Fragmentation
|
|
122
|
-
fragmentSendOffset: 0,
|
|
123
|
-
fragmentBuffer: null,
|
|
124
|
-
fragmentLength: 0,
|
|
125
|
-
fragmentReceived: 0,
|
|
126
|
-
// Timing
|
|
127
|
-
lastReceived: 0,
|
|
128
|
-
lastSent: 0,
|
|
129
|
-
remoteAddress: { type: "IP", port: 1234 },
|
|
130
|
-
// Methods
|
|
131
|
-
setup: import_vitest.vi.fn(),
|
|
132
|
-
reset: import_vitest.vi.fn(),
|
|
133
|
-
transmit: import_vitest.vi.fn(),
|
|
134
|
-
process: import_vitest.vi.fn(),
|
|
135
|
-
canSendReliable: import_vitest.vi.fn(() => true),
|
|
136
|
-
writeReliableByte: import_vitest.vi.fn(),
|
|
137
|
-
writeReliableShort: import_vitest.vi.fn(),
|
|
138
|
-
writeReliableLong: import_vitest.vi.fn(),
|
|
139
|
-
writeReliableString: import_vitest.vi.fn(),
|
|
140
|
-
getReliableData: import_vitest.vi.fn(() => new Uint8Array(0)),
|
|
141
|
-
needsKeepalive: import_vitest.vi.fn(() => false),
|
|
142
|
-
isTimedOut: import_vitest.vi.fn(() => false)
|
|
143
|
-
});
|
|
144
|
-
var createBinaryStreamMock = () => ({
|
|
145
|
-
getPosition: import_vitest.vi.fn(() => 0),
|
|
146
|
-
getReadPosition: import_vitest.vi.fn(() => 0),
|
|
147
|
-
getLength: import_vitest.vi.fn(() => 0),
|
|
148
|
-
getRemaining: import_vitest.vi.fn(() => 0),
|
|
149
|
-
seek: import_vitest.vi.fn(),
|
|
150
|
-
setReadPosition: import_vitest.vi.fn(),
|
|
151
|
-
hasMore: import_vitest.vi.fn(() => true),
|
|
152
|
-
hasBytes: import_vitest.vi.fn((amount) => true),
|
|
153
|
-
readChar: import_vitest.vi.fn(() => 0),
|
|
154
|
-
readByte: import_vitest.vi.fn(() => 0),
|
|
155
|
-
readShort: import_vitest.vi.fn(() => 0),
|
|
156
|
-
readUShort: import_vitest.vi.fn(() => 0),
|
|
157
|
-
readLong: import_vitest.vi.fn(() => 0),
|
|
158
|
-
readULong: import_vitest.vi.fn(() => 0),
|
|
159
|
-
readFloat: import_vitest.vi.fn(() => 0),
|
|
160
|
-
readString: import_vitest.vi.fn(() => ""),
|
|
161
|
-
readStringLine: import_vitest.vi.fn(() => ""),
|
|
162
|
-
readCoord: import_vitest.vi.fn(() => 0),
|
|
163
|
-
readAngle: import_vitest.vi.fn(() => 0),
|
|
164
|
-
readAngle16: import_vitest.vi.fn(() => 0),
|
|
165
|
-
readData: import_vitest.vi.fn((length) => new Uint8Array(length)),
|
|
166
|
-
readPos: import_vitest.vi.fn(),
|
|
167
|
-
readDir: import_vitest.vi.fn()
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
// src/shared/bsp.ts
|
|
171
|
-
var import_shared = require("@quake2ts/shared");
|
|
172
|
-
function makePlane(normal, dist) {
|
|
173
|
-
return {
|
|
174
|
-
normal,
|
|
175
|
-
dist,
|
|
176
|
-
type: Math.abs(normal.x) === 1 ? 0 : Math.abs(normal.y) === 1 ? 1 : Math.abs(normal.z) === 1 ? 2 : 3,
|
|
177
|
-
signbits: (0, import_shared.computePlaneSignBits)(normal)
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
function makeAxisBrush(size, contents = import_shared.CONTENTS_SOLID) {
|
|
181
|
-
const half = size / 2;
|
|
182
|
-
const planes = [
|
|
183
|
-
makePlane({ x: 1, y: 0, z: 0 }, half),
|
|
184
|
-
makePlane({ x: -1, y: 0, z: 0 }, half),
|
|
185
|
-
makePlane({ x: 0, y: 1, z: 0 }, half),
|
|
186
|
-
makePlane({ x: 0, y: -1, z: 0 }, half),
|
|
187
|
-
makePlane({ x: 0, y: 0, z: 1 }, half),
|
|
188
|
-
makePlane({ x: 0, y: 0, z: -1 }, half)
|
|
189
|
-
];
|
|
190
|
-
return {
|
|
191
|
-
contents,
|
|
192
|
-
sides: planes.map((plane) => ({ plane, surfaceFlags: 0 }))
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
function makeNode(plane, children) {
|
|
196
|
-
return { plane, children };
|
|
197
|
-
}
|
|
198
|
-
function makeBspModel(planes, nodes, leaves, brushes, leafBrushes) {
|
|
199
|
-
return {
|
|
200
|
-
planes,
|
|
201
|
-
nodes,
|
|
202
|
-
leaves,
|
|
203
|
-
brushes,
|
|
204
|
-
leafBrushes,
|
|
205
|
-
bmodels: []
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
function makeLeaf(contents, firstLeafBrush, numLeafBrushes) {
|
|
209
|
-
return { contents, cluster: 0, area: 0, firstLeafBrush, numLeafBrushes };
|
|
210
|
-
}
|
|
211
|
-
function makeLeafModel(brushes) {
|
|
212
|
-
const planes = brushes.flatMap((brush) => brush.sides.map((side) => side.plane));
|
|
213
|
-
return {
|
|
214
|
-
planes,
|
|
215
|
-
nodes: [],
|
|
216
|
-
leaves: [makeLeaf(0, 0, brushes.length)],
|
|
217
|
-
brushes,
|
|
218
|
-
leafBrushes: brushes.map((_, i) => i),
|
|
219
|
-
bmodels: []
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
function makeBrushFromMinsMaxs(mins, maxs, contents = import_shared.CONTENTS_SOLID) {
|
|
223
|
-
const planes = [
|
|
224
|
-
makePlane({ x: 1, y: 0, z: 0 }, maxs.x),
|
|
225
|
-
makePlane({ x: -1, y: 0, z: 0 }, -mins.x),
|
|
226
|
-
makePlane({ x: 0, y: 1, z: 0 }, maxs.y),
|
|
227
|
-
makePlane({ x: 0, y: -1, z: 0 }, -mins.y),
|
|
228
|
-
makePlane({ x: 0, y: 0, z: 1 }, maxs.z),
|
|
229
|
-
makePlane({ x: 0, y: 0, z: -1 }, -mins.z)
|
|
230
|
-
];
|
|
231
|
-
return {
|
|
232
|
-
contents,
|
|
233
|
-
sides: planes.map((plane) => ({ plane, surfaceFlags: 0 }))
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// src/game/factories.ts
|
|
238
|
-
var createPlayerStateFactory = (overrides) => ({
|
|
239
|
-
pm_type: 0,
|
|
240
|
-
pm_time: 0,
|
|
241
|
-
pm_flags: 0,
|
|
242
|
-
origin: { x: 0, y: 0, z: 0 },
|
|
243
|
-
velocity: { x: 0, y: 0, z: 0 },
|
|
244
|
-
viewAngles: { x: 0, y: 0, z: 0 },
|
|
245
|
-
onGround: false,
|
|
246
|
-
waterLevel: 0,
|
|
247
|
-
watertype: 0,
|
|
248
|
-
mins: { x: 0, y: 0, z: 0 },
|
|
249
|
-
maxs: { x: 0, y: 0, z: 0 },
|
|
250
|
-
damageAlpha: 0,
|
|
251
|
-
damageIndicators: [],
|
|
252
|
-
blend: [0, 0, 0, 0],
|
|
253
|
-
stats: [],
|
|
254
|
-
kick_angles: { x: 0, y: 0, z: 0 },
|
|
255
|
-
kick_origin: { x: 0, y: 0, z: 0 },
|
|
256
|
-
gunoffset: { x: 0, y: 0, z: 0 },
|
|
257
|
-
gunangles: { x: 0, y: 0, z: 0 },
|
|
258
|
-
gunindex: 0,
|
|
259
|
-
gun_frame: 0,
|
|
260
|
-
rdflags: 0,
|
|
261
|
-
fov: 90,
|
|
262
|
-
renderfx: 0,
|
|
263
|
-
...overrides
|
|
264
|
-
});
|
|
265
|
-
var createEntityStateFactory = (overrides) => ({
|
|
266
|
-
number: 0,
|
|
267
|
-
origin: { x: 0, y: 0, z: 0 },
|
|
268
|
-
angles: { x: 0, y: 0, z: 0 },
|
|
269
|
-
oldOrigin: { x: 0, y: 0, z: 0 },
|
|
270
|
-
modelIndex: 0,
|
|
271
|
-
modelIndex2: 0,
|
|
272
|
-
modelIndex3: 0,
|
|
273
|
-
modelIndex4: 0,
|
|
274
|
-
frame: 0,
|
|
275
|
-
skinNum: 0,
|
|
276
|
-
effects: 0,
|
|
277
|
-
renderfx: 0,
|
|
278
|
-
solid: 0,
|
|
279
|
-
sound: 0,
|
|
280
|
-
event: 0,
|
|
281
|
-
...overrides
|
|
282
|
-
});
|
|
283
|
-
var createGameStateSnapshotFactory = (overrides) => ({
|
|
284
|
-
gravity: { x: 0, y: 0, z: -800 },
|
|
285
|
-
origin: { x: 0, y: 0, z: 0 },
|
|
286
|
-
velocity: { x: 0, y: 0, z: 0 },
|
|
287
|
-
viewangles: { x: 0, y: 0, z: 0 },
|
|
288
|
-
level: { timeSeconds: 0, frameNumber: 0, previousTimeSeconds: 0, deltaSeconds: 0.1 },
|
|
289
|
-
entities: {
|
|
290
|
-
activeCount: 0,
|
|
291
|
-
worldClassname: "worldspawn"
|
|
292
|
-
},
|
|
293
|
-
packetEntities: [],
|
|
294
|
-
pmFlags: 0,
|
|
295
|
-
pmType: 0,
|
|
296
|
-
waterlevel: 0,
|
|
297
|
-
watertype: 0,
|
|
298
|
-
deltaAngles: { x: 0, y: 0, z: 0 },
|
|
299
|
-
health: 100,
|
|
300
|
-
armor: 0,
|
|
301
|
-
ammo: 0,
|
|
302
|
-
blend: [0, 0, 0, 0],
|
|
303
|
-
damageAlpha: 0,
|
|
304
|
-
damageIndicators: [],
|
|
305
|
-
stats: [],
|
|
306
|
-
kick_angles: { x: 0, y: 0, z: 0 },
|
|
307
|
-
kick_origin: { x: 0, y: 0, z: 0 },
|
|
308
|
-
gunoffset: { x: 0, y: 0, z: 0 },
|
|
309
|
-
gunangles: { x: 0, y: 0, z: 0 },
|
|
310
|
-
gunindex: 0,
|
|
311
|
-
pm_time: 0,
|
|
312
|
-
gun_frame: 0,
|
|
313
|
-
rdflags: 0,
|
|
314
|
-
fov: 90,
|
|
315
|
-
renderfx: 0,
|
|
316
|
-
pm_flags: 0,
|
|
317
|
-
pm_type: 0,
|
|
318
|
-
...overrides
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
// src/game/helpers.ts
|
|
322
|
-
var import_vitest2 = require("vitest");
|
|
323
|
-
var import_game = require("@quake2ts/game");
|
|
324
|
-
var import_shared2 = require("@quake2ts/shared");
|
|
325
|
-
var import_shared3 = require("@quake2ts/shared");
|
|
326
|
-
var createMockEngine = () => ({
|
|
327
|
-
sound: import_vitest2.vi.fn(),
|
|
328
|
-
soundIndex: import_vitest2.vi.fn((sound) => 0),
|
|
329
|
-
modelIndex: import_vitest2.vi.fn((model) => 0),
|
|
330
|
-
centerprintf: import_vitest2.vi.fn()
|
|
331
|
-
});
|
|
332
|
-
var createMockGame = (seed = 12345) => {
|
|
333
|
-
const spawnRegistry = new import_game.SpawnRegistry();
|
|
334
|
-
const hooks = new import_game.ScriptHookRegistry();
|
|
335
|
-
const game = {
|
|
336
|
-
random: (0, import_shared2.createRandomGenerator)({ seed }),
|
|
337
|
-
registerEntitySpawn: import_vitest2.vi.fn((classname, spawnFunc) => {
|
|
338
|
-
spawnRegistry.register(classname, (entity) => spawnFunc(entity));
|
|
339
|
-
}),
|
|
340
|
-
unregisterEntitySpawn: import_vitest2.vi.fn((classname) => {
|
|
341
|
-
spawnRegistry.unregister(classname);
|
|
342
|
-
}),
|
|
343
|
-
getCustomEntities: import_vitest2.vi.fn(() => Array.from(spawnRegistry.keys())),
|
|
344
|
-
hooks,
|
|
345
|
-
registerHooks: import_vitest2.vi.fn((newHooks) => hooks.register(newHooks)),
|
|
346
|
-
spawnWorld: import_vitest2.vi.fn(() => {
|
|
347
|
-
hooks.onMapLoad("q2dm1");
|
|
348
|
-
}),
|
|
349
|
-
clientBegin: import_vitest2.vi.fn((client) => {
|
|
350
|
-
hooks.onPlayerSpawn({});
|
|
351
|
-
}),
|
|
352
|
-
damage: import_vitest2.vi.fn((amount) => {
|
|
353
|
-
hooks.onDamage({}, null, null, amount, 0, 0);
|
|
354
|
-
})
|
|
355
|
-
};
|
|
356
|
-
return { game, spawnRegistry };
|
|
357
|
-
};
|
|
358
|
-
function createTestContext(options) {
|
|
359
|
-
const engine = createMockEngine();
|
|
360
|
-
const seed = options?.seed ?? 12345;
|
|
361
|
-
const { game, spawnRegistry } = createMockGame(seed);
|
|
362
|
-
const traceFn = import_vitest2.vi.fn((start, end, mins, maxs) => ({
|
|
363
|
-
fraction: 1,
|
|
364
|
-
ent: null,
|
|
365
|
-
allsolid: false,
|
|
366
|
-
startsolid: false,
|
|
367
|
-
endpos: end,
|
|
368
|
-
plane: { normal: { x: 0, y: 0, z: 1 }, dist: 0 },
|
|
369
|
-
surfaceFlags: 0,
|
|
370
|
-
contents: 0
|
|
371
|
-
}));
|
|
372
|
-
const entityList = options?.initialEntities ? [...options.initialEntities] : [];
|
|
373
|
-
const hooks = game.hooks;
|
|
374
|
-
const entities = {
|
|
375
|
-
spawn: import_vitest2.vi.fn(() => {
|
|
376
|
-
const ent = new import_game.Entity(entityList.length + 1);
|
|
377
|
-
entityList.push(ent);
|
|
378
|
-
hooks.onEntitySpawn(ent);
|
|
379
|
-
return ent;
|
|
380
|
-
}),
|
|
381
|
-
free: import_vitest2.vi.fn((ent) => {
|
|
382
|
-
const idx = entityList.indexOf(ent);
|
|
383
|
-
if (idx !== -1) {
|
|
384
|
-
entityList.splice(idx, 1);
|
|
385
|
-
}
|
|
386
|
-
hooks.onEntityRemove(ent);
|
|
387
|
-
}),
|
|
388
|
-
finalizeSpawn: import_vitest2.vi.fn(),
|
|
389
|
-
freeImmediate: import_vitest2.vi.fn((ent) => {
|
|
390
|
-
const idx = entityList.indexOf(ent);
|
|
391
|
-
if (idx !== -1) {
|
|
392
|
-
entityList.splice(idx, 1);
|
|
393
|
-
}
|
|
394
|
-
}),
|
|
395
|
-
setSpawnRegistry: import_vitest2.vi.fn(),
|
|
396
|
-
timeSeconds: 10,
|
|
397
|
-
deltaSeconds: 0.1,
|
|
398
|
-
modelIndex: import_vitest2.vi.fn(() => 0),
|
|
399
|
-
scheduleThink: import_vitest2.vi.fn((entity, time) => {
|
|
400
|
-
entity.nextthink = time;
|
|
401
|
-
}),
|
|
402
|
-
linkentity: import_vitest2.vi.fn(),
|
|
403
|
-
trace: traceFn,
|
|
404
|
-
pointcontents: import_vitest2.vi.fn(() => 0),
|
|
405
|
-
multicast: import_vitest2.vi.fn(),
|
|
406
|
-
unicast: import_vitest2.vi.fn(),
|
|
407
|
-
engine,
|
|
408
|
-
game,
|
|
409
|
-
sound: import_vitest2.vi.fn((ent, chan, sound, vol, attn, timeofs) => {
|
|
410
|
-
engine.sound(ent, chan, sound, vol, attn, timeofs);
|
|
411
|
-
}),
|
|
412
|
-
soundIndex: import_vitest2.vi.fn((sound) => engine.soundIndex(sound)),
|
|
413
|
-
useTargets: import_vitest2.vi.fn((entity, activator) => {
|
|
414
|
-
}),
|
|
415
|
-
findByTargetName: import_vitest2.vi.fn(() => []),
|
|
416
|
-
pickTarget: import_vitest2.vi.fn(() => null),
|
|
417
|
-
killBox: import_vitest2.vi.fn(),
|
|
418
|
-
rng: (0, import_shared2.createRandomGenerator)({ seed }),
|
|
419
|
-
imports: {
|
|
420
|
-
configstring: import_vitest2.vi.fn(),
|
|
421
|
-
trace: traceFn,
|
|
422
|
-
pointcontents: import_vitest2.vi.fn(() => 0)
|
|
423
|
-
},
|
|
424
|
-
level: {
|
|
425
|
-
intermission_angle: { x: 0, y: 0, z: 0 },
|
|
426
|
-
intermission_origin: { x: 0, y: 0, z: 0 },
|
|
427
|
-
next_auto_save: 0,
|
|
428
|
-
health_bar_entities: null
|
|
429
|
-
},
|
|
430
|
-
targetNameIndex: /* @__PURE__ */ new Map(),
|
|
431
|
-
forEachEntity: import_vitest2.vi.fn((callback) => {
|
|
432
|
-
entityList.forEach(callback);
|
|
433
|
-
}),
|
|
434
|
-
find: import_vitest2.vi.fn((predicate) => {
|
|
435
|
-
return entityList.find(predicate);
|
|
436
|
-
}),
|
|
437
|
-
findByClassname: import_vitest2.vi.fn((classname) => {
|
|
438
|
-
return entityList.find((e) => e.classname === classname);
|
|
439
|
-
}),
|
|
440
|
-
beginFrame: import_vitest2.vi.fn((timeSeconds) => {
|
|
441
|
-
entities.timeSeconds = timeSeconds;
|
|
442
|
-
}),
|
|
443
|
-
targetAwareness: {
|
|
444
|
-
timeSeconds: 10,
|
|
445
|
-
frameNumber: 1,
|
|
446
|
-
sightEntity: null,
|
|
447
|
-
soundEntity: null
|
|
448
|
-
},
|
|
449
|
-
// Adding missing properties to satisfy EntitySystem interface partially or fully
|
|
450
|
-
// We cast to unknown first anyway, but filling these in makes it safer for consumers
|
|
451
|
-
skill: 1,
|
|
452
|
-
deathmatch: false,
|
|
453
|
-
coop: false,
|
|
454
|
-
activeCount: entityList.length,
|
|
455
|
-
world: entityList.find((e) => e.classname === "worldspawn") || new import_game.Entity(0)
|
|
456
|
-
// ... other EntitySystem properties would go here
|
|
457
|
-
};
|
|
458
|
-
return {
|
|
459
|
-
keyValues: {},
|
|
460
|
-
entities,
|
|
461
|
-
game,
|
|
462
|
-
engine,
|
|
463
|
-
health_multiplier: 1,
|
|
464
|
-
warn: import_vitest2.vi.fn(),
|
|
465
|
-
free: import_vitest2.vi.fn(),
|
|
466
|
-
// Mock precache functions if they are part of SpawnContext in future or TestContext extensions
|
|
467
|
-
precacheModel: import_vitest2.vi.fn(),
|
|
468
|
-
precacheSound: import_vitest2.vi.fn(),
|
|
469
|
-
precacheImage: import_vitest2.vi.fn()
|
|
470
|
-
};
|
|
471
|
-
}
|
|
472
|
-
function createSpawnContext() {
|
|
473
|
-
return createTestContext();
|
|
474
|
-
}
|
|
475
|
-
function createEntity() {
|
|
476
|
-
return new import_game.Entity(1);
|
|
477
|
-
}
|
|
478
|
-
|
|
479
84
|
// src/setup/browser.ts
|
|
480
85
|
var import_jsdom = require("jsdom");
|
|
481
86
|
var import_canvas = require("@napi-rs/canvas");
|
|
@@ -871,12 +476,6 @@ function teardownBrowserEnvironment() {
|
|
|
871
476
|
delete global.UIEvent;
|
|
872
477
|
}
|
|
873
478
|
|
|
874
|
-
// src/setup/node.ts
|
|
875
|
-
function setupNodeEnvironment() {
|
|
876
|
-
if (typeof global.fetch === "undefined") {
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
|
|
880
479
|
// src/setup/canvas.ts
|
|
881
480
|
var import_canvas2 = require("@napi-rs/canvas");
|
|
882
481
|
function createMockCanvas(width = 300, height = 150) {
|
|
@@ -958,7 +557,127 @@ function createMockImage(width, height, src) {
|
|
|
958
557
|
return img;
|
|
959
558
|
}
|
|
960
559
|
|
|
560
|
+
// src/setup/node.ts
|
|
561
|
+
function setupNodeEnvironment() {
|
|
562
|
+
if (typeof global.fetch === "undefined") {
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// src/setup/storage.ts
|
|
567
|
+
var import_auto2 = require("fake-indexeddb/auto");
|
|
568
|
+
function createMockLocalStorage(initialData = {}) {
|
|
569
|
+
const storage = new Map(Object.entries(initialData));
|
|
570
|
+
return {
|
|
571
|
+
getItem: (key) => storage.get(key) || null,
|
|
572
|
+
setItem: (key, value) => storage.set(key, value),
|
|
573
|
+
removeItem: (key) => storage.delete(key),
|
|
574
|
+
clear: () => storage.clear(),
|
|
575
|
+
key: (index) => Array.from(storage.keys())[index] || null,
|
|
576
|
+
get length() {
|
|
577
|
+
return storage.size;
|
|
578
|
+
}
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
function createMockSessionStorage(initialData = {}) {
|
|
582
|
+
return createMockLocalStorage(initialData);
|
|
583
|
+
}
|
|
584
|
+
function createMockIndexedDB() {
|
|
585
|
+
if (typeof indexedDB === "undefined") {
|
|
586
|
+
throw new Error("IndexedDB mock not found. Ensure fake-indexeddb is loaded.");
|
|
587
|
+
}
|
|
588
|
+
return indexedDB;
|
|
589
|
+
}
|
|
590
|
+
function createStorageTestScenario(storageType = "local") {
|
|
591
|
+
const storage = storageType === "local" ? createMockLocalStorage() : createMockSessionStorage();
|
|
592
|
+
return {
|
|
593
|
+
storage,
|
|
594
|
+
populate(data) {
|
|
595
|
+
Object.entries(data).forEach(([k, v]) => storage.setItem(k, v));
|
|
596
|
+
},
|
|
597
|
+
verify(key, value) {
|
|
598
|
+
return storage.getItem(key) === value;
|
|
599
|
+
}
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// src/setup/audio.ts
|
|
604
|
+
function createMockAudioContext() {
|
|
605
|
+
return {
|
|
606
|
+
createGain: () => ({
|
|
607
|
+
connect: () => {
|
|
608
|
+
},
|
|
609
|
+
gain: { value: 1, setValueAtTime: () => {
|
|
610
|
+
} }
|
|
611
|
+
}),
|
|
612
|
+
createOscillator: () => ({
|
|
613
|
+
connect: () => {
|
|
614
|
+
},
|
|
615
|
+
start: () => {
|
|
616
|
+
},
|
|
617
|
+
stop: () => {
|
|
618
|
+
},
|
|
619
|
+
frequency: { value: 440 }
|
|
620
|
+
}),
|
|
621
|
+
createBufferSource: () => ({
|
|
622
|
+
connect: () => {
|
|
623
|
+
},
|
|
624
|
+
start: () => {
|
|
625
|
+
},
|
|
626
|
+
stop: () => {
|
|
627
|
+
},
|
|
628
|
+
buffer: null,
|
|
629
|
+
playbackRate: { value: 1 },
|
|
630
|
+
loop: false
|
|
631
|
+
}),
|
|
632
|
+
destination: {},
|
|
633
|
+
currentTime: 0,
|
|
634
|
+
state: "running",
|
|
635
|
+
resume: async () => {
|
|
636
|
+
},
|
|
637
|
+
suspend: async () => {
|
|
638
|
+
},
|
|
639
|
+
close: async () => {
|
|
640
|
+
},
|
|
641
|
+
decodeAudioData: async (buffer) => ({
|
|
642
|
+
duration: 1,
|
|
643
|
+
length: 44100,
|
|
644
|
+
sampleRate: 44100,
|
|
645
|
+
numberOfChannels: 2,
|
|
646
|
+
getChannelData: () => new Float32Array(44100)
|
|
647
|
+
}),
|
|
648
|
+
createBuffer: (channels, length, sampleRate) => ({
|
|
649
|
+
duration: length / sampleRate,
|
|
650
|
+
length,
|
|
651
|
+
sampleRate,
|
|
652
|
+
numberOfChannels: channels,
|
|
653
|
+
getChannelData: () => new Float32Array(length)
|
|
654
|
+
})
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
function setupMockAudioContext() {
|
|
658
|
+
if (typeof global.AudioContext === "undefined" && typeof global.window !== "undefined") {
|
|
659
|
+
global.AudioContext = class {
|
|
660
|
+
constructor() {
|
|
661
|
+
return createMockAudioContext();
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
global.window.AudioContext = global.AudioContext;
|
|
665
|
+
global.window.webkitAudioContext = global.AudioContext;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
function teardownMockAudioContext() {
|
|
669
|
+
if (global.AudioContext && global.AudioContext.toString().includes("class")) {
|
|
670
|
+
delete global.AudioContext;
|
|
671
|
+
delete global.window.AudioContext;
|
|
672
|
+
delete global.window.webkitAudioContext;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
function captureAudioEvents(context) {
|
|
676
|
+
return [];
|
|
677
|
+
}
|
|
678
|
+
|
|
961
679
|
// src/setup/timing.ts
|
|
680
|
+
var activeMockRAF;
|
|
962
681
|
function createMockRAF() {
|
|
963
682
|
let callbacks = [];
|
|
964
683
|
let nextId = 1;
|
|
@@ -998,10 +717,14 @@ function createMockRAF() {
|
|
|
998
717
|
currentTime = 0;
|
|
999
718
|
},
|
|
1000
719
|
enable() {
|
|
720
|
+
activeMockRAF = this;
|
|
1001
721
|
global.requestAnimationFrame = raf;
|
|
1002
722
|
global.cancelAnimationFrame = cancel;
|
|
1003
723
|
},
|
|
1004
724
|
disable() {
|
|
725
|
+
if (activeMockRAF === this) {
|
|
726
|
+
activeMockRAF = void 0;
|
|
727
|
+
}
|
|
1005
728
|
if (originalRAF) {
|
|
1006
729
|
global.requestAnimationFrame = originalRAF;
|
|
1007
730
|
} else {
|
|
@@ -1024,7 +747,6 @@ function createMockPerformance(startTime = 0) {
|
|
|
1024
747
|
timing: {
|
|
1025
748
|
navigationStart: startTime
|
|
1026
749
|
},
|
|
1027
|
-
// Add minimal navigation/resource timing interfaces to satisfy types if needed
|
|
1028
750
|
clearMarks: () => {
|
|
1029
751
|
},
|
|
1030
752
|
clearMeasures: () => {
|
|
@@ -1053,318 +775,585 @@ function createMockPerformance(startTime = 0) {
|
|
|
1053
775
|
mockPerf.setTime = (time) => {
|
|
1054
776
|
currentTime = time;
|
|
1055
777
|
};
|
|
1056
|
-
return mockPerf;
|
|
778
|
+
return mockPerf;
|
|
779
|
+
}
|
|
780
|
+
function createControlledTimer() {
|
|
781
|
+
let currentTime = 0;
|
|
782
|
+
let timers = [];
|
|
783
|
+
let nextId = 1;
|
|
784
|
+
const originalSetTimeout = global.setTimeout;
|
|
785
|
+
const originalClearTimeout = global.clearTimeout;
|
|
786
|
+
const originalSetInterval = global.setInterval;
|
|
787
|
+
const originalClearInterval = global.clearInterval;
|
|
788
|
+
const mockSetTimeout = (callback, delay = 0, ...args) => {
|
|
789
|
+
const id = nextId++;
|
|
790
|
+
timers.push({ id, callback, dueTime: currentTime + delay, args });
|
|
791
|
+
return id;
|
|
792
|
+
};
|
|
793
|
+
const mockClearTimeout = (id) => {
|
|
794
|
+
timers = timers.filter((t) => t.id !== id);
|
|
795
|
+
};
|
|
796
|
+
const mockSetInterval = (callback, delay = 0, ...args) => {
|
|
797
|
+
const id = nextId++;
|
|
798
|
+
timers.push({ id, callback, dueTime: currentTime + delay, interval: delay, args });
|
|
799
|
+
return id;
|
|
800
|
+
};
|
|
801
|
+
const mockClearInterval = (id) => {
|
|
802
|
+
timers = timers.filter((t) => t.id !== id);
|
|
803
|
+
};
|
|
804
|
+
global.setTimeout = mockSetTimeout;
|
|
805
|
+
global.clearTimeout = mockClearTimeout;
|
|
806
|
+
global.setInterval = mockSetInterval;
|
|
807
|
+
global.clearInterval = mockClearInterval;
|
|
808
|
+
return {
|
|
809
|
+
tick() {
|
|
810
|
+
this.advanceBy(0);
|
|
811
|
+
},
|
|
812
|
+
advanceBy(ms) {
|
|
813
|
+
const targetTime = currentTime + ms;
|
|
814
|
+
while (true) {
|
|
815
|
+
let earliest = null;
|
|
816
|
+
for (const t of timers) {
|
|
817
|
+
if (!earliest || t.dueTime < earliest.dueTime) {
|
|
818
|
+
earliest = t;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
if (!earliest || earliest.dueTime > targetTime) {
|
|
822
|
+
break;
|
|
823
|
+
}
|
|
824
|
+
currentTime = earliest.dueTime;
|
|
825
|
+
const { callback, args, interval, id } = earliest;
|
|
826
|
+
if (interval !== void 0) {
|
|
827
|
+
earliest.dueTime += interval;
|
|
828
|
+
if (interval === 0) earliest.dueTime += 1;
|
|
829
|
+
} else {
|
|
830
|
+
timers = timers.filter((t) => t.id !== id);
|
|
831
|
+
}
|
|
832
|
+
callback(...args);
|
|
833
|
+
}
|
|
834
|
+
currentTime = targetTime;
|
|
835
|
+
},
|
|
836
|
+
clear() {
|
|
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.");
|
|
850
|
+
}
|
|
851
|
+
for (let i = 0; i < count; i++) {
|
|
852
|
+
if (callback) callback(i);
|
|
853
|
+
activeMockRAF.advance(frameTimeMs);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
function simulateFramesWithMock(mock, count, frameTimeMs = 16.6, callback) {
|
|
857
|
+
for (let i = 0; i < count; i++) {
|
|
858
|
+
if (callback) callback(i);
|
|
859
|
+
mock.advance(frameTimeMs);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// src/e2e/playwright.ts
|
|
864
|
+
var import_playwright = require("playwright");
|
|
865
|
+
var import_http = require("http");
|
|
866
|
+
var import_serve_handler = __toESM(require("serve-handler"), 1);
|
|
867
|
+
async function createPlaywrightTestClient(options = {}) {
|
|
868
|
+
let staticServer;
|
|
869
|
+
let clientUrl = options.clientUrl;
|
|
870
|
+
const rootPath = options.rootPath || process.cwd();
|
|
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
|
+
});
|
|
899
|
+
}
|
|
900
|
+
const browser = await import_playwright.chromium.launch({
|
|
901
|
+
headless: options.headless ?? true,
|
|
902
|
+
args: [
|
|
903
|
+
"--use-gl=egl",
|
|
904
|
+
"--ignore-gpu-blocklist",
|
|
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)}`;
|
|
930
|
+
}
|
|
931
|
+
console.log(`Navigating to: ${finalUrl}`);
|
|
932
|
+
await page.goto(finalUrl, { waitUntil: "domcontentloaded" });
|
|
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 });
|
|
948
|
+
}
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
async function waitForGameReady(page, timeout = 1e4) {
|
|
952
|
+
try {
|
|
953
|
+
await page.waitForFunction(() => {
|
|
954
|
+
return window.gameInstance && window.gameInstance.isReady;
|
|
955
|
+
}, null, { timeout });
|
|
956
|
+
} catch (e) {
|
|
957
|
+
await page.waitForSelector("canvas", { timeout });
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
async function captureGameState(page) {
|
|
961
|
+
return await page.evaluate(() => {
|
|
962
|
+
if (window.gameInstance && window.gameInstance.getState) {
|
|
963
|
+
return window.gameInstance.getState();
|
|
964
|
+
}
|
|
965
|
+
return {};
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// src/shared/bsp.ts
|
|
970
|
+
var import_shared = require("@quake2ts/shared");
|
|
971
|
+
function makePlane(normal, dist) {
|
|
972
|
+
return {
|
|
973
|
+
normal,
|
|
974
|
+
dist,
|
|
975
|
+
type: Math.abs(normal.x) === 1 ? 0 : Math.abs(normal.y) === 1 ? 1 : Math.abs(normal.z) === 1 ? 2 : 3,
|
|
976
|
+
signbits: (0, import_shared.computePlaneSignBits)(normal)
|
|
977
|
+
};
|
|
1057
978
|
}
|
|
1058
|
-
function
|
|
1059
|
-
const
|
|
1060
|
-
|
|
979
|
+
function makeAxisBrush(size, contents = import_shared.CONTENTS_SOLID) {
|
|
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
|
+
};
|
|
1061
993
|
}
|
|
1062
|
-
function
|
|
1063
|
-
|
|
1064
|
-
if (callback) callback(i);
|
|
1065
|
-
mock.advance(frameTimeMs);
|
|
1066
|
-
}
|
|
994
|
+
function makeNode(plane, children) {
|
|
995
|
+
return { plane, children };
|
|
1067
996
|
}
|
|
1068
|
-
|
|
1069
|
-
// src/setup/storage.ts
|
|
1070
|
-
var import_auto2 = require("fake-indexeddb/auto");
|
|
1071
|
-
function createMockLocalStorage(initialData = {}) {
|
|
1072
|
-
const storage = new Map(Object.entries(initialData));
|
|
997
|
+
function makeBspModel(planes, nodes, leaves, brushes, leafBrushes) {
|
|
1073
998
|
return {
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
return storage.size;
|
|
1081
|
-
}
|
|
999
|
+
planes,
|
|
1000
|
+
nodes,
|
|
1001
|
+
leaves,
|
|
1002
|
+
brushes,
|
|
1003
|
+
leafBrushes,
|
|
1004
|
+
bmodels: []
|
|
1082
1005
|
};
|
|
1083
1006
|
}
|
|
1084
|
-
function
|
|
1085
|
-
return
|
|
1007
|
+
function makeLeaf(contents, firstLeafBrush, numLeafBrushes) {
|
|
1008
|
+
return { contents, cluster: 0, area: 0, firstLeafBrush, numLeafBrushes };
|
|
1086
1009
|
}
|
|
1087
|
-
function
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1010
|
+
function makeLeafModel(brushes) {
|
|
1011
|
+
const planes = brushes.flatMap((brush) => brush.sides.map((side) => side.plane));
|
|
1012
|
+
return {
|
|
1013
|
+
planes,
|
|
1014
|
+
nodes: [],
|
|
1015
|
+
leaves: [makeLeaf(0, 0, brushes.length)],
|
|
1016
|
+
brushes,
|
|
1017
|
+
leafBrushes: brushes.map((_, i) => i),
|
|
1018
|
+
bmodels: []
|
|
1019
|
+
};
|
|
1092
1020
|
}
|
|
1093
|
-
function
|
|
1094
|
-
const
|
|
1021
|
+
function makeBrushFromMinsMaxs(mins, maxs, contents = import_shared.CONTENTS_SOLID) {
|
|
1022
|
+
const planes = [
|
|
1023
|
+
makePlane({ x: 1, y: 0, z: 0 }, maxs.x),
|
|
1024
|
+
makePlane({ x: -1, y: 0, z: 0 }, -mins.x),
|
|
1025
|
+
makePlane({ x: 0, y: 1, z: 0 }, maxs.y),
|
|
1026
|
+
makePlane({ x: 0, y: -1, z: 0 }, -mins.y),
|
|
1027
|
+
makePlane({ x: 0, y: 0, z: 1 }, maxs.z),
|
|
1028
|
+
makePlane({ x: 0, y: 0, z: -1 }, -mins.z)
|
|
1029
|
+
];
|
|
1095
1030
|
return {
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
Object.entries(data).forEach(([k, v]) => storage.setItem(k, v));
|
|
1099
|
-
},
|
|
1100
|
-
verify(key, value) {
|
|
1101
|
-
return storage.getItem(key) === value;
|
|
1102
|
-
}
|
|
1031
|
+
contents,
|
|
1032
|
+
sides: planes.map((plane) => ({ plane, surfaceFlags: 0 }))
|
|
1103
1033
|
};
|
|
1104
1034
|
}
|
|
1105
1035
|
|
|
1106
|
-
// src/
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1036
|
+
// src/shared/mocks.ts
|
|
1037
|
+
var import_vitest = require("vitest");
|
|
1038
|
+
var createBinaryWriterMock = () => ({
|
|
1039
|
+
writeByte: import_vitest.vi.fn(),
|
|
1040
|
+
writeShort: import_vitest.vi.fn(),
|
|
1041
|
+
writeLong: import_vitest.vi.fn(),
|
|
1042
|
+
writeString: import_vitest.vi.fn(),
|
|
1043
|
+
writeBytes: import_vitest.vi.fn(),
|
|
1044
|
+
getBuffer: import_vitest.vi.fn(() => new Uint8Array(0)),
|
|
1045
|
+
reset: import_vitest.vi.fn(),
|
|
1046
|
+
// Legacy methods (if any)
|
|
1047
|
+
writeInt8: import_vitest.vi.fn(),
|
|
1048
|
+
writeUint8: import_vitest.vi.fn(),
|
|
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));
|
|
1114
1219
|
}),
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
},
|
|
1118
|
-
start: () => {
|
|
1119
|
-
},
|
|
1120
|
-
stop: () => {
|
|
1121
|
-
},
|
|
1122
|
-
frequency: { value: 440 }
|
|
1220
|
+
unregisterEntitySpawn: import_vitest2.vi.fn((classname) => {
|
|
1221
|
+
spawnRegistry.unregister(classname);
|
|
1123
1222
|
}),
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
stop: () => {
|
|
1130
|
-
},
|
|
1131
|
-
buffer: null,
|
|
1132
|
-
playbackRate: { value: 1 },
|
|
1133
|
-
loop: false
|
|
1223
|
+
getCustomEntities: import_vitest2.vi.fn(() => Array.from(spawnRegistry.keys())),
|
|
1224
|
+
hooks,
|
|
1225
|
+
registerHooks: import_vitest2.vi.fn((newHooks) => hooks.register(newHooks)),
|
|
1226
|
+
spawnWorld: import_vitest2.vi.fn(() => {
|
|
1227
|
+
hooks.onMapLoad("q2dm1");
|
|
1134
1228
|
}),
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
state: "running",
|
|
1138
|
-
resume: async () => {
|
|
1139
|
-
},
|
|
1140
|
-
suspend: async () => {
|
|
1141
|
-
},
|
|
1142
|
-
close: async () => {
|
|
1143
|
-
},
|
|
1144
|
-
decodeAudioData: async (buffer) => ({
|
|
1145
|
-
duration: 1,
|
|
1146
|
-
length: 44100,
|
|
1147
|
-
sampleRate: 44100,
|
|
1148
|
-
numberOfChannels: 2,
|
|
1149
|
-
getChannelData: () => new Float32Array(44100)
|
|
1229
|
+
clientBegin: import_vitest2.vi.fn((client) => {
|
|
1230
|
+
hooks.onPlayerSpawn({});
|
|
1150
1231
|
}),
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
length,
|
|
1154
|
-
sampleRate,
|
|
1155
|
-
numberOfChannels: channels,
|
|
1156
|
-
getChannelData: () => new Float32Array(length)
|
|
1232
|
+
damage: import_vitest2.vi.fn((amount) => {
|
|
1233
|
+
hooks.onDamage({}, null, null, amount, 0, 0);
|
|
1157
1234
|
})
|
|
1158
1235
|
};
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1236
|
+
return { game, spawnRegistry };
|
|
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;
|
|
1260
|
+
}),
|
|
1261
|
+
free: import_vitest2.vi.fn((ent) => {
|
|
1262
|
+
const idx = entityList.indexOf(ent);
|
|
1263
|
+
if (idx !== -1) {
|
|
1264
|
+
entityList.splice(idx, 1);
|
|
1165
1265
|
}
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
delete global.AudioContext;
|
|
1174
|
-
delete global.window.AudioContext;
|
|
1175
|
-
delete global.window.webkitAudioContext;
|
|
1176
|
-
}
|
|
1177
|
-
}
|
|
1178
|
-
function captureAudioEvents(context) {
|
|
1179
|
-
return [];
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
// src/e2e/playwright.ts
|
|
1183
|
-
var import_playwright = require("playwright");
|
|
1184
|
-
async function createPlaywrightTestClient(options = {}) {
|
|
1185
|
-
const browser = await import_playwright.chromium.launch({
|
|
1186
|
-
headless: options.headless ?? true,
|
|
1187
|
-
args: options.args || [
|
|
1188
|
-
"--use-gl=egl",
|
|
1189
|
-
"--ignore-gpu-blocklist",
|
|
1190
|
-
"--no-sandbox",
|
|
1191
|
-
"--disable-setuid-sandbox"
|
|
1192
|
-
]
|
|
1193
|
-
});
|
|
1194
|
-
const context = await browser.newContext({
|
|
1195
|
-
viewport: options.viewport || { width: 1280, height: 720 },
|
|
1196
|
-
recordVideo: options.recordVideo,
|
|
1197
|
-
deviceScaleFactor: 1
|
|
1198
|
-
});
|
|
1199
|
-
const page = await context.newPage();
|
|
1200
|
-
const waitForGame = async (timeout = 1e4) => {
|
|
1201
|
-
try {
|
|
1202
|
-
await page.waitForFunction(() => {
|
|
1203
|
-
return window.game || document.querySelector("canvas");
|
|
1204
|
-
}, null, { timeout });
|
|
1205
|
-
} catch (e) {
|
|
1206
|
-
throw new Error(`Game did not initialize within ${timeout}ms`);
|
|
1207
|
-
}
|
|
1208
|
-
};
|
|
1209
|
-
const client = {
|
|
1210
|
-
browser,
|
|
1211
|
-
context,
|
|
1212
|
-
page,
|
|
1213
|
-
async navigate(url) {
|
|
1214
|
-
await page.goto(url, { waitUntil: "domcontentloaded" });
|
|
1215
|
-
},
|
|
1216
|
-
async waitForGame(timeout) {
|
|
1217
|
-
await waitForGame(timeout);
|
|
1218
|
-
},
|
|
1219
|
-
async injectInput(type, key) {
|
|
1220
|
-
if (type === "keydown") {
|
|
1221
|
-
await page.keyboard.down(key);
|
|
1222
|
-
} else {
|
|
1223
|
-
await page.keyboard.up(key);
|
|
1266
|
+
hooks.onEntityRemove(ent);
|
|
1267
|
+
}),
|
|
1268
|
+
finalizeSpawn: import_vitest2.vi.fn(),
|
|
1269
|
+
freeImmediate: import_vitest2.vi.fn((ent) => {
|
|
1270
|
+
const idx = entityList.indexOf(ent);
|
|
1271
|
+
if (idx !== -1) {
|
|
1272
|
+
entityList.splice(idx, 1);
|
|
1224
1273
|
}
|
|
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)
|
|
1225
1303
|
},
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
} else if (type === "up") {
|
|
1232
|
-
await page.mouse.up({ button: button === 0 ? "left" : button === 2 ? "right" : "middle" });
|
|
1233
|
-
}
|
|
1304
|
+
level: {
|
|
1305
|
+
intermission_angle: { x: 0, y: 0, z: 0 },
|
|
1306
|
+
intermission_origin: { x: 0, y: 0, z: 0 },
|
|
1307
|
+
next_auto_save: 0,
|
|
1308
|
+
health_bar_entities: null
|
|
1234
1309
|
},
|
|
1235
|
-
|
|
1236
|
-
|
|
1310
|
+
targetNameIndex: /* @__PURE__ */ new Map(),
|
|
1311
|
+
forEachEntity: import_vitest2.vi.fn((callback) => {
|
|
1312
|
+
entityList.forEach(callback);
|
|
1313
|
+
}),
|
|
1314
|
+
find: import_vitest2.vi.fn((predicate) => {
|
|
1315
|
+
return entityList.find(predicate);
|
|
1316
|
+
}),
|
|
1317
|
+
findByClassname: import_vitest2.vi.fn((classname) => {
|
|
1318
|
+
return entityList.find((e) => e.classname === classname);
|
|
1319
|
+
}),
|
|
1320
|
+
beginFrame: import_vitest2.vi.fn((timeSeconds) => {
|
|
1321
|
+
entities.timeSeconds = timeSeconds;
|
|
1322
|
+
}),
|
|
1323
|
+
targetAwareness: {
|
|
1324
|
+
timeSeconds: 10,
|
|
1325
|
+
frameNumber: 1,
|
|
1326
|
+
sightEntity: null,
|
|
1327
|
+
soundEntity: null
|
|
1237
1328
|
},
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1329
|
+
// Adding missing properties to satisfy EntitySystem interface partially or fully
|
|
1330
|
+
// We cast to unknown first anyway, but filling these in makes it safer for consumers
|
|
1331
|
+
skill: 1,
|
|
1332
|
+
deathmatch: false,
|
|
1333
|
+
coop: false,
|
|
1334
|
+
activeCount: entityList.length,
|
|
1335
|
+
world: entityList.find((e) => e.classname === "worldspawn") || new import_game.Entity(0)
|
|
1336
|
+
// ... other EntitySystem properties would go here
|
|
1241
1337
|
};
|
|
1242
|
-
return client;
|
|
1243
|
-
}
|
|
1244
|
-
async function waitForGameReady(page, timeout = 1e4) {
|
|
1245
|
-
await page.waitForFunction(() => {
|
|
1246
|
-
return window.game || document.querySelector("canvas");
|
|
1247
|
-
}, null, { timeout });
|
|
1248
|
-
}
|
|
1249
|
-
async function captureGameState(page) {
|
|
1250
|
-
return await page.evaluate(() => {
|
|
1251
|
-
const game = window.game;
|
|
1252
|
-
if (!game) return {};
|
|
1253
|
-
return {};
|
|
1254
|
-
});
|
|
1255
|
-
}
|
|
1256
|
-
|
|
1257
|
-
// src/e2e/network.ts
|
|
1258
|
-
var CONDITIONS = {
|
|
1259
|
-
"good": {
|
|
1260
|
-
offline: false,
|
|
1261
|
-
downloadThroughput: 10 * 1024 * 1024,
|
|
1262
|
-
// 10 Mbps
|
|
1263
|
-
uploadThroughput: 5 * 1024 * 1024,
|
|
1264
|
-
// 5 Mbps
|
|
1265
|
-
latency: 20
|
|
1266
|
-
},
|
|
1267
|
-
"slow": {
|
|
1268
|
-
offline: false,
|
|
1269
|
-
downloadThroughput: 500 * 1024,
|
|
1270
|
-
// 500 Kbps
|
|
1271
|
-
uploadThroughput: 500 * 1024,
|
|
1272
|
-
latency: 400
|
|
1273
|
-
},
|
|
1274
|
-
"unstable": {
|
|
1275
|
-
offline: false,
|
|
1276
|
-
downloadThroughput: 1 * 1024 * 1024,
|
|
1277
|
-
uploadThroughput: 1 * 1024 * 1024,
|
|
1278
|
-
latency: 100
|
|
1279
|
-
// Jitter needs logic not simple preset
|
|
1280
|
-
},
|
|
1281
|
-
"offline": {
|
|
1282
|
-
offline: true,
|
|
1283
|
-
downloadThroughput: 0,
|
|
1284
|
-
uploadThroughput: 0,
|
|
1285
|
-
latency: 0
|
|
1286
|
-
}
|
|
1287
|
-
};
|
|
1288
|
-
function simulateNetworkCondition(condition) {
|
|
1289
|
-
const config = CONDITIONS[condition];
|
|
1290
|
-
return createCustomNetworkCondition(config.latency, 0, 0, config);
|
|
1291
|
-
}
|
|
1292
|
-
function createCustomNetworkCondition(latency, jitter = 0, packetLoss = 0, baseConfig) {
|
|
1293
1338
|
return {
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
async clear(page) {
|
|
1306
|
-
const client = await page.context().newCDPSession(page);
|
|
1307
|
-
await client.send("Network.emulateNetworkConditions", {
|
|
1308
|
-
offline: false,
|
|
1309
|
-
latency: 0,
|
|
1310
|
-
downloadThroughput: -1,
|
|
1311
|
-
uploadThroughput: -1
|
|
1312
|
-
});
|
|
1313
|
-
}
|
|
1339
|
+
keyValues: {},
|
|
1340
|
+
entities,
|
|
1341
|
+
game,
|
|
1342
|
+
engine,
|
|
1343
|
+
health_multiplier: 1,
|
|
1344
|
+
warn: import_vitest2.vi.fn(),
|
|
1345
|
+
free: import_vitest2.vi.fn(),
|
|
1346
|
+
// Mock precache functions if they are part of SpawnContext in future or TestContext extensions
|
|
1347
|
+
precacheModel: import_vitest2.vi.fn(),
|
|
1348
|
+
precacheSound: import_vitest2.vi.fn(),
|
|
1349
|
+
precacheImage: import_vitest2.vi.fn()
|
|
1314
1350
|
};
|
|
1315
1351
|
}
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
offline: false,
|
|
1319
|
-
latency: 0,
|
|
1320
|
-
downloadThroughput: bytesPerSecond,
|
|
1321
|
-
uploadThroughput: bytesPerSecond
|
|
1322
|
-
});
|
|
1323
|
-
await simulator.apply(page);
|
|
1324
|
-
}
|
|
1325
|
-
|
|
1326
|
-
// src/e2e/visual.ts
|
|
1327
|
-
var import_path = __toESM(require("path"), 1);
|
|
1328
|
-
var import_promises = __toESM(require("fs/promises"), 1);
|
|
1329
|
-
async function captureGameScreenshot(page, name, options = {}) {
|
|
1330
|
-
const dir = options.dir || "__screenshots__";
|
|
1331
|
-
const screenshotPath = import_path.default.join(dir, `${name}.png`);
|
|
1332
|
-
await import_promises.default.mkdir(dir, { recursive: true });
|
|
1333
|
-
return await page.screenshot({
|
|
1334
|
-
path: screenshotPath,
|
|
1335
|
-
fullPage: options.fullPage ?? false,
|
|
1336
|
-
animations: "disabled",
|
|
1337
|
-
// Try to freeze animations if possible
|
|
1338
|
-
caret: "hide"
|
|
1339
|
-
});
|
|
1340
|
-
}
|
|
1341
|
-
async function compareScreenshots(baseline, current, threshold = 0.1) {
|
|
1342
|
-
if (baseline.equals(current)) {
|
|
1343
|
-
return { pixelDiff: 0, matched: true };
|
|
1344
|
-
}
|
|
1345
|
-
return {
|
|
1346
|
-
pixelDiff: -1,
|
|
1347
|
-
// Unknown magnitude
|
|
1348
|
-
matched: false
|
|
1349
|
-
};
|
|
1352
|
+
function createSpawnContext() {
|
|
1353
|
+
return createTestContext();
|
|
1350
1354
|
}
|
|
1351
|
-
function
|
|
1352
|
-
return
|
|
1353
|
-
async capture(snapshotName) {
|
|
1354
|
-
return await captureGameScreenshot(page, `${sceneName}-${snapshotName}`);
|
|
1355
|
-
},
|
|
1356
|
-
async compare(snapshotName, baselineDir) {
|
|
1357
|
-
const name = `${sceneName}-${snapshotName}`;
|
|
1358
|
-
const current = await captureGameScreenshot(page, name, { dir: "__screenshots__/current" });
|
|
1359
|
-
try {
|
|
1360
|
-
const baselinePath = import_path.default.join(baselineDir, `${name}.png`);
|
|
1361
|
-
const baseline = await import_promises.default.readFile(baselinePath);
|
|
1362
|
-
return await compareScreenshots(baseline, current);
|
|
1363
|
-
} catch (e) {
|
|
1364
|
-
return { pixelDiff: -1, matched: false };
|
|
1365
|
-
}
|
|
1366
|
-
}
|
|
1367
|
-
};
|
|
1355
|
+
function createEntity() {
|
|
1356
|
+
return new import_game.Entity(1);
|
|
1368
1357
|
}
|
|
1369
1358
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1370
1359
|
0 && (module.exports = {
|
|
@@ -1372,12 +1361,10 @@ function createVisualTestScenario(page, sceneName) {
|
|
|
1372
1361
|
MockPointerLock,
|
|
1373
1362
|
captureAudioEvents,
|
|
1374
1363
|
captureCanvasDrawCalls,
|
|
1375
|
-
captureGameScreenshot,
|
|
1376
1364
|
captureGameState,
|
|
1377
|
-
compareScreenshots,
|
|
1378
1365
|
createBinaryStreamMock,
|
|
1379
1366
|
createBinaryWriterMock,
|
|
1380
|
-
|
|
1367
|
+
createControlledTimer,
|
|
1381
1368
|
createEntity,
|
|
1382
1369
|
createEntityStateFactory,
|
|
1383
1370
|
createGameStateSnapshotFactory,
|
|
@@ -1400,7 +1387,6 @@ function createVisualTestScenario(page, sceneName) {
|
|
|
1400
1387
|
createSpawnContext,
|
|
1401
1388
|
createStorageTestScenario,
|
|
1402
1389
|
createTestContext,
|
|
1403
|
-
createVisualTestScenario,
|
|
1404
1390
|
intersects,
|
|
1405
1391
|
ladderTrace,
|
|
1406
1392
|
makeAxisBrush,
|
|
@@ -1415,11 +1401,9 @@ function createVisualTestScenario(page, sceneName) {
|
|
|
1415
1401
|
setupNodeEnvironment,
|
|
1416
1402
|
simulateFrames,
|
|
1417
1403
|
simulateFramesWithMock,
|
|
1418
|
-
simulateNetworkCondition,
|
|
1419
1404
|
stairTrace,
|
|
1420
1405
|
teardownBrowserEnvironment,
|
|
1421
1406
|
teardownMockAudioContext,
|
|
1422
|
-
throttleBandwidth,
|
|
1423
1407
|
waitForGameReady
|
|
1424
1408
|
});
|
|
1425
1409
|
//# sourceMappingURL=index.cjs.map
|